diff --git a/.github/workflows/Dockerfile b/.github/workflows/Dockerfile index 5e54b9ece..93dad7e5c 100644 --- a/.github/workflows/Dockerfile +++ b/.github/workflows/Dockerfile @@ -1,14 +1,30 @@ # An image derived from ledgerhq/speculos but also containing the bitcoin-core binaries +# compiled from the master branch FROM ghcr.io/ledgerhq/speculos:latest -# install curl -RUN apt update -y && apt install -y curl +# install git and curl +RUN apt update -y && apt install -y git curl -# download bitcoin-core and decompress it to /bitcoin -RUN curl -o /tmp/bitcoin.tar.gz https://bitcoin.org/bin/bitcoin-core-22.0/bitcoin-22.0-x86_64-linux-gnu.tar.gz && \ - tar -xf /tmp/bitcoin.tar.gz -C / && \ - mv /bitcoin-22.0 /bitcoin +# install autotools bitcoin-core build dependencies +RUN apt install -y automake autotools-dev bsdmainutils build-essential ccache git libboost-dev libboost-filesystem-dev libboost-system-dev libboost-test-dev libevent-dev libminiupnpc-dev libnatpmp-dev libqt5gui5 libqt5core5a libqt5dbus5 libsqlite3-dev libtool libzmq3-dev pkg-config python3 qttools5-dev qttools5-dev-tools qtwayland5 systemtap-sdt-dev + +# clone bitcoin-core from github and compile it +RUN cd / && \ + git clone --depth=1 https://github.com/bitcoin/bitcoin.git && \ + cd bitcoin && \ + ./autogen.sh && \ + ./configure --enable-suppress-external-warnings && \ + make -j "$(($(nproc)+1))" && \ + mkdir bin && \ + cp src/bitcoind src/bitcoin-cli src/bitcoin-tx src/bitcoin-util src/bitcoin-wallet ./bin + + +FROM ghcr.io/ledgerhq/speculos:latest +COPY --from=0 /bitcoin/bin /bitcoin/bin + +# install runtime dependencies for bitcoind +RUN apt update -y && apt install -y libminiupnpc-dev libminiupnpc-dev libnatpmp-dev libevent-dev libzmq3-dev # Add bitcoin binaries to path ENV PATH=/bitcoin/bin:$PATH diff --git a/.github/workflows/build_and_functional_tests.yml b/.github/workflows/build_and_functional_tests.yml new file mode 100644 index 000000000..7e57e5c65 --- /dev/null +++ b/.github/workflows/build_and_functional_tests.yml @@ -0,0 +1,34 @@ +name: Build and run functional tests using ragger through reusable workflow + +# This workflow will build the app and then run functional tests using the Ragger framework upon Speculos emulation. +# It calls a reusable workflow developed by Ledger's internal developer team to build the application and upload the +# resulting binaries. +# It then calls another reusable workflow to run the Ragger tests on the compiled application binary. +# +# While this workflow is optional, having functional testing on your application is mandatory and this workflow and +# tooling environment is meant to be easy to use and adapt after forking your application + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + build_application: + name: Build application using the reusable workflow + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1 + with: + upload_app_binaries_artifact: "compiled_app_binaries" + flags: "DEBUG=0 COIN=bitcoin_testnet" + + ragger_tests: + name: Run ragger tests using the reusable workflow + needs: build_application + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1 + with: + download_app_binaries_artifact: "compiled_app_binaries" + diff --git a/.github/workflows/builder-image-workflow.yml b/.github/workflows/builder-image-workflow.yml index b5ab35ea1..11128cc20 100644 --- a/.github/workflows/builder-image-workflow.yml +++ b/.github/workflows/builder-image-workflow.yml @@ -1,13 +1,11 @@ name: Build custom speculos-bitcoin image on: + workflow_dispatch: push: branches: - master - develop - paths: - - .github/workflows/builder-image-workflow.yml - - .github/workflows/Dockerfile jobs: build: diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index bbabb75f9..910682551 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -13,7 +13,20 @@ on: jobs: job_build: - name: Compilation + name: Compilation for NanoS, X, S+, and Stax + + strategy: + matrix: + include: + - model: nanos + SDK: "$NANOS_SDK" + - model: nanox + SDK: "$NANOX_SDK" + - model: nanosp + SDK: "$NANOSP_SDK" + - model: stax + SDK: "$STAX_SDK" + runs-on: ubuntu-latest container: @@ -25,29 +38,21 @@ jobs: - name: Build run: | - make DEBUG=0 COIN=bitcoin && mv bin/ bitcoin-bin/ - make clean - make DEBUG=0 COIN=bitcoin_testnet && mv bin/ bitcoin-testnet-bin/ + make DEBUG=0 COIN=bitcoin BOLOS_SDK=${{ matrix.SDK }} && mv bin/ bitcoin-bin/ make clean - make DEBUG=0 COIN=bitcoin_testnet_lib && mv bin/ bitcoin-testnet-lib-bin/ + make DEBUG=0 COIN=bitcoin_testnet BOLOS_SDK=${{ matrix.SDK }} && mv bin/ bitcoin-testnet-bin/ - name: Upload Bitcoin app binary uses: actions/upload-artifact@v2 with: - name: bitcoin-app + name: bitcoin-app-${{ matrix.model }} path: bitcoin-bin - name: Upload Bitcoin Testnet app binary uses: actions/upload-artifact@v2 with: - name: bitcoin-testnet-app + name: bitcoin-testnet-app-${{ matrix.model }} path: bitcoin-testnet-bin - - name: Upload Bitcoin Testnet app binary (lib version) - uses: actions/upload-artifact@v2 - with: - name: bitcoin-testnet-lib-app - path: bitcoin-testnet-lib-bin - job_unit_test: name: Unit test needs: job_build @@ -78,10 +83,10 @@ jobs: path: unit-tests/coverage - name: Upload to codecov.io - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - file: ./unit-tests/coverage.info + files: ./unit-tests/coverage.info flags: unittests name: codecov-app-bitcoin fail_ci_if_error: true @@ -95,8 +100,16 @@ jobs: name: documentation path: doc/html - job_test: - name: Tests + job_test_mainnet: + name: Tests on mainnet + strategy: + matrix: + include: + - model: nanos + - model: nanox + - model: nanosp + - model: stax + needs: job_build runs-on: ubuntu-latest @@ -115,20 +128,20 @@ jobs: - name: Clone uses: actions/checkout@v2 - - name: Download Bitcoin Testnet app binary + - name: Download Bitcoin app binary uses: actions/download-artifact@v2 with: - name: bitcoin-testnet-app + name: bitcoin-app-${{matrix.model}} path: bin - name: Run tests run: | - cd tests + cd tests_mainnet pip install -r requirements.txt - PYTHONPATH=$PYTHONPATH:/speculos pytest --headless + PYTHONPATH=$PYTHONPATH:/speculos pytest --headless --model=${{ matrix.model }} --timeout=300 - job_test_mainnet: - name: Tests on mainnet + job_test_python_lib_legacyapp: + name: Tests with the Python library and legacy Bitcoin app needs: job_build runs-on: ubuntu-latest @@ -147,17 +160,12 @@ jobs: - name: Clone uses: actions/checkout@v2 - - name: Download Bitcoin app binary - uses: actions/download-artifact@v2 - with: - name: bitcoin-app - path: bin - - name: Run tests run: | - cd tests_mainnet + cd bitcoin_client/tests pip install -r requirements.txt - PYTHONPATH=$PYTHONPATH:/speculos pytest --headless + PYTHONPATH=$PYTHONPATH:/speculos pytest --headless --timeout=300 + job_test_js_lib: name: Tests with the JS library @@ -191,7 +199,7 @@ jobs: - name: Download Bitcoin Testnet app binary uses: actions/download-artifact@v2 with: - name: bitcoin-testnet-app + name: bitcoin-testnet-app-nanos path: bin - name: Run tests @@ -200,74 +208,20 @@ jobs: yarn install LOG_SPECULOS=1 LOG_APDUS=1 SPECULOS="/speculos/speculos.py" yarn test - job_test_legacy_native: - name: Legacy tests - needs: job_build - runs-on: ubuntu-latest - - container: - image: ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin:latest - ports: - - 1234:1234 - - 9999:9999 - - 40000:40000 - - 41000:41000 - - 42000:42000 - - 43000:43000 - options: --entrypoint /bin/bash - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Download Bitcoin Testnet app binary - uses: actions/download-artifact@v2 - with: - name: bitcoin-testnet-app - path: tests-legacy/bitcoin-testnet-bin - - - name: Run tests - run: | - cd tests-legacy - pip install -r requirements.txt - PATH=$PATH:/speculos pytest - - - job_test_legacy_lib: - name: Legacy tests (library) + job_test_rust_client: + name: Tests for rust client library needs: job_build runs-on: ubuntu-latest container: - image: ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin:latest - ports: - - 1234:1234 - - 9999:9999 - - 40000:40000 - - 41000:41000 - - 42000:42000 - - 43000:43000 - options: --entrypoint /bin/bash + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest steps: - name: Clone uses: actions/checkout@v2 - - name: Download Bitcoin app binary - uses: actions/download-artifact@v2 - with: - name: bitcoin-app - path: tests-legacy/bitcoin-bin - - - name: Download Bitcoin Testnet app binary (library version) - uses: actions/download-artifact@v2 - with: - name: bitcoin-testnet-lib-app - path: tests-legacy/bitcoin-testnet-bin - - - name: Run tests run: | - cd tests-legacy - pip install -r requirements.txt - PATH=$PATH:/speculos pytest + cd bitcoin_client_rs/ + cargo test --no-default-features --features="async" diff --git a/.github/workflows/codeql-workflow.yml b/.github/workflows/codeql-workflow.yml new file mode 100644 index 000000000..05c758ae5 --- /dev/null +++ b/.github/workflows/codeql-workflow.yml @@ -0,0 +1,47 @@ +name: "CodeQL" + +on: + workflow_dispatch: + push: + branches: + - master + - develop + pull_request: + branches: + - master + - develop + +jobs: + analyse: + name: CodeQL Analyse of boilerplate application + strategy: + matrix: + include: + - SDK: "$NANOS_SDK" + artifact: boilerplate-app-nanoS + - SDK: "$NANOX_SDK" + artifact: boilerplate-app-nanoX + - SDK: "$NANOSP_SDK" + artifact: boilerplate-app-nanoSP + language: [ 'cpp' ] + runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest + + steps: + - name: Clone + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: security-and-quality + + - name: Build + run: | + make BOLOS_SDK=${{ matrix.SDK }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + diff --git a/.github/workflows/guidelines-enforcer.yml b/.github/workflows/guidelines-enforcer.yml new file mode 100644 index 000000000..c154d6cf5 --- /dev/null +++ b/.github/workflows/guidelines-enforcer.yml @@ -0,0 +1,22 @@ +name: Ensure compliance with Ledger guidelines + +# This workflow is mandatory in all applications +# It calls a reusable workflow guidelines_enforcer developed by Ledger's internal developer team. +# The successful completion of the reusable workflow is a mandatory step for an app to be available on the Ledger +# application store. +# +# More information on the guidelines can be found in the repository: +# LedgerHQ/ledger-app-workflows/ + +on: + workflow_dispatch: + push: + branches: + - master + - develop + pull_request: + +jobs: + guidelines_enforcer: + name: Call Ledger guidelines_enforcer + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 693bcc4a8..6269b0103 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -12,55 +12,37 @@ on: types: [opened, synchronize, reopened] jobs: - build: - name: Build + sonarcloud: runs-on: ubuntu-latest container: - image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest + env: - SONAR_SCANNER_VERSION: 4.4.0.2170 - SONAR_SERVER_URL: "https://sonarcloud.io" BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Download and set up sonar-scanner - env: - SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip - run: | - apt-get update -y - apt-get upgrade -y - apt-get install -y - curl -sL https://deb.nodesource.com/setup_16.x | bash - - apt-get install -y gcovr nodejs unzip - mkdir -p $HOME/.sonar - curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} - unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ - echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH - - name: Download and set up build-wrapper - env: - BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip - run: | - curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }} - unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ - echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH - - name: Generate code coverage - run: | - cd unit-tests/ - cmake -Bbuild -H. && make -C build - make -C build test - gcovr --root .. --sonarqube coverage.xml - - name: Run build-wrapper - run: | + - uses: actions/checkout@v3 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + - name: Install dependencies + run: | + apt-get update -y + apt-get upgrade -y + DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata + apt-get install -y libcmocka-dev gcovr unzip + - name: Install sonar-scanner and build-wrapper + uses: sonarsource/sonarcloud-github-c-cpp@v2 + - name: Generate code coverage + run: | + cd unit-tests/ + cmake -Bbuild -H. && make -C build + make -C build test + gcovr --root .. --sonarqube coverage.xml + - name: Run build-wrapper + run: | build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make clean all - - name: Run sonar-scanner - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" \ No newline at end of file + - name: Run sonar-scanner + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" #Consult https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-cli/ for more information and options \ No newline at end of file diff --git a/.github/workflows/swap-ci-workflow.yml b/.github/workflows/swap-ci-workflow.yml new file mode 100644 index 000000000..85225ecac --- /dev/null +++ b/.github/workflows/swap-ci-workflow.yml @@ -0,0 +1,16 @@ +name: Swap functional tests + +on: + workflow_dispatch: + push: + branches: + - master + - develop + pull_request: + +jobs: + job_functional_tests: + uses: LedgerHQ/app-exchange/.github/workflows/reusable_swap_functional_tests.yml@develop + with: + branch_for_bitcoin: ${{ github.ref }} + test_filter: '"btc or bitcoin or Bitcoin"' diff --git a/.gitignore b/.gitignore index 97a1d7083..3a33f742d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,11 +12,9 @@ unit-tests/build/ unit-tests/coverage/ unit-tests/coverage.info -tests-legacy/bitcoin-bin -tests-legacy/bitcoin-testnet-bin - # temporary folder used during tests tests/.test_bitcoin +tests/snapshots-tmp # Fuzzing fuzzing/build/ @@ -35,13 +33,12 @@ __pycache__/ .eggs/ .python-version .pytest_cache +venv/ +ledger/ # Doxygen doc/html doc/latex -# Python virtual environment -.venv* - # Mac directory metadata -.DS_Store +.DS_Store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a2e6974b..65d405494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,106 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Dates are in `dd-mm-yyyy` format. +## [2.2.3] - 06-05-2024 + +### Added + +- Support for signing transactions with `OP_RETURN` outputs extended to up to 5 push opcodes, instead of a single one. + +## [2.2.2] - 08-04-2024 + +### Added + +- During wallet policy registration, the app will recognize and explicitly label as `dummy` any extended public key whose compressed pubkey is `0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0`. This is useful especially for taproot miniscript policies which do not intend to use keypath spending. + +### Changed + +- Message signing: will now show the full text of the message, instead of its hash. If the message is too long (over 640 characters) or it contains non-printable characters (not in the range `0x20..0x70`, inclusive), then the SHA256 hash will be shown, as in previous versions of the app. +- Transaction signing: changed the wording to make the ux slightly simpler and clearer. + +## [2.2.1] - 18-03-2024 + +### Fixed + +- Signing failure for certain taproot policies in versions 2.1.2, 2.1.3 and 2.2.0: returned tapleaf hashes (and corresponding signatures) are incorrect if the descriptor template has a derivation path not ending for `/**` or `/<0;1>/*` for that key. + +## [2.2.0] - 29-01-2024 + +### Added + +- 🥕 Support for miniscript on taproot wallet policies. +- Warning if the fees are above 10% of the amount, if the total amount is above 10000 sats (0.0001 ₿). + +### Changed + +- Increased limits for the maximum in-memory size of wallet policies. + +## [2.1.3] - 21-06-2023 + +### Changed + +- Improved UX for self-transfers, that is, transactions where all the outputs are change outputs. +- Outputs containing a single `OP_RETURN` (without any data push) can now be signed in order to support [BIP-0322](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki) implementations. + + +### Fixed + +- Wrong address generation for miniscript policies containing an unusual `thresh(1,X)` fragment (that is, with threshold 1, and a single condition). This should not happen in practice, as the policy is redundant for just `X`. Client libraries have been updated to detect and prevent usage of these policies. +- Resolved a slight regression in signing performance introduced in v2.1.2. + +## [2.1.2] - 03-04-2023 + +### Added + +- 🥕 Initial support for taproot scripts; taproot trees support up to 8 leaves, and the only supported scripts in tapleaves are `pk`, `multi_a` and `sortedmulti_a`. + +### Fixed + +- Miniscript policies containing an `a:` fragment returned an incorrect address in versions `2.1.0` and `2.1.1` of the app. The **upgrade is strongly recommended** for users of miniscript wallets. +- The app will now reject showing or returning an address for a wallet policy if the `address_index` is larger than or equal to `2147483648`; previous version would return an address for a hardened derivation, which is undesirable. +- Nested segwit transactions (P2SH-P2WPKH and P2SH-P2WSH) can now be signed (with a warning) if the PSBT contains the witness-utxo but no non-witness-utxo. This aligns their behavior to other types of Segwitv0 transactions since version 2.0.6. + +## [2.1.1] - 23-01-2023 + +### Changed + +- Allow silent xpub exports at the `m/45'/coin_type'/account'` derivation paths. +- Allow silent xpub exports for any unhardened child of an allowed path. +- Allow up to 8 derivation steps for BIP-32 paths (instead of 6). + +## [2.1.0] - 16-11-2022 + +### Added + +- Miniscript support on SegWit. +- Improved support for wallet policies. +- Support for sighash flags. + +### Changed + +- Wallet policies now allow external keys with no key origin information. +- Wallet policies now allow multiple internal keys. + +### Removed + +- Support for legacy protocol (pre-2.0.0 version) and support for altcoins, now done via separate apps. Substantial binary size reduction as a consequence. + +## [2.0.6] - 06-06-2022 + +### Added + +- Support signing of segwit V0 transactions with unverified inputs for compatibility with software unable to provide the previous transaction. + +### Fixed + +- Fixed bug preventing signing transactions with external inputs (or with mixed script types). + +## [2.0.5] - 03-05-2022 + +### Changed + +- Technical release; restore compatibility with some client libraries that rely on deprecated legacy behavior. + ## [2.0.4] - 28-03-2022 ### Added diff --git a/Makefile b/Makefile index 84bdd7561..0f5cfed61 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # **************************************************************************** # Ledger App for Bitcoin -# (c) 2021 Ledger SAS. +# (c) 2024 Ledger SAS. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,10 +15,12 @@ # limitations under the License. # **************************************************************************** -ifdef DEBUG ifndef DEBUG_LOG_LEVEL -DEBUG_LOG_LEVEL := $(DEBUG) -endif + ifdef DEBUG + DEBUG_LOG_LEVEL := $(DEBUG) + else + DEBUG_LOG_LEVEL := 0 + endif endif ifeq ($(BOLOS_SDK),) @@ -27,387 +29,248 @@ endif include $(BOLOS_SDK)/Makefile.defines -# TODO: compile with the right path restrictions -# APP_LOAD_PARAMS = --curve secp256k1 -APP_LOAD_PARAMS = $(COMMON_LOAD_PARAMS) -APP_PATH = "" +# TODO: Compile with the right path restrictions +# +# The right path restriction would be something like +# --path "*'/0'" +# for mainnet, and +# --path "*'/1'" +# for testnet. +# +# That is, restrict the BIP-44 coin_type, but not the purpose. +# However, such wildcards are not currently supported by the OS. +# +# Note that the app still requires explicit user approval before exporting +# any xpub outside of a small set of allowed standard paths. + +# Application allowed derivation curves. +CURVE_APP_LOAD_PARAMS = secp256k1 +# Application allowed derivation paths. +PATH_APP_LOAD_PARAMS = "" + +# Allowed SLIP21 paths +PATH_SLIP21_APP_LOAD_PARAMS = "LEDGER-Wallet policy" + +# Application version APPVERSION_M = 2 -APPVERSION_N = 0 -APPVERSION_P = 4 -APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" +APPVERSION_N = 2 +APPVERSION_P = 3 +APPVERSION_SUFFIX = # if not empty, appended at the end. Do not add a dash. +ifeq ($(APPVERSION_SUFFIX),) +APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" +else +APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)-$(strip $(APPVERSION_SUFFIX))" +endif -APP_STACK_SIZE = 1500 +# Setting to allow building variant applications +VARIANT_PARAM = COIN +VARIANT_VALUES = bitcoin_testnet bitcoin liquid_regtest liquid_regtest_headless liquid liquid_headless # simplify for tests ifndef COIN COIN=liquid_regtest endif -# Flags: BOLOS_SETTINGS, GLOBAL_PIN, DERIVE_MASTER -# Dependency to Bitcoin app (for altcoins) -APP_LOAD_FLAGS=--appFlags 0xa50 --dep Bitcoin:$(APPVERSION) +######################################## +# Application custom permissions # +######################################## +HAVE_APPLICATION_FLAG_DERIVE_MASTER = 1 +HAVE_APPLICATION_FLAG_GLOBAL_PIN = 1 +HAVE_APPLICATION_FLAG_BOLOS_SETTINGS = 1 +ifneq (,$(findstring bitcoin,$(COIN))) +HAVE_APPLICATION_FLAG_LIBRARY = 1 +endif -ifeq ($(COIN),bitcoin_testnet) -# we're not using the lib :) -DEFINES_LIB= -APP_LOAD_FLAGS=--appFlags 0xa50 +ifeq ($(COIN),bitcoin_testnet) -# Bitcoin testnet (can also be used for signet) +# Bitcoin testnet, no legacy support DEFINES += BIP32_PUBKEY_VERSION=0x043587CF -DEFINES += BIP32_PRIVKEY_VERSION=0x04358394 DEFINES += BIP44_COIN_TYPE=1 -DEFINES += BIP44_COIN_TYPE_2=1 DEFINES += COIN_P2PKH_VERSION=111 DEFINES += COIN_P2SH_VERSION=196 DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"tb\" -DEFINES += COIN_FAMILY=1 -DEFINES += COIN_COINID=\"Bitcoin\" -DEFINES += COIN_COINID_HEADER=\"BITCOIN\" -DEFINES += COIN_COINID_NAME=\"Bitcoin\" DEFINES += COIN_COINID_SHORT=\"TEST\" -DEFINES += COIN_KIND=COIN_KIND_BITCOIN_TESTNET -DEFINES += COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT APPNAME = "Bitcoin Test" else ifeq ($(COIN),bitcoin) -# we're not using the lib :) -DEFINES_LIB= -APP_LOAD_FLAGS=--appFlags 0xa50 - -# Bitcoin mainnet +# Bitcoin mainnet, no legacy support DEFINES += BIP32_PUBKEY_VERSION=0x0488B21E -DEFINES += BIP32_PRIVKEY_VERSION=0x0488ADE4 DEFINES += BIP44_COIN_TYPE=0 -DEFINES += BIP44_COIN_TYPE_2=0 DEFINES += COIN_P2PKH_VERSION=0 DEFINES += COIN_P2SH_VERSION=5 DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"bc\" -DEFINES += COIN_FAMILY=1 -DEFINES += COIN_COINID=\"Bitcoin\" -DEFINES += COIN_COINID_HEADER=\"BITCOIN\" -DEFINES += COIN_COINID_NAME=\"Bitcoin\\x20Testnet\" DEFINES += COIN_COINID_SHORT=\"BTC\" -DEFINES += COIN_KIND=COIN_KIND_BITCOIN -DEFINES += COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT APPNAME = "Bitcoin" -else ifeq ($(COIN),bitcoin_regtest) -# This target can be used to compile a version of the app that uses regtest addresses - -# we're not using the lib :) -DEFINES_LIB= -APP_LOAD_FLAGS=--appFlags 0xa50 - -# Bitcoin regtest test network -DEFINES += BIP32_PUBKEY_VERSION=0x043587CF -DEFINES += BIP32_PRIVKEY_VERSION=0x04358394 -DEFINES += BIP44_COIN_TYPE=1 -DEFINES += BIP44_COIN_TYPE_2=1 -DEFINES += COIN_P2PKH_VERSION=111 -DEFINES += COIN_P2SH_VERSION=196 -DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"bcrt\" -DEFINES += COIN_FAMILY=1 -DEFINES += COIN_COINID=\"Bitcoin\" -DEFINES += COIN_COINID_HEADER=\"BITCOIN\" -DEFINES += COIN_COINID_NAME=\"Bitcoin\" -DEFINES += COIN_COINID_SHORT=\"TEST\" -DEFINES += COIN_KIND=COIN_KIND_BITCOIN_TESTNET -DEFINES += COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT -APPNAME = "Bitcoin Regtest" - else ifeq ($(COIN),liquid_regtest) -# we're not using the lib :) -DEFINES_LIB= -# Flags: DERIVE_MASTER, GLOBAL_PIN, BOLOS_SETTINGS -APP_LOAD_FLAGS=--appFlags 0x250 - # Liquid regtest DEFINES += BIP32_PUBKEY_VERSION=0x043587CF DEFINES += BIP32_PRIVKEY_VERSION=0x04358394 DEFINES += BIP44_COIN_TYPE=1 -DEFINES += BIP44_COIN_TYPE_2=1 DEFINES += COIN_P2PKH_VERSION=111 DEFINES += COIN_P2SH_VERSION=75 DEFINES += COIN_PREFIX_CONFIDENTIAL=4 DEFINES += HAVE_LIQUID DEFINES += HAVE_LIQUID_TEST DEFINES += COIN_BLINDED_VERSION=4 -DEFINES += COIN_FAMILY=1 -DEFINES += COIN_COINID=\"Bitcoin\" -DEFINES += COIN_COINID_HEADER=\"BITCOIN\" -DEFINES += COIN_COINID_NAME=\"Bitcoin\" -DEFINES += COIN_COINID_SHORT=\"BTC\" +DEFINES += COIN_COINID_SHORT=\"L-BTC\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"ert\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX_CONFIDENTIAL=\"el\" -DEFINES += COIN_KIND=COIN_KIND_BITCOIN -DEFINES += COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT + APPNAME = "Liquid Regtest" -# -disabled- APP_LOAD_PARAMS += --curve secp256k1 else ifeq ($(COIN),liquid_regtest_headless) -# we're not using the lib :) -DEFINES_LIB= -# Flags: DERIVE_MASTER, GLOBAL_PIN, BOLOS_SETTINGS -APP_LOAD_FLAGS=--appFlags 0x250 - # Liquid regtest headless DEFINES += BIP32_PUBKEY_VERSION=0x043587CF DEFINES += BIP32_PRIVKEY_VERSION=0x04358394 DEFINES += BIP44_COIN_TYPE=1 -DEFINES += BIP44_COIN_TYPE_2=1 DEFINES += COIN_P2PKH_VERSION=111 DEFINES += COIN_P2SH_VERSION=75 DEFINES += COIN_PREFIX_CONFIDENTIAL=4 DEFINES += HAVE_LIQUID DEFINES += HAVE_LIQUID_TEST DEFINES += COIN_BLINDED_VERSION=4 -DEFINES += COIN_FAMILY=1 -DEFINES += COIN_COINID=\"Bitcoin\" -DEFINES += COIN_COINID_HEADER=\"BITCOIN\" -DEFINES += COIN_COINID_NAME=\"Bitcoin\" -DEFINES += COIN_COINID_SHORT=\"BTC\" +DEFINES += COIN_COINID_SHORT=\"L-BTC\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"ert\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX_CONFIDENTIAL=\"el\" -DEFINES += COIN_KIND=COIN_KIND_BITCOIN -DEFINES += COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT DEFINES += HAVE_LIQUID_HEADLESS + APPNAME = "Liquid Regtest Hless" -# -disabled- APP_LOAD_PARAMS += --curve secp256k1 else ifeq ($(COIN),liquid) -# we're not using the lib :) -DEFINES_LIB= -# Flags: DERIVE_MASTER, GLOBAL_PIN, BOLOS_SETTINGS -APP_LOAD_FLAGS=--appFlags 0x250 - # Liquid DEFINES += BIP32_PUBKEY_VERSION=0x0488B21E DEFINES += BIP32_PRIVKEY_VERSION=0x0488ADE4 DEFINES += BIP44_COIN_TYPE=1776 -DEFINES += BIP44_COIN_TYPE_2=1776 DEFINES += COIN_P2PKH_VERSION=57 DEFINES += COIN_P2SH_VERSION=39 DEFINES += COIN_PREFIX_CONFIDENTIAL=12 DEFINES += HAVE_LIQUID DEFINES += COIN_BLINDED_VERSION=12 -DEFINES += COIN_FAMILY=1 -DEFINES += COIN_COINID=\"Bitcoin\" -DEFINES += COIN_COINID_HEADER=\"BITCOIN\" -DEFINES += COIN_COINID_NAME=\"Bitcoin\" -DEFINES += COIN_COINID_SHORT=\"BTC\" +DEFINES += COIN_COINID_SHORT=\"L-BTC\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"ex\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX_CONFIDENTIAL=\"lq\" -DEFINES += COIN_KIND=COIN_KIND_BITCOIN -DEFINES += COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT + APPNAME = "Liquid" -# -disabled- APP_LOAD_PARAMS += --curve secp256k1 else ifeq ($(COIN),liquid_headless) -# we're not using the lib :) -DEFINES_LIB= -# Flags: DERIVE_MASTER, GLOBAL_PIN, BOLOS_SETTINGS -APP_LOAD_FLAGS=--appFlags 0x250 - # Liquid Headless DEFINES += BIP32_PUBKEY_VERSION=0x0488B21E DEFINES += BIP32_PRIVKEY_VERSION=0x0488ADE4 DEFINES += BIP44_COIN_TYPE=1776 -DEFINES += BIP44_COIN_TYPE_2=1776 DEFINES += COIN_P2PKH_VERSION=57 DEFINES += COIN_P2SH_VERSION=39 DEFINES += COIN_PREFIX_CONFIDENTIAL=12 DEFINES += HAVE_LIQUID DEFINES += COIN_BLINDED_VERSION=12 -DEFINES += COIN_FAMILY=1 -DEFINES += COIN_COINID=\"Bitcoin\" -DEFINES += COIN_COINID_HEADER=\"BITCOIN\" DEFINES += COIN_COLOR_HDR=0xFCB653 DEFINES += COIN_COLOR_DB=0xFEDBA9 -DEFINES += COIN_COINID_NAME=\"Bitcoin\" -DEFINES += COIN_COINID_SHORT=\"BTC\" +DEFINES += COIN_COINID_SHORT=\"L-BTC\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"ex\" DEFINES += COIN_NATIVE_SEGWIT_PREFIX_CONFIDENTIAL=\"lq\" -DEFINES += COIN_KIND=COIN_KIND_BITCOIN -DEFINES += COIN_FLAGS=FLAG_SEGWIT_CHANGE_SUPPORT DEFINES += HAVE_LIQUID_HEADLESS + APPNAME = "Liquid Hless" -# -disabled- APP_LOAD_PARAMS += --curve secp256k1 else ifeq ($(filter clean,$(MAKECMDGOALS)),) -$(error Unsupported COIN - use bitcoin_testnet, bitcoin, bitcoin_regtest, liquid_regtest, liquid_regtest_headless, liquid, liquid_headless) +$(error Unsupported COIN - use bitcoin_testnet, bitcoin, liquid_regtest, liquid_regtest_headless, liquid, liquid_headless) endif endif -APP_LOAD_PARAMS += $(APP_LOAD_FLAGS) -DEFINES += $(DEFINES_LIB) - -ifeq ($(TARGET_NAME),TARGET_NANOS) -ICONNAME=icons/nanos_app_$(COIN).gif +# Application icons following guidelines: +# https://developers.ledger.com/docs/embedded-app/design-requirements/#device-icon + +ifneq (,$(findstring bitcoin,$(COIN))) +# Bitcoin icons +ICON_NANOS = icons/nanos_app_bitcoin.gif +ICON_NANOX = icons/nanox_app_bitcoin.gif +ICON_NANOSP = icons/nanox_app_bitcoin.gif +ICON_STAX = icons/stax_app_bitcoin.gif +else ifneq (,$(findstring liquid,$(COIN))) +# Liquid icons +ICON_NANOS = icons/nanos_app_liquid.gif +ICON_NANOX = icons/nanox_app_liquid.gif +ICON_NANOSP = icons/nanox_app_liquid.gif +ICON_STAX = icons/stax_app_liquid.gif else -ICONNAME=icons/nanox_app_$(COIN).gif +$(error Unsupported COIN) endif -all: default - -# TODO: double check if all those flags are still relevant/needed (was copied from legacy app-bitcoin) +######################################## +# Application communication interfaces # +######################################## +ENABLE_BLUETOOTH = 1 -DEFINES += APPVERSION=\"$(APPVERSION)\" -DEFINES += MAJOR_VERSION=$(APPVERSION_M) MINOR_VERSION=$(APPVERSION_N) PATCH_VERSION=$(APPVERSION_P) -DEFINES += OS_IO_SEPROXYHAL -DEFINES += HAVE_BAGL HAVE_SPRINTF HAVE_SNPRINTF_FORMAT_U -DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=4 IO_HID_EP_LENGTH=64 HAVE_USB_APDU -DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) TCS_LOADER_PATCH_VERSION=0 -DEFINES += HAVE_UX_FLOW +######################################## +# NBGL custom features # +######################################## +ENABLE_NBGL_QRCODE = 1 -DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL="" +######################################## +# Features disablers # +######################################## +# Don't use standard app file to avoid conflicts for now +DISABLE_STANDARD_APP_FILES = 1 -DEFINES += UNUSED\(x\)=\(void\)x -DEFINES += APPVERSION=\"$(APPVERSION)\" +# Don't use default IO_SEPROXY_BUFFER_SIZE to use another +# value for NANOS for an unknown reason. +DISABLE_DEFAULT_IO_SEPROXY_BUFFER_SIZE = 1 DEFINES += HAVE_BOLOS_APP_STACK_CANARY - ifeq ($(TARGET_NAME),TARGET_NANOS) DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=72 DEFINES += HAVE_WALLET_ID_SDK else DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 -DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 -DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX -endif - -ifeq ($(TARGET_NAME),TARGET_NANOX) -DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000 -DEFINES += HAVE_BLE_APDU # basic ledger apdu transport over BLE endif ifeq ($(TARGET_NAME),TARGET_NANOS) # enables optimizations using the shared 1K CXRAM region DEFINES += USE_CXRAM_SECTION + # enables usage of the NVRAM to free up some RAM + DEFINES += USE_NVRAM_STASH endif # debugging helper functions and macros CFLAGS += -include debug-helpers/debug.h -# DEFINES += HAVE_PRINT_STACK_POINTER +# DEFINES += HAVE_PRINT_STACK_POINTER +# DEFINES += HAVE_LOG_PROCESSOR +# DEFINES += HAVE_APDU_LOG ifeq ($(TEST),1) $(warning On-device tests should only be run with Speculos!) - DEBUG_LOG_LEVEL = 10 - DEFINES += RUN_ON_DEVICE_TESTS -endif - -ifndef DEBUG_LOG_LEVEL - DEBUG_LOG_LEVEL = 0 -endif - -ifeq ($(DEBUG_LOG_LEVEL),0) - DEFINES += PRINTF\(...\)= -else - ifeq ($(DEBUG_LOG_LEVEL),10) - $(warning Using semihosted PRINTF. Only run with Speculos!) - DEFINES += HAVE_PRINTF HAVE_SEMIHOSTED_PRINTF PRINTF=semihosted_printf - #DEFINES += HAVE_LOG_PROCESSOR - #DEFINES += HAVE_APDU_LOG - #DEFINES += HAVE_PRINT_STACK_POINTER - else ifeq ($(DEBUG_LOG_LEVEL),11) - $(warning CCMD PRINTF is used! APDU exchage is affected.) - DEFINES += HAVE_CCMD_PRINTF - else - ifeq ($(TARGET_NAME),TARGET_NANOS) - DEFINES += HAVE_PRINTF PRINTF=screen_printf - else - DEFINES += HAVE_PRINTF PRINTF=mcu_usb_printf - endif - endif + DEFINES += RUN_ON_DEVICE_TESTS HAVE_PRINTF HAVE_SEMIHOSTED_PRINTF +else ifeq ($(DEBUG_LOG_LEVEL),10) + $(warning Using semihosted PRINTF. Only run with speculos!) + DEFINES += HAVE_PRINTF HAVE_SEMIHOSTED_PRINTF +else ifeq ($(DEBUG_LOG_LEVEL),11) + $(warning CCMD PRINTF is used! APDU exchage is affected.) + DEFINES += HAVE_CCMD_PRINTF endif - # Needed to be able to include the definition of G_cx INCLUDES_PATH += $(BOLOS_SDK)/lib_cxng/src - -ifneq ($(BOLOS_ENV),) -$(info BOLOS_ENV=$(BOLOS_ENV)) -CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/ -GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/ -else -$(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH) -endif -ifeq ($(CLANGPATH),) -$(info CLANGPATH is not set: clang will be used from PATH) -endif -ifeq ($(GCCPATH),) -$(info GCCPATH is not set: arm-none-eabi-* will be used from PATH) -endif - -CC := $(CLANGPATH)clang -AS := $(GCCPATH)arm-none-eabi-gcc -LD := $(GCCPATH)arm-none-eabi-gcc -LDLIBS += -lm -lgcc -lc - -ifeq ($(DEBUG_LOG_LEVEL),0) - $(info *** Release version is being built ***) - CFLAGS += -Oz - LDFLAGS += -O3 -Os -else - $(info *** Debug version is being built ***) - CFLAGS += -Og -g - LDFLAGS += -Og -endif - -include $(BOLOS_SDK)/Makefile.glyphs - +# Application source files APP_SOURCE_PATH += src -SDK_SOURCE_PATH += lib_stusb lib_stusb_impl lib_ux - -ifeq ($(TARGET_NAME),TARGET_NANOX) - SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl -endif - -load: all - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) - -load-offline: all - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) --offline -load-no-build: - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) - -load-offline-no-build: - python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) --offline - -delete: - python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) - -include $(BOLOS_SDK)/Makefile.rules - -dep/%.d: %.c Makefile - - -# Temporary restriction until we a Resistance Nano X icon -ifeq ($(TARGET_NAME),TARGET_NANOS) -listvariants: - @echo VARIANTS COIN bitcoin_testnet bitcoin bitcoin_cash bitcoin_gold litecoin dogecoin dash zcash horizen komodo stratis peercoin pivx viacoin vertcoin stealth digibyte qtum bitcoin_private firo gamecredits zclassic xsn nix lbry ravencoin resistance hydra hydra_testnet xrhodium -else -listvariants: - @echo VARIANTS COIN bitcoin_testnet bitcoin bitcoin_cash bitcoin_gold litecoin dogecoin dash zcash horizen komodo stratis peercoin pivx viacoin vertcoin stealth digibyte qtum bitcoin_private firo gamecredits zclassic xsn nix lbry ravencoin hydra hydra_testnet xrhodium -endif +# Allow usage of function from lib_standard_app/crypto_helpers.c +APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/crypto_helpers.c +include $(BOLOS_SDK)/Makefile.standard_app # Makes a detailed report of code and data size in debug/size-report.txt # More useful for production builds with DEBUG=0 diff --git a/README.md b/README.md index 07b3b3564..6ae34cd42 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ the process outputs HTML and LaTeX documentations in `doc/html` and `doc/latex` ## Client libraries -A [Python client library](bitcoin_client) and a [TypeScript client library](bitcoin_client_js) are available in this repository. +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. ## Tests & Continuous Integration diff --git a/bitcoin_client/.gitignore b/bitcoin_client/.gitignore index 0021b4fb3..108413e8c 100644 --- a/bitcoin_client/.gitignore +++ b/bitcoin_client/.gitignore @@ -1 +1,2 @@ -dist/** \ No newline at end of file +dist/** +**/.venv diff --git a/bitcoin_client/CHANGELOG.md b/bitcoin_client/CHANGELOG.md index 70da3dc6b..baf9065cd 100644 --- a/bitcoin_client/CHANGELOG.md +++ b/bitcoin_client/CHANGELOG.md @@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Dates are in `dd-mm-yyyy` format. +## [0.2.1] - 18-04-2023 + +### Changed +- Avoid using miniscript policies containing an `a:` fragment on versions below `2.1.2` of the bitcoin app. + +## [0.2.0] - 3-04-2023 + +This release introduces a breaking change in the return type of the `sign_psbt`method. + +### Added +- Added new `PartialSignature` data class together with support for taproot script signing, which is supported in version `2.1.2` of the bitcoin app. + +## [0.1.2] - 09-01-2023 + +### Fixed +- Added missing dependency. + +## [0.1.1] - 26-10-2022 + +### Changed + +- Improved interface of TransportClient for better interoperability with HID. +- `sign_psbt` now accepts the psbt to be passed as `bytes` or `str`. + +## [0.1.0] - 18-10-2022 + +### Changed + +Upgraded library to version 2.1.0 of the app. + ## [0.0.3] - 25-04-2022 ### Changed diff --git a/bitcoin_client/README.md b/bitcoin_client/README.md index 817b3c730..3183a9a8a 100644 --- a/bitcoin_client/README.md +++ b/bitcoin_client/README.md @@ -35,7 +35,7 @@ It is possible to run the app and the library with the [speculos](https://github ⚠️ Currently, speculos does not correctly emulate the version of the app, always returning a dummy value; in order to use the library, it is necessary to set the `SPECULOS_APPNAME` environment variable before starting speculos, for example with: ``` -$ export SPECULOS_APPNAME="Bitcoin Test:2.0.0" +$ export SPECULOS_APPNAME="Bitcoin Test:2.1.0" ``` Similarly, to test the library behavior on a legacy version of the app, one can set the version to `1.6.5` (the final version of the 1.X series). @@ -53,7 +53,7 @@ Testing the `sign_psbt` method requires producing a valid PSBT (with any externa ```python from typing import Optional -from ledger_bitcoin import createClient, Chain, MultisigWallet, MultisigWallet, PolicyMapWallet, AddressType, TransportClient +from ledger_bitcoin import createClient, Chain, MultisigWallet, MultisigWallet, WalletPolicy, AddressType, TransportClient from ledger_bitcoin.psbt import PSBT @@ -71,11 +71,11 @@ def main(): # ==> Get and display on screen the first taproot address first_taproot_account_pubkey = client.get_extended_pubkey("m/86'/1'/0'") - first_taproot_account_policy = PolicyMapWallet( + first_taproot_account_policy = WalletPolicy( "", - "tr(@0)", + "tr(@0/**)", [ - f"[{fpr}/86'/1'/0']{first_taproot_account_pubkey}/**" + f"[{fpr}/86'/1'/0']{first_taproot_account_pubkey}" ], ) first_taproot_account_address = client.get_wallet_address( @@ -91,15 +91,15 @@ def main(): # ==> Register a multisig wallet named "Cold storage" our_pubkey = client.get_extended_pubkey("m/48'/1'/0'/2'") - other_key_info = "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**" + other_key_info = "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF" multisig_policy = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, threshold=2, keys_info=[ - other_key_info, # some other bitcoiner - f"[{fpr}/48'/1'/0'/2']{our_pubkey}/**", # that's us + other_key_info, # some other bitcoiner + f"[{fpr}/48'/1'/0'/2']{our_pubkey}", # that's us ], ) @@ -118,7 +118,7 @@ def main(): # TODO: set a wallet policy and a valid psbt file in order to test psbt signing psbt_filename: Optional[str] = None - signing_policy: Optional[PolicyMapWallet] = None + signing_policy: Optional[WalletPolicy] = None signing_policy_hmac: Optional[bytes] = None if not psbt_filename or not signing_policy: print("Nothing to sign :(") diff --git a/bitcoin_client/ledger_bitcoin/__init__.py b/bitcoin_client/ledger_bitcoin/__init__.py index 52038368b..7c52114cf 100644 --- a/bitcoin_client/ledger_bitcoin/__init__.py +++ b/bitcoin_client/ledger_bitcoin/__init__.py @@ -1,10 +1,22 @@ """Ledger Nano Bitcoin app client""" -from .client_base import Client, TransportClient +from .client_base import Client, TransportClient, PartialSignature from .client import createClient from .common import Chain -from .wallet import AddressType, Wallet, MultisigWallet, PolicyMapWallet, BlindedWallet, BlindedMultisigWallet +from .wallet import AddressType, WalletPolicy, MultisigWallet, WalletType, WalletPolicy, BlindedWallet, BlindedMultisigWallet -__all__ = ["Client", "TransportClient", "createClient", "Chain", "AddressType", "Wallet", "MultisigWallet", "PolicyMapWallet"] +__version__ = '0.3.0' + +__all__ = [ + "Client", + "TransportClient", + "PartialSignature", + "createClient", + "Chain", + "AddressType", + "WalletPolicy", + "MultisigWallet", + "WalletType" +] diff --git a/bitcoin_client/ledger_bitcoin/btchip/btchipHelpers.py b/bitcoin_client/ledger_bitcoin/btchip/btchipHelpers.py index ba5b66c5f..bb74cd56b 100644 --- a/bitcoin_client/ledger_bitcoin/btchip/btchipHelpers.py +++ b/bitcoin_client/ledger_bitcoin/btchip/btchipHelpers.py @@ -21,8 +21,8 @@ import re # from pycoin -SATOSHI_PER_COIN = decimal.Decimal(1e8) -COIN_PER_SATOSHI = decimal.Decimal(1)/SATOSHI_PER_COIN +SATOSHI_PER_COIN = decimal.Decimal(100_000_000) +COIN_PER_SATOSHI = decimal.Decimal('0.00000001') def satoshi_to_btc(satoshi_count): if satoshi_count == 0: diff --git a/bitcoin_client/ledger_bitcoin/client.py b/bitcoin_client/ledger_bitcoin/client.py index 8d810a454..8e3ced614 100644 --- a/bitcoin_client/ledger_bitcoin/client.py +++ b/bitcoin_client/ledger_bitcoin/client.py @@ -1,16 +1,23 @@ +from packaging.version import parse as parse_version from typing import Tuple, List, Mapping, Optional, Union import base64 from io import BytesIO, BufferedReader +from .embit.base import EmbitError +from .embit.descriptor import Descriptor +from .embit.networks import NETWORKS + from .command_builder import BitcoinCommandBuilder, BitcoinInsType -from .common import Chain, read_varint +from .common import Chain, read_uint, read_varint from .client_command import ClientCommandInterpreter, ClientCommandCode -from .client_base import Client, TransportClient +from .client_base import Client, TransportClient, PartialSignature from .client_legacy import LegacyClient from .exception import DeviceException +from .errors import UnknownDeviceError from .merkle import get_merkleized_map_commitment -from .wallet import Wallet, WalletType, PolicyMapWallet -from .psbt import PSBT +from .wallet import WalletPolicy, WalletType +from .psbt import PSBT, normalize_psbt +from . import segwit_addr from ._serialize import deser_string @@ -32,6 +39,23 @@ def parse_stream_to_map(f: BufferedReader) -> Mapping[bytes, bytes]: return result +def _make_partial_signature(pubkey_augm: bytes, signature: bytes) -> PartialSignature: + if len(pubkey_augm) == 64: + # tapscript spend: pubkey_augm is the concatenation of: + # - a 32-byte x-only pubkey + # - the 32-byte tapleaf_hash + return PartialSignature(signature=signature, pubkey=pubkey_augm[0:32], tapleaf_hash=pubkey_augm[32:]) + + else: + # either legacy, segwit or taproot keypath spend + # pubkey must be 32 (taproot x-only pubkey) or 33 bytes (compressed pubkey) + + if len(pubkey_augm) not in [32, 33]: + raise UnknownDeviceError(f"Invalid pubkey length returned: {len(pubkey_augm)}") + + return PartialSignature(signature=signature, pubkey=pubkey_augm) + + class NewClient(Client): # internal use for testing: if set to True, sign_psbt will not clone the psbt before converting to psbt version 2 _no_clone_psbt: bool = False @@ -69,14 +93,17 @@ def get_extended_pubkey(self, path: str, display: bool = False) -> str: return response.decode() - def register_wallet(self, wallet: Wallet) -> Tuple[bytes, bytes]: - if wallet.type != WalletType.POLICYMAP: - raise ValueError("wallet type must be POLICYMAP") + def register_wallet(self, wallet: WalletPolicy) -> Tuple[bytes, bytes]: + if wallet.version not in [WalletType.WALLET_POLICY_V1, WalletType.WALLET_POLICY_V2]: + raise ValueError("invalid wallet policy version") client_intepreter = ClientCommandInterpreter() client_intepreter.add_known_preimage(wallet.serialize()) client_intepreter.add_known_list([k.encode() for k in wallet.keys_info]) + # necessary for version 1 of the protocol (introduced in version 2.1.0) + client_intepreter.add_known_preimage(wallet.descriptor_template.encode()) + sw, response = self._make_request( self.builder.register_wallet(wallet), client_intepreter ) @@ -90,21 +117,25 @@ def register_wallet(self, wallet: Wallet) -> Tuple[bytes, bytes]: wallet_id = response[0:32] wallet_hmac = response[32:64] + # sanity check: for miniscripts, derive the first address independently with python-bip380 + first_addr_device = self.get_wallet_address(wallet, wallet_hmac, 0, 0, False) + + if first_addr_device != self._derive_address_for_policy(wallet, False, 0): + raise RuntimeError("Invalid address. Please update your Bitcoin app. If the problem persists, report a bug at https://github.com/LedgerHQ/app-bitcoin-new") + return wallet_id, wallet_hmac def get_wallet_address( self, - wallet: Wallet, + wallet: WalletPolicy, wallet_hmac: Optional[bytes], change: int, address_index: int, display: bool, ) -> str: - if wallet.type != WalletType.POLICYMAP or not isinstance( - wallet, PolicyMapWallet - ): - raise ValueError("wallet type must be POLICYMAP") + if not isinstance(wallet, WalletPolicy) or wallet.version not in [WalletType.WALLET_POLICY_V1, WalletType.WALLET_POLICY_V2]: + raise ValueError("wallet type must be WalletPolicy, with version either WALLET_POLICY_V1 or WALLET_POLICY_V2") if change != 0 and change != 1: raise ValueError("Invalid change") @@ -113,6 +144,9 @@ def get_wallet_address( client_intepreter.add_known_list([k.encode() for k in wallet.keys_info]) client_intepreter.add_known_preimage(wallet.serialize()) + # necessary for version 1 of the protocol (introduced in version 2.1.0) + client_intepreter.add_known_preimage(wallet.descriptor_template.encode()) + sw, response = self._make_request( self.builder.get_wallet_address( wallet, wallet_hmac, address_index, change, display @@ -123,32 +157,19 @@ def get_wallet_address( if sw != 0x9000: raise DeviceException(error_code=sw, ins=BitcoinInsType.GET_WALLET_ADDRESS) - return response.decode() + result = response.decode() - def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> Mapping[int, bytes]: - """Signs a PSBT using a registered wallet (or a standard wallet that does not need registration). + # sanity check: for miniscripts, derive the address independently with python-bip380 - Signature requires explicit approval from the user. + if result != self._derive_address_for_policy(wallet, change, address_index): + raise RuntimeError("Invalid address. Please update your Bitcoin app. If the problem persists, report a bug at https://github.com/LedgerHQ/app-bitcoin-new") - Parameters - ---------- - psbt : PSBT - A PSBT of version 0 or 2, with all the necessary information to sign the inputs already filled in; what the - required fields changes depending on the type of input. - The non-witness UTXO must be present for both legacy and SegWit inputs, or the hardware wallet will reject - signing. This is not required for Taproot inputs. + return result - wallet : Wallet - The registered wallet policy, or a standard wallet policy. + def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: Optional[bytes]) -> List[Tuple[int, PartialSignature]]: - wallet_hmac: Optional[bytes] - For a registered wallet, the hmac obtained at wallet registration. `None` for a standard wallet policy. + psbt = normalize_psbt(psbt) - Returns - ------- - Mapping[int, bytes] - A mapping that has as keys the indexes of inputs that the Hardware Wallet signed, and the corresponding signatures as values. - """ if psbt.version != 2: if self._no_clone_psbt: psbt.convert_to_v2() @@ -173,6 +194,9 @@ def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> client_intepreter.add_known_list([k.encode() for k in wallet.keys_info]) client_intepreter.add_known_preimage(wallet.serialize()) + # necessary for version 1 of the protocol (introduced in version 2.1.0) + client_intepreter.add_known_preimage(wallet.descriptor_template.encode()) + global_map: Mapping[bytes, bytes] = parse_stream_to_map(f) client_intepreter.add_known_mapping(global_map) @@ -211,18 +235,19 @@ def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> if any(len(x) <= 1 for x in results): raise RuntimeError("Invalid response") - results_map = {} + results_list: List[Tuple[int, PartialSignature]] = [] for res in results: res_buffer = BytesIO(res) input_index = read_varint(res_buffer) - signature = res_buffer.read() - if input_index in results_map: - raise RuntimeError(f"Multiple signatures produced for the same input: {input_index}") + pubkey_augm_len = read_uint(res_buffer, 8) + pubkey_augm = res_buffer.read(pubkey_augm_len) + + signature = res_buffer.read() - results_map[input_index] = signature + results_list.append((input_index, _make_partial_signature(pubkey_augm, signature))) - return results_map + return results_list def get_master_fingerprint(self) -> bytes: sw, response = self._make_request(self.builder.get_master_fingerprint()) @@ -265,6 +290,16 @@ def liquid_get_blinding_key(self, script: bytes) -> bytes: raise DeviceException(error_code=sw, ins=BitcoinInsType.LIQUID_GET_BLINDING_KEY) return response + def _derive_address_for_policy(self, wallet: WalletPolicy, change: bool, address_index: int) -> Optional[str]: + desc_str = wallet.get_descriptor(change) + try: + desc = Descriptor.from_string(desc_str) + + desc = desc.derive(address_index) + net = NETWORKS['main'] if self.chain == Chain.MAIN else NETWORKS['test'] + return desc.script_pubkey().address(net) + except EmbitError: + return None def createClient(comm_client: Optional[TransportClient] = None, chain: Chain = Chain.MAIN, debug: bool = False) -> Union[LegacyClient, NewClient]: @@ -272,8 +307,16 @@ def createClient(comm_client: Optional[TransportClient] = None, chain: Chain = C comm_client = TransportClient("hid") base_client = Client(comm_client, chain, debug) - _, app_version, _ = base_client.get_version() - if app_version >= "2": - return NewClient(comm_client, chain, debug) - else: + app_name, app_version, _ = base_client.get_version() + + version = parse_version(app_version) + + # Use the legacy client if either: + # - the name of the app is "Bitcoin Legacy" or "Bitcoin Test Legacy" (regardless of the version) + # - the version is strictly less than 2.1 + use_legacy = app_name in ["Bitcoin Legacy", "Bitcoin Test Legacy"] or version.major < 2 or (version.major == 2 and version.minor == 0) + + if use_legacy: return LegacyClient(comm_client, chain, debug) + else: + return NewClient(comm_client, chain, debug) diff --git a/bitcoin_client/ledger_bitcoin/client_base.py b/bitcoin_client/ledger_bitcoin/client_base.py index cdcb7c333..1b69f93d1 100644 --- a/bitcoin_client/ledger_bitcoin/client_base.py +++ b/bitcoin_client/ledger_bitcoin/client_base.py @@ -1,14 +1,17 @@ -from typing import Tuple, Mapping, Optional, Union, Literal +from dataclasses import dataclass +from typing import List, Tuple, Optional, Union, Literal from io import BytesIO -from ledgercomm import Transport +from ledgercomm.interfaces.hid_device import HID + +from .transport import Transport from .common import Chain from .command_builder import DefaultInsType from .exception import DeviceException -from .wallet import Wallet +from .wallet import WalletPolicy from .psbt import PSBT from ._serialize import deser_string @@ -24,8 +27,8 @@ def __init__(self, sw: int, data: bytes) -> None: class TransportClient: - def __init__(self, interface: Literal['hid', 'tcp'] = "tcp", server: str = "127.0.0.1", port: int = 9999, debug: bool = False): - self.transport = Transport('hid', debug=debug) if interface == 'hid' else Transport(interface, server, port, debug) + def __init__(self, interface: Literal['hid', 'tcp'] = "tcp", *, server: str = "127.0.0.1", port: int = 9999, path: Optional[str] = None, hid: Optional[HID] = None, debug: bool = False): + self.transport = Transport('hid', path=path, hid=hid, debug=debug) if interface == 'hid' else Transport(interface, server=server, port=port, debug=debug) def apdu_exchange( self, cla: int, ins: int, data: bytes = b"", p1: int = 0, p2: int = 0 @@ -45,6 +48,7 @@ def apdu_exchange_nowait( def stop(self) -> None: self.transport.close() + def print_apdu(apdu_dict: dict) -> None: serialized_apdu = b''.join([ apdu_dict["cla"].to_bytes(1, byteorder='big'), @@ -56,10 +60,25 @@ def print_apdu(apdu_dict: dict) -> None: ]) print(f"=> {serialized_apdu.hex()}") + def print_response(sw: int, data: bytes) -> None: print(f"<= {data.hex()}{sw.to_bytes(2, byteorder='big').hex()}") +@dataclass(frozen=True) +class PartialSignature: + """Represents a partial signature returned by sign_psbt. + + It always contains a pubkey and a signature. + The pubkey + + The tapleaf_hash is also filled if signing a for a tapscript. + """ + pubkey: bytes + signature: bytes + tapleaf_hash: Optional[bytes] = None + + class Client: def __init__(self, transport_client: TransportClient, chain: Chain = Chain.MAIN, debug: bool = False) -> None: self.transport_client = transport_client @@ -146,12 +165,12 @@ def get_extended_pubkey(self, path: str, display: bool = False) -> str: raise NotImplementedError - def register_wallet(self, wallet: Wallet) -> Tuple[bytes, bytes]: + def register_wallet(self, wallet: WalletPolicy) -> Tuple[bytes, bytes]: """Registers a wallet policy with the user. After approval returns the wallet id and hmac to be stored on the client. Parameters ---------- - wallet : Wallet + wallet : WalletPolicy The Wallet policy to register on the device. Returns @@ -165,7 +184,7 @@ def register_wallet(self, wallet: Wallet) -> Tuple[bytes, bytes]: def get_wallet_address( self, - wallet: Wallet, + wallet: WalletPolicy, wallet_hmac: Optional[bytes], change: int, address_index: int, @@ -176,7 +195,7 @@ def get_wallet_address( Parameters ---------- - wallet : Wallet + wallet : WalletPolicy The registered wallet policy, or a standard wallet policy. wallet_hmac: Optional[bytes] @@ -199,20 +218,21 @@ def get_wallet_address( raise NotImplementedError - def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> Mapping[int, bytes]: + def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: Optional[bytes]) -> List[Tuple[int, PartialSignature]]: """Signs a PSBT using a registered wallet (or a standard wallet that does not need registration). Signature requires explicit approval from the user. Parameters ---------- - psbt : PSBT + psbt : PSBT | bytes | str A PSBT of version 0 or 2, with all the necessary information to sign the inputs already filled in; what the required fields changes depending on the type of input. The non-witness UTXO must be present for both legacy and SegWit inputs, or the hardware wallet will reject signing (this will change for Taproot inputs). + The argument can be either a `PSBT` object, or `bytes`, or a base64-encoded `str`. - wallet : Wallet + wallet : WalletPolicy The registered wallet policy, or a standard wallet policy. wallet_hmac: Optional[bytes] @@ -220,8 +240,10 @@ def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> Returns ------- - Mapping[int, bytes] - A mapping that has as keys the indexes of inputs that the Hardware Wallet signed, and the corresponding signatures as values. + List[Tuple[int, PartialSignature]] + A list of tuples returned by the hardware wallets, where each element is a tuple of: + - an integer, the index of the input being signed; + - an instance of `PartialSignature`. """ raise NotImplementedError diff --git a/bitcoin_client/ledger_bitcoin/client_command.py b/bitcoin_client/ledger_bitcoin/client_command.py index 5a50a2fb5..c7a158a1a 100644 --- a/bitcoin_client/ledger_bitcoin/client_command.py +++ b/bitcoin_client/ledger_bitcoin/client_command.py @@ -247,8 +247,8 @@ def __init__(self): self.commands = {cmd.code: cmd for cmd in commands} def execute(self, hw_response: bytes) -> bytes: - """Interprets the client command requested by the hardware wallet, returning the appropriet - response and updating the client interpreter's internal state if appropriate. + """Interprets the client command requested by the hardware wallet, returning the appropriate + response and updating the client interpreter's internal state if needed. Parameters ---------- diff --git a/bitcoin_client/ledger_bitcoin/client_legacy.py b/bitcoin_client/ledger_bitcoin/client_legacy.py index 32101e115..1f21a922a 100644 --- a/bitcoin_client/ledger_bitcoin/client_legacy.py +++ b/bitcoin_client/ledger_bitcoin/client_legacy.py @@ -10,14 +10,15 @@ import re import base64 +from .client_base import PartialSignature from .client import Client, TransportClient -from typing import List, Tuple, Mapping, Optional, Union +from typing import List, Tuple, Optional, Union from .common import AddressType, Chain, hash160 from .key import ExtendedKey, parse_path -from .psbt import PSBT -from .wallet import Wallet, PolicyMapWallet +from .psbt import PSBT, normalize_psbt +from .wallet import WalletPolicy from ._script import is_p2sh, is_witness, is_p2wpkh, is_p2wsh @@ -26,12 +27,12 @@ from .btchip.bitcoinTransaction import bitcoinTransaction -def get_address_type_for_policy(policy: PolicyMapWallet) -> AddressType: - if policy.policy_map == "pkh(@0)": +def get_address_type_for_policy(policy: WalletPolicy) -> AddressType: + if policy.descriptor_template in ["pkh(@0/**)", "pkh(@0/<0;1>/*)"]: return AddressType.LEGACY - elif policy.policy_map == "wpkh(@0)": + elif policy.descriptor_template in ["wpkh(@0/**)", "wpkh(@0/<0:1>/*)"]: return AddressType.WIT - elif policy.policy_map == "sh(wpkh(@0))": + elif policy.descriptor_template in ["sh(wpkh(@0/**))", "sh(wpkh(@0/<0;1>/*))"]: return AddressType.SH_WIT else: raise ValueError("Invalid or unsupported policy") @@ -76,7 +77,7 @@ def __init__(self, comm_client: TransportClient, chain: Chain = Chain.MAIN, debu self.app = btchip(DongleAdaptor(comm_client)) - if self.app.getAppName() not in ["Bitcoin", "Bitcoin Test", "app"]: + if self.app.getAppName() not in ["Bitcoin", "Bitcoin Legacy", "Bitcoin Test", "Bitcoin Test Legacy", "app"]: raise ValueError("Ledger is not in either the Bitcoin or Bitcoin Testnet app") def get_extended_pubkey(self, path: str, display: bool = False) -> str: @@ -116,12 +117,12 @@ def get_extended_pubkey(self, path: str, display: bool = False) -> str: ) return xpub.to_string() - def register_wallet(self, wallet: Wallet) -> Tuple[bytes, bytes]: - raise NotImplementedError # legacy app does not have this functionality + def register_wallet(self, wallet: WalletPolicy) -> Tuple[bytes, bytes]: + raise NotImplementedError # legacy app does not have this functionality def get_wallet_address( self, - wallet: Wallet, + wallet: WalletPolicy, wallet_hmac: Optional[bytes], change: int, address_index: int, @@ -129,11 +130,11 @@ def get_wallet_address( ) -> str: # TODO: check keypath - if wallet_hmac != None or wallet.n_keys != 1: + if wallet_hmac is not None or wallet.n_keys != 1: raise NotImplementedError("Policy wallets are only supported from version 2.0.0. Please update your Ledger hardware wallet") - if not isinstance(wallet, PolicyMapWallet): - raise ValueError("Invalid wallet policy type, it must be PolicyMapWallet") + if not isinstance(wallet, WalletPolicy): + raise ValueError("Invalid wallet policy type, it must be WalletPolicy") key_info = wallet.keys_info[0] try: @@ -153,18 +154,20 @@ def get_wallet_address( bech32 = addr_type == AddressType.WIT output = self.app.getWalletPublicKey(f"{key_origin_path}/{change}/{address_index}", display, p2sh_p2wpkh or bech32, bech32) assert isinstance(output["address"], str) - return output['address'][12:-2] # HACK: A bug in getWalletPublicKey results in the address being returned as the string "bytearray(b'
')". This extracts the actual address to work around this. + return output['address'][12:-2] # HACK: A bug in getWalletPublicKey results in the address being returned as the string "bytearray(b'
')". This extracts the actual address to work around this. - def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> Mapping[int, bytes]: - if wallet_hmac != None or wallet.n_keys != 1: + def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: Optional[bytes]) -> List[Tuple[int, PartialSignature]]: + if wallet_hmac is not None or wallet.n_keys != 1: raise NotImplementedError("Policy wallets are only supported from version 2.0.0. Please update your Ledger hardware wallet") - if not isinstance(wallet, PolicyMapWallet): - raise ValueError("Invalid wallet policy type, it must be PolicyMapWallet") + if not isinstance(wallet, WalletPolicy): + raise ValueError("Invalid wallet policy type, it must be WalletPolicy") - if not wallet.policy_map in ['pkh(@0)', 'wpkh(@0)', 'sh(wpkh(@0))']: + if wallet.descriptor_template not in ["pkh(@0/**)", "pkh(@0/<0;1>/*)", "wpkh(@0/**)", "wpkh(@0/<0;1>/*)", "sh(wpkh(@0/**))", "sh(wpkh(@0/<0;1>/*))"]: raise NotImplementedError("Unsupported policy") + psbt = normalize_psbt(psbt) + # the rest of the code is basically the HWI code, and it ignores wallet tx = psbt @@ -278,7 +281,7 @@ def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> all_signature_attempts[i_num] = signature_attempts - result = {} + result: List[Tuple[int, PartialSignature]] = [] # Sign any segwit inputs if has_segwit: @@ -296,22 +299,32 @@ def sign_psbt(self, psbt: PSBT, wallet: Wallet, wallet_hmac: Optional[bytes]) -> self.app.startUntrustedTransaction(False, 0, [segwit_inputs[i]], script_codes[i], c_tx.nVersion) # tx.inputs[i].partial_sigs[signature_attempt[1]] = self.app.untrustedHashSign(signature_attempt[0], "", c_tx.nLockTime, 0x01) - result[i] = self.app.untrustedHashSign(signature_attempt[0], "", c_tx.nLockTime, 0x01) + + partial_sig = PartialSignature( + signature=self.app.untrustedHashSign(signature_attempt[0], "", c_tx.nLockTime, 0x01), + pubkey=signature_attempt[1] + ) + result.append((i, partial_sig)) elif has_legacy: first_input = True # Legacy signing if all inputs are legacy for i in range(len(legacy_inputs)): for signature_attempt in all_signature_attempts[i]: - assert(tx.inputs[i].non_witness_utxo is not None) + assert (tx.inputs[i].non_witness_utxo is not None) self.app.startUntrustedTransaction(first_input, i, legacy_inputs, script_codes[i], c_tx.nVersion) self.app.finalizeInput(b"DUMMY", -1, -1, change_path, tx_bytes) #tx.inputs[i].partial_sigs[signature_attempt[1]] = self.app.untrustedHashSign(signature_attempt[0], "", c_tx.nLockTime, 0x01) - result[i] = self.app.untrustedHashSign(signature_attempt[0], "", c_tx.nLockTime, 0x01) + + partial_sig = PartialSignature( + signature=self.app.untrustedHashSign(signature_attempt[0], "", c_tx.nLockTime, 0x01), + pubkey=signature_attempt[1] + ) + result.append((i, partial_sig)) first_input = False - # Send map of input signatures + # Send list of input signatures return result def get_master_fingerprint(self) -> bytes: diff --git a/bitcoin_client/ledger_bitcoin/command_builder.py b/bitcoin_client/ledger_bitcoin/command_builder.py index 2d062bab0..e79e111c0 100644 --- a/bitcoin_client/ledger_bitcoin/command_builder.py +++ b/bitcoin_client/ledger_bitcoin/command_builder.py @@ -1,10 +1,12 @@ import enum from typing import List, Tuple, Mapping, Union, Iterator, Optional -from .common import bip32_path_from_string, AddressType, sha256, hash256, write_varint +from .common import bip32_path_from_string, write_varint from .merkle import get_merkleized_map_commitment, MerkleTree, element_hash -from .wallet import Wallet +from .wallet import WalletPolicy +# p2 encodes the protocol version implemented +CURRENT_PROTOCOL_VERSION = 1 def chunkify(data: bytes, chunk_len: int) -> Iterator[Tuple[bool, bytes]]: size: int = len(data) @@ -54,7 +56,7 @@ def serialize( cla: int, ins: Union[int, enum.IntEnum], p1: int = 0, - p2: int = 0, + p2: int = CURRENT_PROTOCOL_VERSION, cdata: bytes = b"", ) -> dict: """Serialize the whole APDU command (header + data). @@ -96,7 +98,7 @@ def get_extended_pubkey(self, bip32_path: str, display: bool = False): cdata=cdata, ) - def register_wallet(self, wallet: Wallet): + def register_wallet(self, wallet: WalletPolicy): wallet_bytes = wallet.serialize() return self.serialize( @@ -107,7 +109,7 @@ def register_wallet(self, wallet: Wallet): def get_wallet_address( self, - wallet: Wallet, + wallet: WalletPolicy, wallet_hmac: Optional[bytes], address_index: int, change: bool, @@ -134,7 +136,7 @@ def sign_psbt( global_mapping: Mapping[bytes, bytes], input_mappings: List[Mapping[bytes, bytes]], output_mappings: List[Mapping[bytes, bytes]], - wallet: Wallet, + wallet: WalletPolicy, wallet_hmac: Optional[bytes], ): diff --git a/bitcoin_client/ledger_bitcoin/common.py b/bitcoin_client/ledger_bitcoin/common.py index c46cda68b..66ad38f07 100644 --- a/bitcoin_client/ledger_bitcoin/common.py +++ b/bitcoin_client/ledger_bitcoin/common.py @@ -103,15 +103,6 @@ def read_varint(buf: BytesIO, return int.from_bytes(b, byteorder="little") -def read(buf: BytesIO, size: int) -> bytes: - b: bytes = buf.read(size) - - if len(b) < size: - raise ValueError(f"Cant read {size} bytes in buffer!") - - return b - - def read_uint(buf: BytesIO, bit_len: int, byteorder: Literal['big', 'little'] = 'little') -> int: @@ -125,14 +116,19 @@ def read_uint(buf: BytesIO, def serialize_str(value: str) -> bytes: - return len(value).to_bytes(1, byteorder="big") + value.encode("latin-1") + return len(value.encode()).to_bytes(1, byteorder="big") + value.encode() def ripemd160(x: bytes) -> bytes: - h = hashlib.new("ripemd160") - h.update(x) - return h.digest() - + try: + h = hashlib.new("ripemd160") + h.update(x) + return h.digest() + except BaseException: + # ripemd160 is not always present in hashlib. + # Fallback to custom implementation if missing. + from . import ripemd + return ripemd.ripemd160(x) def sha256(s: bytes) -> bytes: return hashlib.new('sha256', s).digest() diff --git a/bitcoin_client/ledger_bitcoin/descriptor.py b/bitcoin_client/ledger_bitcoin/descriptor.py deleted file mode 100644 index ef85e8f55..000000000 --- a/bitcoin_client/ledger_bitcoin/descriptor.py +++ /dev/null @@ -1,633 +0,0 @@ - -""" -Original version: https://github.com/bitcoin-core/HWI/blob/3fe369d0379212fae1c72729a179d133b0adc872/hwilib/descriptor.py -Distributed under the MIT License. - -Output Script Descriptors -************************* - - -HWI has a more limited implementation of descriptors. -See `Bitcoin Core's documentation `_ for more details on descriptors. - -This implementation only supports ``sh()``, ``wsh()``, ``pkh()``, ``wpkh()``, ``multi()``, and ``sortedmulti()`` descriptors. -Descriptors can be parsed, however the actual scripts are not generated. -""" - - -from .key import ExtendedKey, KeyOriginInfo, parse_path -from .common import hash160, sha256 - -from binascii import unhexlify -from collections import namedtuple -from enum import Enum -from typing import ( - List, - Optional, - Tuple, -) - - -MAX_TAPROOT_NODES = 128 - - -ExpandedScripts = namedtuple("ExpandedScripts", ["output_script", "redeem_script", "witness_script"]) - -def PolyMod(c: int, val: int) -> int: - """ - :meta private: - Function to compute modulo over the polynomial used for descriptor checksums - From: https://github.com/bitcoin/bitcoin/blob/master/src/script/descriptor.cpp - """ - c0 = c >> 35 - c = ((c & 0x7ffffffff) << 5) ^ val - if (c0 & 1): - c ^= 0xf5dee51989 - if (c0 & 2): - c ^= 0xa9fdca3312 - if (c0 & 4): - c ^= 0x1bab10e32d - if (c0 & 8): - c ^= 0x3706b1677a - if (c0 & 16): - c ^= 0x644d626ffd - return c - -def DescriptorChecksum(desc: str) -> str: - """ - Compute the checksum for a descriptor - :param desc: The descriptor string to compute a checksum for - :return: A checksum - """ - INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " - CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" - - c = 1 - cls = 0 - clscount = 0 - for ch in desc: - pos = INPUT_CHARSET.find(ch) - if pos == -1: - return "" - c = PolyMod(c, pos & 31) - cls = cls * 3 + (pos >> 5) - clscount += 1 - if clscount == 3: - c = PolyMod(c, cls) - cls = 0 - clscount = 0 - if clscount > 0: - c = PolyMod(c, cls) - for j in range(0, 8): - c = PolyMod(c, 0) - c ^= 1 - - ret = [''] * 8 - for j in range(0, 8): - ret[j] = CHECKSUM_CHARSET[(c >> (5 * (7 - j))) & 31] - return ''.join(ret) - -def AddChecksum(desc: str) -> str: - """ - Compute and attach the checksum for a descriptor - :param desc: The descriptor string to add a checksum to - :return: Descriptor with checksum - """ - return desc + "#" + DescriptorChecksum(desc) - - -class PubkeyProvider(object): - """ - A public key expression in a descriptor. - Can contain the key origin info, the pubkey itself, and subsequent derivation paths for derivation from the pubkey - The pubkey can be a typical pubkey or an extended pubkey. - """ - - def __init__( - self, - origin: Optional['KeyOriginInfo'], - pubkey: str, - deriv_path: Optional[str] - ) -> None: - """ - :param origin: The key origin if one is available - :param pubkey: The public key. Either a hex string or a serialized extended pubkey - :param deriv_path: Additional derivation path if the pubkey is an extended pubkey - """ - self.origin = origin - self.pubkey = pubkey - self.deriv_path = deriv_path - - # Make ExtendedKey from pubkey if it isn't hex - self.extkey = None - try: - unhexlify(self.pubkey) - # Is hex, normal pubkey - except Exception: - # Not hex, maybe xpub - self.extkey = ExtendedKey.deserialize(self.pubkey) - - @classmethod - def parse(cls, s: str) -> 'PubkeyProvider': - """ - Deserialize a key expression from the string into a ``PubkeyProvider``. - :param s: String containing the key expression - :return: A new ``PubkeyProvider`` containing the details given by ``s`` - """ - origin = None - deriv_path = None - - if s[0] == "[": - end = s.index("]") - origin = KeyOriginInfo.from_string(s[1:end]) - s = s[end + 1:] - - pubkey = s - slash_idx = s.find("/") - if slash_idx != -1: - pubkey = s[:slash_idx] - deriv_path = s[slash_idx:] - - return cls(origin, pubkey, deriv_path) - - def to_string(self) -> str: - """ - Serialize the pubkey expression to a string to be used in a descriptor - :return: The pubkey expression as a string - """ - s = "" - if self.origin: - s += "[{}]".format(self.origin.to_string()) - s += self.pubkey - if self.deriv_path: - s += self.deriv_path - return s - - def get_pubkey_bytes(self, pos: int) -> bytes: - if self.extkey is not None: - if self.deriv_path is not None: - path_str = self.deriv_path[1:] - if path_str[-1] == "*": - path_str = path_str[-1] + str(pos) - path = parse_path(path_str) - child_key = self.extkey.derive_pub_path(path) - return child_key.pubkey - else: - return self.extkey.pubkey - return unhexlify(self.pubkey) - - def get_full_derivation_path(self, pos: int) -> str: - """ - Returns the full derivation path at the given position, including the origin - """ - path = self.origin.get_derivation_path() if self.origin is not None else "m/" - path += self.deriv_path if self.deriv_path is not None else "" - if path[-1] == "*": - path = path[:-1] + str(pos) - return path - - def get_full_derivation_int_list(self, pos: int) -> List[int]: - """ - Returns the full derivation path as an integer list at the given position. - Includes the origin and master key fingerprint as an int - """ - path: List[int] = self.origin.get_full_int_list() if self.origin is not None else [] - if self.deriv_path is not None: - der_split = self.deriv_path.split("/") - for p in der_split: - if not p: - continue - if p == "*": - i = pos - elif p[-1] in "'phHP": - assert len(p) >= 2 - i = int(p[:-1]) | 0x80000000 - else: - i = int(p) - path.append(i) - return path - - def __lt__(self, other: 'PubkeyProvider') -> bool: - return self.pubkey < other.pubkey - - -class Descriptor(object): - r""" - An abstract class for Descriptors themselves. - Descriptors can contain multiple :class:`PubkeyProvider`\ s and multiple ``Descriptor`` as subdescriptors. - """ - - def __init__( - self, - pubkeys: List['PubkeyProvider'], - subdescriptors: List['Descriptor'], - name: str - ) -> None: - r""" - :param pubkeys: The :class:`PubkeyProvider`\ s that are part of this descriptor - :param subdescriptor: The ``Descriptor``s that are part of this descriptor - :param name: The name of the function for this descriptor - """ - self.pubkeys = pubkeys - self.subdescriptors = subdescriptors - self.name = name - - def to_string_no_checksum(self) -> str: - """ - Serializes the descriptor as a string without the descriptor checksum - :return: The descriptor string - """ - return "{}({}{})".format( - self.name, - ",".join([p.to_string() for p in self.pubkeys]), - self.subdescriptors[0].to_string_no_checksum() if len(self.subdescriptors) > 0 else "" - ) - - def to_string(self) -> str: - """ - Serializes the descriptor as a string with the checksum - :return: The descriptor with a checksum - """ - return AddChecksum(self.to_string_no_checksum()) - - def expand(self, pos: int) -> "ExpandedScripts": - """ - Returns the scripts for a descriptor at the given `pos` for ranged descriptors. - """ - raise NotImplementedError("The Descriptor base class does not implement this method") - - -class PKDescriptor(Descriptor): - """ - A descriptor for ``pk()`` descriptors - """ - - def __init__( - self, - pubkey: 'PubkeyProvider' - ) -> None: - """ - :param pubkey: The :class:`PubkeyProvider` for this descriptor - """ - super().__init__([pubkey], [], "pk") - - -class PKHDescriptor(Descriptor): - """ - A descriptor for ``pkh()`` descriptors - """ - - def __init__( - self, - pubkey: 'PubkeyProvider' - ) -> None: - """ - :param pubkey: The :class:`PubkeyProvider` for this descriptor - """ - super().__init__([pubkey], [], "pkh") - - def expand(self, pos: int) -> "ExpandedScripts": - script = b"\x76\xa9\x14" + hash160(self.pubkeys[0].get_pubkey_bytes(pos)) + b"\x88\xac" - return ExpandedScripts(script, None, None) - - -class WPKHDescriptor(Descriptor): - """ - A descriptor for ``wpkh()`` descriptors - """ - - def __init__( - self, - pubkey: 'PubkeyProvider' - ) -> None: - """ - :param pubkey: The :class:`PubkeyProvider` for this descriptor - """ - super().__init__([pubkey], [], "wpkh") - - def expand(self, pos: int) -> "ExpandedScripts": - script = b"\x00\x14" + hash160(self.pubkeys[0].get_pubkey_bytes(pos)) - return ExpandedScripts(script, None, None) - - -class MultisigDescriptor(Descriptor): - """ - A descriptor for ``multi()`` and ``sortedmulti()`` descriptors - """ - - def __init__( - self, - pubkeys: List['PubkeyProvider'], - thresh: int, - is_sorted: bool - ) -> None: - r""" - :param pubkeys: The :class:`PubkeyProvider`\ s for this descriptor - :param thresh: The number of keys required to sign this multisig - :param is_sorted: Whether this is a ``sortedmulti()`` descriptor - """ - super().__init__(pubkeys, [], "sortedmulti" if is_sorted else "multi") - self.thresh = thresh - self.is_sorted = is_sorted - if self.is_sorted: - self.pubkeys.sort() - - def to_string_no_checksum(self) -> str: - return "{}({},{})".format(self.name, self.thresh, ",".join([p.to_string() for p in self.pubkeys])) - - def expand(self, pos: int) -> "ExpandedScripts": - if self.thresh > 16: - m = b"\x01" + self.thresh.to_bytes(1, "big") - else: - m = (self.thresh + 0x50).to_bytes(1, "big") if self.thresh > 0 else b"\x00" - n = (len(self.pubkeys) + 0x50).to_bytes(1, "big") if len(self.pubkeys) > 0 else b"\x00" - script: bytes = m - der_pks = [p.get_pubkey_bytes(pos) for p in self.pubkeys] - if self.is_sorted: - der_pks.sort() - for pk in der_pks: - script += len(pk).to_bytes(1, "big") + pk - script += n + b"\xae" - - return ExpandedScripts(script, None, None) - - -class SHDescriptor(Descriptor): - """ - A descriptor for ``sh()`` descriptors - """ - - def __init__( - self, - subdescriptor: 'Descriptor' - ) -> None: - """ - :param subdescriptor: The :class:`Descriptor` that is a sub-descriptor for this descriptor - """ - super().__init__([], [subdescriptor], "sh") - - def expand(self, pos: int) -> "ExpandedScripts": - assert len(self.subdescriptors) == 1 - redeem_script, _, witness_script = self.subdescriptors[0].expand(pos) - script = b"\xa9\x14" + hash160(redeem_script) + b"\x87" - return ExpandedScripts(script, redeem_script, witness_script) - - -class WSHDescriptor(Descriptor): - """ - A descriptor for ``wsh()`` descriptors - """ - - def __init__( - self, - subdescriptor: 'Descriptor' - ) -> None: - """ - :param subdescriptor: The :class:`Descriptor` that is a sub-descriptor for this descriptor - """ - super().__init__([], [subdescriptor], "wsh") - - def expand(self, pos: int) -> "ExpandedScripts": - assert len(self.subdescriptors) == 1 - witness_script, _, _ = self.subdescriptors[0].expand(pos) - script = b"\x00\x20" + sha256(witness_script) - return ExpandedScripts(script, None, witness_script) - - -class TRDescriptor(Descriptor): - """ - A descriptor for ``tr()`` descriptors - """ - - def __init__( - self, - internal_key: 'PubkeyProvider', - subdescriptors: List['Descriptor'] = [], - depths: List[int] = [] - ) -> None: - """ - :param internal_key: The :class:`PubkeyProvider` that is the internal key for this descriptor - :param subdescriptors: The :class:`Descriptor`s that are the leaf scripts for this descriptor - :param depths: The depths of the leaf scripts in the same order as `subdescriptors` - """ - super().__init__([internal_key], subdescriptors, "tr") - self.depths = depths - - def to_string_no_checksum(self) -> str: - r = f"{self.name}({self.pubkeys[0].to_string()}" - path: List[bool] = [] # Track left or right for each depth - for p, depth in enumerate(self.depths): - r += "," - while len(path) <= depth: - if len(path) > 0: - r += "{" - path.append(False) - r += self.subdescriptors[p].to_string_no_checksum() - while len(path) > 0 and path[-1]: - if len(path) > 0: - r += "}" - path.pop() - if len(path) > 0: - path[-1] = True - r += ")" - return r - -def _get_func_expr(s: str) -> Tuple[str, str]: - """ - Get the function name and then the expression inside - :param s: The string that begins with a function name - :return: The function name as the first element of the tuple, and the expression contained within the function as the second element - :raises: ValueError: if a matching pair of parentheses cannot be found - """ - start = s.index("(") - end = s.rindex(")") - return s[0:start], s[start + 1:end] - - -def _get_const(s: str, const: str) -> str: - """ - Get the first character of the string, make sure it is the expected character, - and return the rest of the string - :param s: The string that begins with a constant character - :param const: The constant character - :return: The remainder of the string without the constant character - :raises: ValueError: if the first character is not the constant character - """ - if s[0] != const: - raise ValueError(f"Expected '{const}' but got '{s[0]}'") - return s[1:] - - -def _get_expr(s: str) -> Tuple[str, str]: - """ - Extract the expression that ``s`` begins with. - This will return the initial part of ``s``, up to the first comma or closing brace, - skipping ones that are surrounded by braces. - :param s: The string to extract the expression from - :return: A pair with the first item being the extracted expression and the second the rest of the string - """ - level: int = 0 - for i, c in enumerate(s): - if c in ["(", "{"]: - level += 1 - elif level > 0 and c in [")", "}"]: - level -= 1 - elif level == 0 and c in [")", "}", ","]: - break - return s[0:i], s[i:] - -def parse_pubkey(expr: str) -> Tuple['PubkeyProvider', str]: - """ - Parses an individual pubkey expression from a string that may contain more than one pubkey expression. - :param expr: The expression to parse a pubkey expression from - :return: The :class:`PubkeyProvider` that is parsed as the first item of a tuple, and the remainder of the expression as the second item. - """ - end = len(expr) - comma_idx = expr.find(",") - next_expr = "" - if comma_idx != -1: - end = comma_idx - next_expr = expr[end + 1:] - return PubkeyProvider.parse(expr[:end]), next_expr - - -class _ParseDescriptorContext(Enum): - """ - :meta private: - Enum representing the level that we are in when parsing a descriptor. - Some expressions aren't allowed at certain levels, this helps us track those. - """ - - TOP = 1 - """The top level, not within any descriptor""" - - P2SH = 2 - """Within a ``sh()`` descriptor""" - - P2WSH = 3 - """Within a ``wsh()`` descriptor""" - - P2TR = 4 - """Within a ``tr()`` descriptor""" - - -def _parse_descriptor(desc: str, ctx: '_ParseDescriptorContext') -> 'Descriptor': - """ - :meta private: - Parse a descriptor given the context level we are in. - Used recursively to parse subdescriptors - :param desc: The descriptor string to parse - :param ctx: The :class:`_ParseDescriptorContext` indicating the level we are in - :return: The parsed descriptor - :raises: ValueError: if the descriptor is malformed - """ - func, expr = _get_func_expr(desc) - if func == "pk": - pubkey, expr = parse_pubkey(expr) - if expr: - raise ValueError("more than one pubkey in pk descriptor") - return PKDescriptor(pubkey) - if func == "pkh": - if not (ctx == _ParseDescriptorContext.TOP or ctx == _ParseDescriptorContext.P2SH or ctx == _ParseDescriptorContext.P2WSH): - raise ValueError("Can only have pkh at top level, in sh(), or in wsh()") - pubkey, expr = parse_pubkey(expr) - if expr: - raise ValueError("More than one pubkey in pkh descriptor") - return PKHDescriptor(pubkey) - if func == "sortedmulti" or func == "multi": - if not (ctx == _ParseDescriptorContext.TOP or ctx == _ParseDescriptorContext.P2SH or ctx == _ParseDescriptorContext.P2WSH): - raise ValueError("Can only have multi/sortedmulti at top level, in sh(), or in wsh()") - is_sorted = func == "sortedmulti" - comma_idx = expr.index(",") - thresh = int(expr[:comma_idx]) - expr = expr[comma_idx + 1:] - pubkeys = [] - while expr: - pubkey, expr = parse_pubkey(expr) - pubkeys.append(pubkey) - if len(pubkeys) == 0 or len(pubkeys) > 16: - raise ValueError("Cannot have {} keys in a multisig; must have between 1 and 16 keys, inclusive".format(len(pubkeys))) - elif thresh < 1: - raise ValueError("Multisig threshold cannot be {}, must be at least 1".format(thresh)) - elif thresh > len(pubkeys): - raise ValueError("Multisig threshold cannot be larger than the number of keys; threshold is {} but only {} keys specified".format(thresh, len(pubkeys))) - if ctx == _ParseDescriptorContext.TOP and len(pubkeys) > 3: - raise ValueError("Cannot have {} pubkeys in bare multisig: only at most 3 pubkeys") - return MultisigDescriptor(pubkeys, thresh, is_sorted) - if func == "wpkh": - if not (ctx == _ParseDescriptorContext.TOP or ctx == _ParseDescriptorContext.P2SH): - raise ValueError("Can only have wpkh() at top level or inside sh()") - pubkey, expr = parse_pubkey(expr) - if expr: - raise ValueError("More than one pubkey in pkh descriptor") - return WPKHDescriptor(pubkey) - if func == "sh": - if ctx != _ParseDescriptorContext.TOP: - raise ValueError("Can only have sh() at top level") - subdesc = _parse_descriptor(expr, _ParseDescriptorContext.P2SH) - return SHDescriptor(subdesc) - if func == "wsh": - if not (ctx == _ParseDescriptorContext.TOP or ctx == _ParseDescriptorContext.P2SH): - raise ValueError("Can only have wsh() at top level or inside sh()") - subdesc = _parse_descriptor(expr, _ParseDescriptorContext.P2WSH) - return WSHDescriptor(subdesc) - if func == "tr": - if ctx != _ParseDescriptorContext.TOP: - raise ValueError("Can only have tr at top level") - internal_key, expr = parse_pubkey(expr) - subscripts = [] - depths = [] - if expr: - # Path from top of the tree to what we're currently processing. - # branches[i] == False: left branch in the i'th step from the top - # branches[i] == true: right branch - branches = [] - while True: - # Process open braces - while True: - try: - expr = _get_const(expr, "{") - branches.append(False) - except ValueError: - break - if len(branches) > MAX_TAPROOT_NODES: - raise ValueError("tr() supports at most {MAX_TAPROOT_NODES} nesting levels") - # Process script expression - sarg, expr = _get_expr(expr) - subscripts.append(_parse_descriptor(sarg, _ParseDescriptorContext.P2TR)) - depths.append(len(branches)) - # Process closing braces - while len(branches) > 0 and branches[-1]: - expr = _get_const(expr, "}") - branches.pop() - # If we're at the end of a left branch, expect a comma - if len(branches) > 0 and not branches[-1]: - expr = _get_const(expr, ",") - branches[-1] = True - - if len(branches) == 0: - break - return TRDescriptor(internal_key, subscripts, depths) - if ctx == _ParseDescriptorContext.P2SH: - raise ValueError("A function is needed within P2SH") - elif ctx == _ParseDescriptorContext.P2WSH: - raise ValueError("A function is needed within P2WSH") - raise ValueError("{} is not a valid descriptor function".format(func)) - - -def parse_descriptor(desc: str) -> 'Descriptor': - """ - Parse a descriptor string into a :class:`Descriptor`. - Validates the checksum if one is provided in the string - :param desc: The descriptor string - :return: The parsed :class:`Descriptor` - :raises: ValueError: if the descriptor string is malformed - """ - i = desc.find("#") - if i != -1: - checksum = desc[i + 1:] - desc = desc[:i] - computed = DescriptorChecksum(desc) - if computed != checksum: - raise ValueError("The checksum does not match; Got {}, expected {}".format(checksum, computed)) - return _parse_descriptor(desc, _ParseDescriptorContext.TOP) diff --git a/bitcoin_client/ledger_bitcoin/embit/LICENSE b/bitcoin_client/ledger_bitcoin/embit/LICENSE new file mode 100644 index 000000000..db295028b --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Stepan Snigirev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bitcoin_client/ledger_bitcoin/embit/README.md b/bitcoin_client/ledger_bitcoin/embit/README.md new file mode 100644 index 000000000..66483faed --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/README.md @@ -0,0 +1,5 @@ +This is a stripped down version of the embit library, cloned at commit [189efc45](https://github.com/diybitcoinhardware/embit/tree/189efc4583d497a2b97632646daf1531d00442b0). + +Support for the `0` and `1` miniscript fragments was added after cloning. + +All the content of this folder is released according to the [LICENSE](LICENSE), as per the original repository. diff --git a/tests-legacy/bitcoin_client/__init__.py b/bitcoin_client/ledger_bitcoin/embit/__init__.py similarity index 100% rename from tests-legacy/bitcoin_client/__init__.py rename to bitcoin_client/ledger_bitcoin/embit/__init__.py diff --git a/bitcoin_client/ledger_bitcoin/embit/base.py b/bitcoin_client/ledger_bitcoin/embit/base.py new file mode 100644 index 000000000..9dbac7398 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/base.py @@ -0,0 +1,116 @@ +"""Base classes""" +from io import BytesIO +from binascii import hexlify, unhexlify + + +class EmbitError(Exception): + """Generic Embit error""" + + pass + + +class EmbitBase: + @classmethod + def read_from(cls, stream, *args, **kwargs): + """All classes should be readable from stream""" + raise NotImplementedError( + "%s doesn't implement reading from stream" % cls.__name__ + ) + + @classmethod + def parse(cls, s: bytes, *args, **kwargs): + """Parse raw bytes""" + stream = BytesIO(s) + res = cls.read_from(stream, *args, **kwargs) + if len(stream.read(1)) > 0: + raise EmbitError("Unexpected extra bytes") + return res + + def write_to(self, stream, *args, **kwargs) -> int: + """All classes should be writable to stream""" + raise NotImplementedError( + "%s doesn't implement writing to stream" % type(self).__name__ + ) + + def serialize(self, *args, **kwargs) -> bytes: + """Serialize instance to raw bytes""" + stream = BytesIO() + self.write_to(stream, *args, **kwargs) + return stream.getvalue() + + def to_string(self, *args, **kwargs) -> str: + """ + String representation. + If not implemented - uses hex or calls to_base58() method if defined. + """ + if hasattr(self, "to_base58"): + res = self.to_base58(*args, **kwargs) + if not isinstance(res, str): + raise ValueError("to_base58() must return string") + return res + return hexlify(self.serialize(*args, **kwargs)).decode() + + @classmethod + def from_string(cls, s, *args, **kwargs): + """Create class instance from string""" + if hasattr(cls, "from_base58"): + return cls.from_base58(s, *args, **kwargs) + return cls.parse(unhexlify(s)) + + def __str__(self): + """Internally calls `to_string()` method with no arguments""" + return self.to_string() + + def __repr__(self): + try: + return type(self).__name__ + "(%s)" % str(self) + except: + return type(self).__name__ + "()" + + def __eq__(self, other): + """Compare two objects by checking their serializations""" + if not hasattr(other, "serialize"): + return False + return self.serialize() == other.serialize() + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(self.serialize()) + + +class EmbitKey(EmbitBase): + def sec(self) -> bytes: + """ + Any EmbitKey should implement sec() method that returns + a sec-serialized public key + """ + raise NotImplementedError( + "%s doesn't implement sec() method" % type(self).__name__ + ) + + def xonly(self) -> bytes: + """xonly representation of the key""" + return self.sec()[1:33] + + @property + def is_private(self) -> bool: + """ + Any EmbitKey should implement `is_private` property to distinguish + between private and public keys. + """ + raise NotImplementedError( + "%s doesn't implement is_private property" % type(self).__name__ + ) + + def __lt__(self, other): + # for lexagraphic ordering + return self.sec() < other.sec() + + def __gt__(self, other): + # for lexagraphic ordering + return self.sec() > other.sec() + + def __hash__(self): + return hash(self.serialize()) diff --git a/bitcoin_client/ledger_bitcoin/embit/base58.py b/bitcoin_client/ledger_bitcoin/embit/base58.py new file mode 100644 index 000000000..196a79dbf --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/base58.py @@ -0,0 +1,79 @@ +# Partially copy-pasted from python-bitcoinlib: +# https://github.com/petertodd/python-bitcoinlib/blob/master/bitcoin/base58.py + +"""Base58 encoding and decoding""" + +import binascii +from . import hashes + +B58_DIGITS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + +def encode(b: bytes) -> str: + """Encode bytes to a base58-encoded string""" + + # Convert big-endian bytes to integer + n = int("0x0" + binascii.hexlify(b).decode("utf8"), 16) + + # Divide that integer into bas58 + chars = [] + while n > 0: + n, r = divmod(n, 58) + chars.append(B58_DIGITS[r]) + result = "".join(chars[::-1]) + + pad = 0 + for c in b: + if c == 0: + pad += 1 + else: + break + return B58_DIGITS[0] * pad + result + + +def decode(s: str) -> bytes: + """Decode a base58-encoding string, returning bytes""" + if not s: + return b"" + + # Convert the string to an integer + n = 0 + for c in s: + n *= 58 + if c not in B58_DIGITS: + raise ValueError("Character %r is not a valid base58 character" % c) + digit = B58_DIGITS.index(c) + n += digit + + # Convert the integer to bytes + h = "%x" % n + if len(h) % 2: + h = "0" + h + res = binascii.unhexlify(h.encode("utf8")) + + # Add padding back. + pad = 0 + for c in s[:-1]: + if c == B58_DIGITS[0]: + pad += 1 + else: + break + return b"\x00" * pad + res + + +def encode_check(b: bytes) -> str: + """Encode bytes to a base58-encoded string with a checksum""" + return encode(b + hashes.double_sha256(b)[0:4]) + + +def decode_check(s: str) -> bytes: + """Decode a base58-encoding string with checksum check. + Returns bytes without checksum + """ + b = decode(s) + checksum = hashes.double_sha256(b[:-4])[:4] + if b[-4:] != checksum: + raise ValueError( + "Checksum mismatch: expected %r, calculated %r" % (b[-4:], checksum) + ) + return b[:-4] diff --git a/bitcoin_client/ledger_bitcoin/embit/bech32.py b/bitcoin_client/ledger_bitcoin/embit/bech32.py new file mode 100644 index 000000000..24ee7fa72 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/bech32.py @@ -0,0 +1,146 @@ +# Copyright (c) 2017 Pieter Wuille +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Reference implementation for Bech32 and segwit addresses.""" +from .misc import const + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32_CONST = const(1) +BECH32M_CONST = const(0x2BC830A3) + + +class Encoding: + """Enumeration type to list the various supported encodings.""" + + BECH32 = 1 + BECH32M = 2 + + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1FFFFFF) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp: str): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + check = bech32_polymod(bech32_hrp_expand(hrp) + data) + if check == BECH32_CONST: + return Encoding.BECH32 + elif check == BECH32M_CONST: + return Encoding.BECH32M + else: + return None + + +def bech32_create_checksum(encoding, hrp, data): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + const = BECH32M_CONST if encoding == Encoding.BECH32M else BECH32_CONST + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(encoding, hrp, data): + """Compute a Bech32 or Bech32m string given HRP and data values.""" + combined = data + bech32_create_checksum(encoding, hrp, data) + return hrp + "1" + "".join([CHARSET[d] for d in combined]) + + +def bech32_decode(bech): + """Validate a Bech32/Bech32m string, and determine HRP and data.""" + if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or ( + bech.lower() != bech and bech.upper() != bech + ): + return (None, None, None) + bech = bech.lower() + pos = bech.rfind("1") + if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: + return (None, None, None) + if not all(x in CHARSET for x in bech[pos + 1 :]): + return (None, None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos + 1 :]] + encoding = bech32_verify_checksum(hrp, data) + if encoding is None: + return (None, None, None) + return (encoding, hrp, data[:-6]) + + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + encoding, hrpgot, data = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2 or len(decoded) > 40: + return (None, None) + if data[0] > 16: + return (None, None) + if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: + return (None, None) + if (data[0] == 0 and encoding != Encoding.BECH32) or ( + data[0] != 0 and encoding != Encoding.BECH32M + ): + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + encoding = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5)) + if decode(hrp, ret) == (None, None): + return None + return ret diff --git a/bitcoin_client/ledger_bitcoin/embit/bip32.py b/bitcoin_client/ledger_bitcoin/embit/bip32.py new file mode 100644 index 000000000..e31cb7a26 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/bip32.py @@ -0,0 +1,309 @@ +from . import ec +from .base import EmbitKey, EmbitError +from .misc import copy, const, secp256k1 +from .networks import NETWORKS +from . import base58 +from . import hashes +import hmac +from binascii import hexlify + +HARDENED_INDEX = const(0x80000000) + + +class HDError(EmbitError): + pass + + +class HDKey(EmbitKey): + """HD Private or Public key""" + + def __init__( + self, + key: EmbitKey, # more specifically, PrivateKey or PublicKey + chain_code: bytes, + version=None, + depth: int = 0, + fingerprint: bytes = b"\x00\x00\x00\x00", + child_number: int = 0, + ): + self.key = key + if len(key.serialize()) != 32 and len(key.serialize()) != 33: + raise HDError("Invalid key. Should be private or compressed public") + if version is not None: + self.version = version + else: + if len(key.serialize()) == 32: + self.version = NETWORKS["main"]["xprv"] + else: + self.version = NETWORKS["main"]["xpub"] + self.chain_code = chain_code + self.depth = depth + self.fingerprint = fingerprint + self._my_fingerprint = b"" + self.child_number = child_number + # check that base58[1:4] is "prv" or "pub" + if self.is_private and self.to_base58()[1:4] != "prv": + raise HDError("Invalid version") + if not self.is_private and self.to_base58()[1:4] != "pub": + raise HDError("Invalid version") + + @classmethod + def from_seed(cls, seed: bytes, version=NETWORKS["main"]["xprv"]): + """Creates a root private key from 64-byte seed""" + raw = hmac.new(b"Bitcoin seed", seed, digestmod="sha512").digest() + private_key = ec.PrivateKey(raw[:32]) + chain_code = raw[32:] + return cls(private_key, chain_code, version=version) + + @classmethod + def from_base58(cls, s: str): + b = base58.decode_check(s) + return cls.parse(b) + + @property + def my_fingerprint(self) -> bytes: + if not self._my_fingerprint: + sec = self.sec() + self._my_fingerprint = hashes.hash160(sec)[:4] + return self._my_fingerprint + + @property + def is_private(self) -> bool: + """checks if the HDKey is private or public""" + return self.key.is_private + + @property + def secret(self): + if not self.is_private: + raise HDError("Key is not private") + return self.key.secret + + def write_to(self, stream, version=None) -> int: + if version is None: + version = self.version + res = stream.write(version) + res += stream.write(bytes([self.depth])) + res += stream.write(self.fingerprint) + res += stream.write(self.child_number.to_bytes(4, "big")) + res += stream.write(self.chain_code) + if self.is_private: + res += stream.write(b"\x00") + res += stream.write(self.key.serialize()) + return res + + def to_base58(self, version=None) -> str: + b = self.serialize(version) + res = base58.encode_check(b) + if res[1:4] == "prv" and not self.is_private: + raise HDError("Invalid version for private key") + if res[1:4] == "pub" and self.is_private: + raise HDError("Invalid version for public key") + return res + + @classmethod + def from_string(cls, s: str): + return cls.from_base58(s) + + def to_string(self, version=None): + return self.to_base58(version) + + @classmethod + def read_from(cls, stream): + version = stream.read(4) + depth = stream.read(1)[0] + fingerprint = stream.read(4) + child_number = int.from_bytes(stream.read(4), "big") + chain_code = stream.read(32) + k = stream.read(33) + if k[0] == 0: + key = ec.PrivateKey.parse(k[1:]) + else: + key = ec.PublicKey.parse(k) + + if len(version) < 4 or len(fingerprint) < 4 or len(chain_code) < 32: + raise HDError("Not enough bytes") + hd = cls( + key, + chain_code, + version=version, + depth=depth, + fingerprint=fingerprint, + child_number=child_number, + ) + subver = hd.to_base58()[1:4] + if subver != "prv" and subver != "pub": + raise HDError("Invalid version") + if depth == 0 and child_number != 0: + raise HDError("zero depth with non-zero index") + if depth == 0 and fingerprint != b"\x00\x00\x00\x00": + raise HDError("zero depth with non-zero parent") + return hd + + def to_public(self, version=None): + if not self.is_private: + raise HDError("Already public") + if version is None: + # detect network + for net in NETWORKS: + for k in NETWORKS[net]: + if "prv" in k and NETWORKS[net][k] == self.version: + # xprv -> xpub, zprv -> zpub etc + version = NETWORKS[net][k.replace("prv", "pub")] + break + if version is None: + raise HDError("Can't find proper version. Provide it with version keyword") + return self.__class__( + self.key.get_public_key(), + self.chain_code, + version=version, + depth=self.depth, + fingerprint=self.fingerprint, + child_number=self.child_number, + ) + + def get_public_key(self): + return self.key.get_public_key() if self.is_private else self.key + + def sec(self) -> bytes: + """Returns SEC serialization of the public key""" + return self.key.sec() + + def xonly(self) -> bytes: + return self.key.xonly() + + def taproot_tweak(self, h=b""): + return HDKey( + self.key.taproot_tweak(h), + self.chain_code, + version=self.version, + depth=self.depth, + fingerprint=self.fingerprint, + child_number=self.child_number, + ) + + def child(self, index: int, hardened: bool = False): + """Derives a child HDKey""" + if index > 0xFFFFFFFF: + raise HDError("Index should be less then 2^32") + if hardened and index < HARDENED_INDEX: + index += HARDENED_INDEX + if index >= HARDENED_INDEX: + hardened = True + if hardened and not self.is_private: + raise HDError("Can't do hardened with public key") + + # we need pubkey for fingerprint anyways + sec = self.sec() + fingerprint = hashes.hash160(sec)[:4] + if hardened: + data = b"\x00" + self.key.serialize() + index.to_bytes(4, "big") + else: + data = sec + index.to_bytes(4, "big") + raw = hmac.new(self.chain_code, data, digestmod="sha512").digest() + secret = raw[:32] + chain_code = raw[32:] + if self.is_private: + secret = secp256k1.ec_privkey_add(secret, self.key.serialize()) + key = ec.PrivateKey(secret) + else: + # copy of internal secp256k1 point structure + point = copy(self.key._point) + point = secp256k1.ec_pubkey_add(point, secret) + key = ec.PublicKey(point) + return HDKey( + key, + chain_code, + version=self.version, + depth=self.depth + 1, + fingerprint=fingerprint, + child_number=index, + ) + + def derive(self, path): + """path: int array or a string starting with m/""" + if isinstance(path, str): + # string of the form m/44h/0'/ind + path = parse_path(path) + child = self + for idx in path: + child = child.child(idx) + return child + + def sign(self, msg_hash: bytes) -> ec.Signature: + """signs a hash of the message with the private key""" + if not self.is_private: + raise HDError("HD public key can't sign") + return self.key.sign(msg_hash) + + def schnorr_sign(self, msg_hash): + if not self.is_private: + raise HDError("HD public key can't sign") + return self.key.schnorr_sign(msg_hash) + + def verify(self, sig, msg_hash) -> bool: + return self.key.verify(sig, msg_hash) + + def schnorr_verify(self, sig, msg_hash) -> bool: + return self.key.schnorr_verify(sig, msg_hash) + + def __eq__(self, other): + # skip version + return self.serialize()[4:] == other.serialize()[4:] + + def __hash__(self): + return hash(self.serialize()) + + +def detect_version(path, default="xprv", network=None) -> bytes: + """ + Detects slip-132 version from the path for certain network. + Trying to be smart, use if you want, but with care. + """ + key = default + net = network + if network is None: + net = NETWORKS["main"] + if isinstance(path, str): + path = parse_path(path) + if len(path) == 0: + return network[key] + if path[0] == HARDENED_INDEX + 84: + key = "z" + default[1:] + elif path[0] == HARDENED_INDEX + 49: + key = "y" + default[1:] + elif path[0] == HARDENED_INDEX + 48: + if len(path) >= 4: + if path[3] == HARDENED_INDEX + 1: + key = "Y" + default[1:] + elif path[3] == HARDENED_INDEX + 2: + key = "Z" + default[1:] + if network is None and len(path) > 1 and path[1] == HARDENED_INDEX + 1: + net = NETWORKS["test"] + return net[key] + + +def _parse_der_item(e: str) -> int: + if e[-1] in {"h", "H", "'"}: + return int(e[:-1]) + HARDENED_INDEX + else: + return int(e) + + +def parse_path(path: str) -> list: + """converts derivation path of the form m/44h/1'/0'/0/32 to int array""" + arr = path.rstrip("/").split("/") + if arr[0] == "m": + arr = arr[1:] + if len(arr) == 0: + return [] + return [_parse_der_item(e) for e in arr] + + +def path_to_str(path: list, fingerprint=None) -> str: + s = "m" if fingerprint is None else hexlify(fingerprint).decode() + for el in path: + if el >= HARDENED_INDEX: + s += "/%dh" % (el - HARDENED_INDEX) + else: + s += "/%d" % el + return s diff --git a/bitcoin_client/ledger_bitcoin/embit/compact.py b/bitcoin_client/ledger_bitcoin/embit/compact.py new file mode 100644 index 000000000..0138f394e --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/compact.py @@ -0,0 +1,41 @@ +""" Compact Int parsing / serialization """ +import io + + +def to_bytes(i: int) -> bytes: + """encodes an integer as a compact int""" + if i < 0: + raise ValueError("integer can't be negative: {}".format(i)) + order = 0 + while i >> (8 * (2**order)): + order += 1 + if order == 0: + if i < 0xFD: + return bytes([i]) + order = 1 + if order > 3: + raise ValueError("integer too large: {}".format(i)) + return bytes([0xFC + order]) + i.to_bytes(2**order, "little") + + +def from_bytes(b: bytes) -> int: + s = io.BytesIO(b) + res = read_from(s) + if len(s.read(1)) > 0: + raise ValueError("Too many bytes") + return res + + +def read_from(stream) -> int: + """reads a compact integer from a stream""" + c = stream.read(1) + if not isinstance(c, bytes): + raise TypeError("Bytes must be returned from stream.read()") + if len(c) != 1: + raise RuntimeError("Can't read one byte from the stream") + i = c[0] + if i >= 0xFD: + bytes_to_read = 2 ** (i - 0xFC) + return int.from_bytes(stream.read(bytes_to_read), "little") + else: + return i diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/__init__.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/__init__.py new file mode 100644 index 000000000..600296d3c --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/__init__.py @@ -0,0 +1,3 @@ +from . import miniscript +from .descriptor import Descriptor +from .arguments import Key diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/arguments.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/arguments.py new file mode 100644 index 000000000..3f92500a7 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/arguments.py @@ -0,0 +1,515 @@ +from binascii import hexlify, unhexlify +from .base import DescriptorBase +from .errors import ArgumentError +from .. import bip32, ec, compact, hashes +from ..bip32 import HARDENED_INDEX +from ..misc import read_until + + +class KeyOrigin: + def __init__(self, fingerprint: bytes, derivation: list): + self.fingerprint = fingerprint + self.derivation = derivation + + @classmethod + def from_string(cls, s: str): + arr = s.split("/") + mfp = unhexlify(arr[0]) + assert len(mfp) == 4 + arr[0] = "m" + path = "/".join(arr) + derivation = bip32.parse_path(path) + return cls(mfp, derivation) + + def __str__(self): + return bip32.path_to_str(self.derivation, fingerprint=self.fingerprint) + + +class AllowedDerivation(DescriptorBase): + # xpub/<0;1>/* - <0;1> is a set of allowed branches, wildcard * is stored as None + def __init__(self, indexes=[[0, 1], None]): + # check only one wildcard + if ( + len( + [i for i in indexes if i is None or (isinstance(i, list) and None in i)] + ) + > 1 + ): + raise ArgumentError("Only one wildcard is allowed") + # check only one set is in the derivation + if len([i for i in indexes if isinstance(i, list)]) > 1: + raise ArgumentError("Only one set of branches is allowed") + self.indexes = indexes + + @property + def is_wildcard(self): + return None in self.indexes + + def fill(self, idx, branch_index=None): + # None is ok + if idx is not None and (idx < 0 or idx >= HARDENED_INDEX): + raise ArgumentError("Hardened indexes are not allowed in wildcard") + arr = [i for i in self.indexes] + for i, el in enumerate(arr): + if el is None: + arr[i] = idx + if isinstance(el, list): + if branch_index is None: + arr[i] = el[0] + else: + if branch_index < 0 or branch_index >= len(el): + raise ArgumentError("Invalid branch index") + arr[i] = el[branch_index] + return arr + + def branch(self, branch_index): + arr = self.fill(None, branch_index) + return type(self)(arr) + + def check_derivation(self, derivation: list): + if len(derivation) != len(self.indexes): + return None + branch_idx = 0 # default branch if no branches in descriptor + idx = None + for i, el in enumerate(self.indexes): + der = derivation[i] + if isinstance(el, int): + if el != der: + return None + # branch + elif isinstance(el, list): + if der not in el: + return None + branch_idx = el.index(der) + # wildcard + elif el is None: + idx = der + # shouldn't happen + else: + raise ArgumentError("Strange derivation index...") + if branch_idx is not None and idx is not None: + return idx, branch_idx + + @classmethod + def default(cls): + return AllowedDerivation([[0, 1], None]) + + @property + def branches(self): + for el in self.indexes: + if isinstance(el, list): + return el + return None + + @property + def has_hardend(self): + for idx in self.indexes: + if isinstance(idx, int) and idx >= HARDENED_INDEX: + return True + if ( + isinstance(idx, list) + and len([i for i in idx if i >= HARDENED_INDEX]) > 0 + ): + return True + return False + + @classmethod + def from_string(cls, der: str, allow_hardened=False, allow_set=True): + if len(der) == 0: + return None + indexes = [ + cls.parse_element(d, allow_hardened, allow_set) for d in der.split("/") + ] + return cls(indexes) + + @classmethod + def parse_element(cls, d: str, allow_hardened=False, allow_set=True): + # wildcard + if d == "*": + return None + # branch set - legacy `{m,n}` + if d[0] == "{" and d[-1] == "}": + if not allow_set: + raise ArgumentError("Set is not allowed in derivation %s" % d) + return [ + cls.parse_element(dd, allow_hardened, allow_set=False) + for dd in d[1:-1].split(",") + ] + # branch set - multipart `` + if d[0] == "<" and d[-1] == ">": + if not allow_set: + raise ArgumentError("Set is not allowed in derivation %s" % d) + return [ + cls.parse_element(dd, allow_hardened, allow_set=False) + for dd in d[1:-1].split(";") + ] + idx = 0 + if d[-1] in ["h", "H", "'"]: + if not allow_hardened: + raise ArgumentError("Hardened derivation is not allowed in %s" % d) + idx = HARDENED_INDEX + d = d[:-1] + i = int(d) + if i < 0 or i >= HARDENED_INDEX: + raise ArgumentError( + "Derivation index can be in a range [0, %d)" % HARDENED_INDEX + ) + return idx + i + + def __str__(self): + r = "" + for idx in self.indexes: + if idx is None: + r += "/*" + if isinstance(idx, int): + if idx >= HARDENED_INDEX: + r += "/%dh" % (idx - HARDENED_INDEX) + else: + r += "/%d" % idx + if isinstance(idx, list): + r += "/<" + r += ";".join( + [ + str(i) if i < HARDENED_INDEX else str(i - HARDENED_INDEX) + "h" + for i in idx + ] + ) + r += ">" + return r + + +class Key(DescriptorBase): + def __init__( + self, + key, + origin=None, + derivation=None, + taproot=False, + xonly_repr=False, + ): + self.origin = origin + self.key = key + self.taproot = taproot + self.xonly_repr = xonly_repr and taproot + if not hasattr(key, "derive") and derivation: + raise ArgumentError("Key %s doesn't support derivation" % key) + self.allowed_derivation = derivation + + def __len__(self): + return 34 - int(self.taproot) # <33:sec> or <32:xonly> + + @property + def my_fingerprint(self): + if self.is_extended: + return self.key.my_fingerprint + return None + + @property + def fingerprint(self): + if self.origin is not None: + return self.origin.fingerprint + else: + if self.is_extended: + return self.key.my_fingerprint + return None + + @property + def derivation(self): + return [] if self.origin is None else self.origin.derivation + + @classmethod + def read_from(cls, s, taproot: bool = False): + """ + Reads key argument from stream. + If taproot is set to True - allows both x-only and sec pubkeys. + If taproot is False - will raise when finds xonly pubkey. + """ + first = s.read(1) + origin = None + if first == b"[": + prefix, char = read_until(s, b"]") + if char != b"]": + raise ArgumentError("Invalid key - missing ]") + origin = KeyOrigin.from_string(prefix.decode()) + else: + s.seek(-1, 1) + k, char = read_until(s, b",)/") + der = b"" + # there is a following derivation + if char == b"/": + der, char = read_until(s, b"<{,)") + # legacy branches: {a,b,c...} + if char == b"{": + der += b"{" + branch, char = read_until(s, b"}") + if char is None: + raise ArgumentError("Failed reading the key, missing }") + der += branch + b"}" + rest, char = read_until(s, b",)") + der += rest + # multipart descriptor: + elif char == b"<": + der += b"<" + branch, char = read_until(s, b">") + if char is None: + raise ArgumentError("Failed reading the key, missing >") + der += branch + b">" + rest, char = read_until(s, b",)") + der += rest + if char is not None: + s.seek(-1, 1) + # parse key + k, xonly_repr = cls.parse_key(k, taproot) + # parse derivation + allow_hardened = isinstance(k, bip32.HDKey) and isinstance(k.key, ec.PrivateKey) + derivation = AllowedDerivation.from_string( + der.decode(), allow_hardened=allow_hardened + ) + return cls(k, origin, derivation, taproot, xonly_repr) + + @classmethod + def parse_key(cls, key: bytes, taproot: bool = False): + # convert to string + k = key.decode() + if len(k) in [66, 130] and k[:2] in ["02", "03", "04"]: + # bare public key + return ec.PublicKey.parse(unhexlify(k)), False + elif taproot and len(k) == 64: + # x-only pubkey + return ec.PublicKey.parse(b"\x02" + unhexlify(k)), True + elif k[1:4] in ["pub", "prv"]: + # bip32 key + return bip32.HDKey.from_base58(k), False + else: + return ec.PrivateKey.from_wif(k), False + + @property + def is_extended(self): + return isinstance(self.key, bip32.HDKey) + + def check_derivation(self, derivation_path): + rest = None + # full derivation path + if self.fingerprint == derivation_path.fingerprint: + origin = self.derivation + if origin == derivation_path.derivation[: len(origin)]: + rest = derivation_path.derivation[len(origin) :] + # short derivation path + if self.my_fingerprint == derivation_path.fingerprint: + rest = derivation_path.derivation + if self.allowed_derivation is None or rest is None: + return None + return self.allowed_derivation.check_derivation(rest) + + def get_public_key(self): + return ( + self.key.get_public_key() + if (self.is_extended or self.is_private) + else self.key + ) + + def sec(self): + return self.key.sec() + + def xonly(self): + return self.key.xonly() + + def taproot_tweak(self, h=b""): + assert self.taproot + return self.key.taproot_tweak(h) + + def serialize(self): + if self.taproot: + return self.sec()[1:33] + return self.sec() + + def compile(self): + d = self.serialize() + return compact.to_bytes(len(d)) + d + + @property + def prefix(self): + if self.origin: + return "[%s]" % self.origin + return "" + + @property + def suffix(self): + return "" if self.allowed_derivation is None else str(self.allowed_derivation) + + @property + def can_derive(self): + return self.allowed_derivation is not None and hasattr(self.key, "derive") + + @property + def branches(self): + return self.allowed_derivation.branches if self.allowed_derivation else None + + @property + def num_branches(self): + return 1 if self.branches is None else len(self.branches) + + def branch(self, branch_index=None): + der = ( + self.allowed_derivation.branch(branch_index) + if self.allowed_derivation is not None + else None + ) + return type(self)(self.key, self.origin, der, self.taproot) + + @property + def is_wildcard(self): + return self.allowed_derivation.is_wildcard if self.allowed_derivation else False + + def derive(self, idx, branch_index=None): + # nothing to derive + if self.allowed_derivation is None: + return self + der = self.allowed_derivation.fill(idx, branch_index=branch_index) + k = self.key.derive(der) + if self.origin: + origin = KeyOrigin(self.origin.fingerprint, self.origin.derivation + der) + else: + origin = KeyOrigin(self.key.child(0).fingerprint, der) + # empty derivation + derivation = None + return type(self)(k, origin, derivation, self.taproot) + + @property + def is_private(self): + return isinstance(self.key, ec.PrivateKey) or ( + self.is_extended and self.key.is_private + ) + + def to_public(self): + if not self.is_private: + return self + if isinstance(self.key, ec.PrivateKey): + return type(self)( + self.key.get_public_key(), + self.origin, + self.allowed_derivation, + self.taproot, + ) + else: + return type(self)( + self.key.to_public(), self.origin, self.allowed_derivation, self.taproot + ) + + @property + def private_key(self): + if not self.is_private: + raise ArgumentError("Key is not private") + # either HDKey.key or just the key + return self.key.key if self.is_extended else self.key + + @property + def secret(self): + return self.private_key.secret + + def to_string(self, version=None): + if isinstance(self.key, ec.PublicKey): + k = self.key.sec() if not self.xonly_repr else self.key.xonly() + return self.prefix + hexlify(k).decode() + if isinstance(self.key, bip32.HDKey): + return self.prefix + self.key.to_base58(version) + self.suffix + if isinstance(self.key, ec.PrivateKey): + return self.prefix + self.key.wif() + return self.prefix + self.key + + @classmethod + def from_string(cls, s, taproot=False): + return cls.parse(s.encode(), taproot) + + +class KeyHash(Key): + @classmethod + def parse_key(cls, k: bytes, *args, **kwargs): + # convert to string + kd = k.decode() + # raw 20-byte hash + if len(kd) == 40: + return kd, False + return super().parse_key(k, *args, **kwargs) + + def serialize(self, *args, **kwargs): + if isinstance(self.key, str): + return unhexlify(self.key) + # TODO: should it be xonly? + if self.taproot: + return hashes.hash160(self.key.sec()[1:33]) + return hashes.hash160(self.key.sec()) + + def __len__(self): + return 21 # <20:pkh> + + def compile(self): + d = self.serialize() + return compact.to_bytes(len(d)) + d + + +class Number(DescriptorBase): + def __init__(self, num): + self.num = num + + @classmethod + def read_from(cls, s, taproot=False): + num = 0 + char = s.read(1) + while char in b"0123456789": + num = 10 * num + int(char.decode()) + char = s.read(1) + s.seek(-1, 1) + return cls(num) + + def compile(self): + if self.num == 0: + return b"\x00" + if self.num <= 16: + return bytes([80 + self.num]) + b = self.num.to_bytes(32, "little").rstrip(b"\x00") + if b[-1] >= 128: + b += b"\x00" + return bytes([len(b)]) + b + + def __len__(self): + return len(self.compile()) + + def __str__(self): + return "%d" % self.num + + +class Raw(DescriptorBase): + LEN = 32 + + def __init__(self, raw): + if len(raw) != self.LEN * 2: + raise ArgumentError("Invalid raw element length: %d" % len(raw)) + self.raw = unhexlify(raw) + + @classmethod + def read_from(cls, s, taproot=False): + return cls(s.read(2 * cls.LEN).decode()) + + def __str__(self): + return hexlify(self.raw).decode() + + def compile(self): + return compact.to_bytes(len(self.raw)) + self.raw + + def __len__(self): + return len(compact.to_bytes(self.LEN)) + self.LEN + + +class Raw32(Raw): + LEN = 32 + + def __len__(self): + return 33 + + +class Raw20(Raw): + LEN = 20 + + def __len__(self): + return 21 diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/base.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/base.py new file mode 100644 index 000000000..0c203b21b --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/base.py @@ -0,0 +1,22 @@ +from io import BytesIO +from ..base import EmbitBase + + +class DescriptorBase(EmbitBase): + """ + Descriptor is purely text-based, so parse/serialize do + the same as from/to_string, just returning ascii bytes + instead of ascii string. + """ + + @classmethod + def from_string(cls, s: str, *args, **kwargs): + return cls.parse(s.encode(), *args, **kwargs) + + def serialize(self, *args, **kwargs) -> bytes: + stream = BytesIO() + self.write_to(stream) + return stream.getvalue() + + def to_string(self, *args, **kwargs) -> str: + return self.serialize().decode() diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/checksum.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/checksum.py new file mode 100644 index 000000000..1e487ea51 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/checksum.py @@ -0,0 +1,56 @@ +from .errors import DescriptorError + + +def polymod(c: int, val: int) -> int: + c0 = c >> 35 + c = ((c & 0x7FFFFFFFF) << 5) ^ val + if c0 & 1: + c ^= 0xF5DEE51989 + if c0 & 2: + c ^= 0xA9FDCA3312 + if c0 & 4: + c ^= 0x1BAB10E32D + if c0 & 8: + c ^= 0x3706B1677A + if c0 & 16: + c ^= 0x644D626FFD + return c + + +def checksum(desc: str) -> str: + """Calculate checksum of desciptor string""" + INPUT_CHARSET = ( + "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVW" + 'XYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#"\\ ' + ) + CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + + c = 1 + cls = 0 + clscount = 0 + for ch in desc: + pos = INPUT_CHARSET.find(ch) + if pos == -1: + raise DescriptorError("Invalid character '%s' in the input string" % ch) + c = polymod(c, pos & 31) + cls = cls * 3 + (pos >> 5) + clscount += 1 + if clscount == 3: + c = polymod(c, cls) + cls = 0 + clscount = 0 + if clscount > 0: + c = polymod(c, cls) + for j in range(0, 8): + c = polymod(c, 0) + c ^= 1 + + ret = [CHECKSUM_CHARSET[(c >> (5 * (7 - j))) & 31] for j in range(0, 8)] + return "".join(ret) + + +def add_checksum(desc: str) -> str: + """Add checksum to descriptor string""" + if "#" in desc: + desc = desc.split("#")[0] + return desc + "#" + checksum(desc) diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/descriptor.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/descriptor.py new file mode 100644 index 000000000..9f585dc5e --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/descriptor.py @@ -0,0 +1,384 @@ +from io import BytesIO +from .. import script +from ..networks import NETWORKS +from .errors import DescriptorError +from .base import DescriptorBase +from .miniscript import Miniscript, Multi, Sortedmulti +from .arguments import Key +from .taptree import TapTree + + +class Descriptor(DescriptorBase): + def __init__( + self, + miniscript=None, + sh=False, + wsh=True, + key=None, + wpkh=True, + taproot=False, + taptree=None, + ): + # TODO: add support for taproot scripts + # Should: + # - accept taptree without a key + # - accept key without taptree + # - raise if miniscript is not None, but taproot=True + # - raise if taptree is not None, but taproot=False + if key is None and miniscript is None and taptree is None: + raise DescriptorError("Provide a key, miniscript or taptree") + if miniscript is not None: + # will raise if can't verify + miniscript.verify() + if miniscript.type != "B": + raise DescriptorError("Top level miniscript should be 'B'") + # check all branches have the same length + branches = { + len(k.branches) for k in miniscript.keys if k.branches is not None + } + if len(branches) > 1: + raise DescriptorError("All branches should have the same length") + self.sh = sh + self.wsh = wsh + self.key = key + self.miniscript = miniscript + self.wpkh = wpkh + self.taproot = taproot + self.taptree = taptree or TapTree() + # make sure all keys are either taproot or not + for k in self.keys: + k.taproot = taproot + + @property + def script_len(self): + if self.taproot: + return 34 # OP_1 <32:xonly> + if self.miniscript: + return len(self.miniscript) + if self.wpkh: + return 22 # 00 <20:pkh> + return 25 # OP_DUP OP_HASH160 <20:pkh> OP_EQUALVERIFY OP_CHECKSIG + + @property + def num_branches(self): + return max([k.num_branches for k in self.keys]) + + def branch(self, branch_index=None): + if self.miniscript: + return type(self)( + self.miniscript.branch(branch_index), + self.sh, + self.wsh, + None, + self.wpkh, + self.taproot, + ) + else: + return type(self)( + None, + self.sh, + self.wsh, + self.key.branch(branch_index), + self.wpkh, + self.taproot, + self.taptree.branch(branch_index), + ) + + @property + def is_wildcard(self): + return any([key.is_wildcard for key in self.keys]) + + @property + def is_wrapped(self): + return self.sh and self.is_segwit + + @property + def is_legacy(self): + return not (self.is_segwit or self.is_taproot) + + @property + def is_segwit(self): + return ( + (self.wsh and self.miniscript) or (self.wpkh and self.key) or self.taproot + ) + + @property + def is_pkh(self): + return self.key is not None and not self.taproot + + @property + def is_taproot(self): + return self.taproot + + @property + def is_basic_multisig(self) -> bool: + # TODO: should be true for taproot basic multisig with NUMS as internal key + # Sortedmulti is subclass of Multi + return bool(self.miniscript and isinstance(self.miniscript, Multi)) + + @property + def is_sorted(self) -> bool: + return bool(self.is_basic_multisig and isinstance(self.miniscript, Sortedmulti)) + + def scriptpubkey_type(self): + if self.is_taproot: + return "p2tr" + if self.sh: + return "p2sh" + if self.is_pkh: + if self.is_legacy: + return "p2pkh" + if self.is_segwit: + return "p2wpkh" + else: + return "p2wsh" + + @property + def brief_policy(self): + if self.taptree: + return "taptree" + if self.key: + return "single key" + if self.is_basic_multisig: + return ( + str(self.miniscript.args[0]) + + " of " + + str(len(self.keys)) + + " multisig" + + (" (sorted)" if self.is_sorted else "") + ) + return "miniscript" + + @property + def full_policy(self): + if (self.key and not self.taptree) or self.is_basic_multisig: + return self.brief_policy + s = str(self.miniscript or self) + for i, k in enumerate(self.keys): + s = s.replace(str(k), chr(65 + i)) + return s + + def derive(self, idx, branch_index=None): + if self.miniscript: + return type(self)( + self.miniscript.derive(idx, branch_index), + self.sh, + self.wsh, + None, + self.wpkh, + self.taproot, + ) + else: + return type(self)( + None, + self.sh, + self.wsh, + self.key.derive(idx, branch_index), + self.wpkh, + self.taproot, + self.taptree.derive(idx, branch_index), + ) + + def to_public(self): + if self.miniscript: + return type(self)( + self.miniscript.to_public(), + self.sh, + self.wsh, + None, + self.wpkh, + self.taproot, + ) + else: + return type(self)( + None, + self.sh, + self.wsh, + self.key.to_public(), + self.wpkh, + self.taproot, + self.taptree.to_public(), + ) + + def owns(self, psbt_scope): + """Checks if psbt input or output belongs to this descriptor""" + # we can't check if we don't know script_pubkey + if psbt_scope.script_pubkey is None: + return False + # quick check of script_pubkey type + if psbt_scope.script_pubkey.script_type() != self.scriptpubkey_type(): + return False + for pub, der in psbt_scope.bip32_derivations.items(): + # check of the fingerprints + for k in self.keys: + if not k.is_extended: + continue + res = k.check_derivation(der) + if res: + idx, branch_idx = res + sc = self.derive(idx, branch_index=branch_idx).script_pubkey() + # if derivation is found but scriptpubkey doesn't match - fail + return sc == psbt_scope.script_pubkey + for pub, (leafs, der) in psbt_scope.taproot_bip32_derivations.items(): + # check of the fingerprints + for k in self.keys: + if not k.is_extended: + continue + res = k.check_derivation(der) + if res: + idx, branch_idx = res + sc = self.derive(idx, branch_index=branch_idx).script_pubkey() + # if derivation is found but scriptpubkey doesn't match - fail + return sc == psbt_scope.script_pubkey + return False + + def check_derivation(self, derivation_path): + for k in self.keys: + # returns a tuple branch_idx, idx + der = k.check_derivation(derivation_path) + if der is not None: + return der + return None + + def witness_script(self): + if self.wsh and self.miniscript is not None: + return script.Script(self.miniscript.compile()) + + def redeem_script(self): + if not self.sh: + return None + if self.miniscript: + if not self.wsh: + return script.Script(self.miniscript.compile()) + else: + return script.p2wsh(script.Script(self.miniscript.compile())) + else: + return script.p2wpkh(self.key) + + def script_pubkey(self): + # covers sh-wpkh, sh and sh-wsh + if self.taproot: + return script.p2tr(self.key, self.taptree) + if self.sh: + return script.p2sh(self.redeem_script()) + if self.wsh: + return script.p2wsh(self.witness_script()) + if self.miniscript: + return script.Script(self.miniscript.compile()) + if self.wpkh: + return script.p2wpkh(self.key) + return script.p2pkh(self.key) + + def address(self, network=NETWORKS["main"]): + return self.script_pubkey().address(network) + + @property + def keys(self): + if self.taptree and self.key: + return [self.key] + self.taptree.keys + elif self.taptree: + return self.taptree.keys + elif self.key: + return [self.key] + return self.miniscript.keys + + @classmethod + def from_string(cls, desc): + s = BytesIO(desc.encode()) + res = cls.read_from(s) + left = s.read() + if len(left) > 0 and not left.startswith(b"#"): + raise DescriptorError("Unexpected characters after descriptor: %r" % left) + return res + + @classmethod + def read_from(cls, s): + # starts with sh(wsh()), sh() or wsh() + start = s.read(7) + sh = False + wsh = False + wpkh = False + is_miniscript = True + taproot = False + taptree = TapTree() + if start.startswith(b"tr("): + taproot = True + s.seek(-4, 1) + elif start.startswith(b"sh(wsh("): + sh = True + wsh = True + elif start.startswith(b"wsh("): + sh = False + wsh = True + s.seek(-3, 1) + elif start.startswith(b"sh(wpkh"): + is_miniscript = False + sh = True + wpkh = True + assert s.read(1) == b"(" + elif start.startswith(b"wpkh("): + is_miniscript = False + wpkh = True + s.seek(-2, 1) + elif start.startswith(b"pkh("): + is_miniscript = False + s.seek(-3, 1) + elif start.startswith(b"sh("): + sh = True + wsh = False + s.seek(-4, 1) + else: + raise ValueError("Invalid descriptor (starts with '%s')" % start.decode()) + # taproot always has a key, and may have taptree miniscript + if taproot: + miniscript = None + key = Key.read_from(s, taproot=True) + nbrackets = 1 + c = s.read(1) + # TODO: should it be ok to pass just taptree without a key? + # check if we have taptree after the key + if c != b",": + s.seek(-1, 1) + else: + taptree = TapTree.read_from(s) + elif is_miniscript: + miniscript = Miniscript.read_from(s) + key = None + nbrackets = int(sh) + int(wsh) + # single key for sure + else: + miniscript = None + key = Key.read_from(s, taproot=taproot) + nbrackets = 1 + int(sh) + end = s.read(nbrackets) + if end != b")" * nbrackets: + raise ValueError( + "Invalid descriptor (expected ')' but ends with '%s')" % end.decode() + ) + return cls( + miniscript, + sh=sh, + wsh=wsh, + key=key, + wpkh=wpkh, + taproot=taproot, + taptree=taptree, + ) + + def to_string(self): + if self.taproot: + if self.taptree: + return "tr(%s,%s)" % (self.key, self.taptree) + return "tr(%s)" % self.key + if self.miniscript is not None: + res = str(self.miniscript) + if self.wsh: + res = "wsh(%s)" % res + else: + if self.wpkh: + res = "wpkh(%s)" % self.key + else: + res = "pkh(%s)" % self.key + if self.sh: + res = "sh(%s)" % res + return res diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/errors.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/errors.py new file mode 100644 index 000000000..b125f9f6c --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/errors.py @@ -0,0 +1,17 @@ +from ..base import EmbitError + + +class DescriptorError(EmbitError): + pass + + +class MiniscriptError(DescriptorError): + pass + + +class ArgumentError(MiniscriptError): + pass + + +class KeyError(ArgumentError): + pass diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/miniscript.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/miniscript.py new file mode 100644 index 000000000..397317f52 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/miniscript.py @@ -0,0 +1,1070 @@ +from ..misc import read_until +from .errors import MiniscriptError +from .base import DescriptorBase +from .arguments import Key, KeyHash, Number, Raw32, Raw20 + + +class Miniscript(DescriptorBase): + def __init__(self, *args, **kwargs): + self.args = args + self.taproot = kwargs.get("taproot", False) + + def compile(self): + return self.inner_compile() + + def verify(self): + for arg in self.args: + if isinstance(arg, Miniscript): + arg.verify() + + @property + def keys(self): + return sum( + [arg.keys for arg in self.args if isinstance(arg, Miniscript)], + [k for k in self.args if isinstance(k, Key) or isinstance(k, KeyHash)], + ) + + def derive(self, idx, branch_index=None): + args = [ + arg.derive(idx, branch_index) if hasattr(arg, "derive") else arg + for arg in self.args + ] + return type(self)(*args, taproot=self.taproot) + + def to_public(self): + args = [ + arg.to_public() if hasattr(arg, "to_public") else arg for arg in self.args + ] + return type(self)(*args, taproot=self.taproot) + + def branch(self, branch_index): + args = [ + arg.branch(branch_index) if hasattr(arg, "branch") else arg + for arg in self.args + ] + return type(self)(*args, taproot=self.taproot) + + @property + def properties(self): + return self.PROPS + + @property + def type(self): + return self.TYPE + + @classmethod + def read_from(cls, s, taproot=False): + op, char = read_until(s, b"(,)") + op = op.decode() + wrappers = "" + if ":" in op: + wrappers, op = op.split(":") + if op not in OPERATOR_NAMES: + raise MiniscriptError("Unknown operator '%s'" % op) + # number of arguments, classes of args, compile fn, type, validity checker + MiniscriptCls = OPERATORS[OPERATOR_NAMES.index(op)] + if MiniscriptCls.NARGS != 0 and char != b"(": + raise MiniscriptError("Missing operator") + + if MiniscriptCls.NARGS is None or MiniscriptCls.NARGS > 0: + args = MiniscriptCls.read_arguments(s, taproot=taproot) + else: + s.seek(-1, 1) + args = [] + miniscript = MiniscriptCls(*args, taproot=taproot) + for w in reversed(wrappers): + if w not in WRAPPER_NAMES: + raise MiniscriptError("Unknown wrapper") + WrapperCls = WRAPPERS[WRAPPER_NAMES.index(w)] + miniscript = WrapperCls(miniscript, taproot=taproot) + return miniscript + + @classmethod + def read_arguments(cls, s, taproot=False): + args = [] + if cls.NARGS is None: + if type(cls.ARGCLS) == tuple: + firstcls, nextcls = cls.ARGCLS + else: + firstcls, nextcls = cls.ARGCLS, cls.ARGCLS + args.append(firstcls.read_from(s, taproot=taproot)) + while True: + char = s.read(1) + if char == b",": + args.append(nextcls.read_from(s, taproot=taproot)) + elif char == b")": + break + else: + raise MiniscriptError( + "Expected , or ), got: %s" % (char + s.read()) + ) + else: + for i in range(cls.NARGS): + args.append(cls.ARGCLS.read_from(s, taproot=taproot)) + if i < cls.NARGS - 1: + char = s.read(1) + if char != b",": + raise MiniscriptError("Missing arguments, %s" % char) + char = s.read(1) + if char != b")": + raise MiniscriptError("Expected ) got %s" % (char + s.read())) + return args + + def __str__(self): + return type(self).NAME + "(" + ",".join([str(arg) for arg in self.args]) + ")" + + def __len__(self): + """Length of the compiled script, override this if you know the length""" + return len(self.compile()) + + def len_args(self): + return sum([len(arg) for arg in self.args]) + + +########### Known fragments (miniscript operators) ############## + + +class OneArg(Miniscript): + NARGS = 1 + + # small handy functions + @property + def arg(self): + return self.args[0] + + @property + def carg(self): + return self.arg.compile() + + +class NumberZero(Miniscript): + # 0 + + NARGS = 0 + NAME = "0" + TYPE = "B" + PROPS = "zud" + + def inner_compile(self): + return b"\x00" + + def __len__(self): + return 1 + + +class NumberOne(Miniscript): + # 1 + + NARGS = 0 + NAME = "1" + TYPE = "B" + PROPS = "zu" + + def inner_compile(self): + return b"\x51" + + def __len__(self): + return 1 + + +class PkK(OneArg): + # + NAME = "pk_k" + ARGCLS = Key + TYPE = "K" + PROPS = "ondu" + + def inner_compile(self): + return self.carg + + def __len__(self): + return self.len_args() + + +class PkH(OneArg): + # DUP HASH160 EQUALVERIFY + NAME = "pk_h" + ARGCLS = KeyHash + TYPE = "K" + PROPS = "ndu" + + def inner_compile(self): + return b"\x76\xa9" + self.carg + b"\x88" + + def __len__(self): + return self.len_args() + 3 + + +class Older(OneArg): + # CHECKSEQUENCEVERIFY + NAME = "older" + ARGCLS = Number + TYPE = "B" + PROPS = "z" + + def inner_compile(self): + return self.carg + b"\xb2" + + def verify(self): + super().verify() + if (self.arg.num < 1) or (self.arg.num >= 0x80000000): + raise MiniscriptError( + "%s should have an argument in range [1, 0x80000000)" % self.NAME + ) + + def __len__(self): + return self.len_args() + 1 + + +class After(Older): + # CHECKLOCKTIMEVERIFY + NAME = "after" + + def inner_compile(self): + return self.carg + b"\xb1" + + +class Sha256(OneArg): + # SIZE <32> EQUALVERIFY SHA256 EQUAL + NAME = "sha256" + ARGCLS = Raw32 + TYPE = "B" + PROPS = "ondu" + + def inner_compile(self): + return b"\x82" + Number(32).compile() + b"\x88\xa8" + self.carg + b"\x87" + + def __len__(self): + return self.len_args() + 6 + + +class Hash256(Sha256): + # SIZE <32> EQUALVERIFY HASH256 EQUAL + NAME = "hash256" + + def inner_compile(self): + return b"\x82" + Number(32).compile() + b"\x88\xaa" + self.carg + b"\x87" + + +class Ripemd160(Sha256): + # SIZE <32> EQUALVERIFY RIPEMD160 EQUAL + NAME = "ripemd160" + ARGCLS = Raw20 + + def inner_compile(self): + return b"\x82" + Number(32).compile() + b"\x88\xa6" + self.carg + b"\x87" + + +class Hash160(Ripemd160): + # SIZE <32> EQUALVERIFY HASH160 EQUAL + NAME = "hash160" + + def inner_compile(self): + return b"\x82" + Number(32).compile() + b"\x88\xa9" + self.carg + b"\x87" + + +class AndOr(Miniscript): + # [X] NOTIF [Z] ELSE [Y] ENDIF + NAME = "andor" + NARGS = 3 + ARGCLS = Miniscript + + @property + def type(self): + # same as Y/Z + return self.args[1].type + + def verify(self): + # requires: X is Bdu; Y and Z are both B, K, or V + super().verify() + if self.args[0].type != "B": + raise MiniscriptError("andor: X should be 'B'") + px = self.args[0].properties + if "d" not in px and "u" not in px: + raise MiniscriptError("andor: X should be 'du'") + if self.args[1].type != self.args[2].type: + raise MiniscriptError("andor: Y and Z should have the same types") + if self.args[1].type not in "BKV": + raise MiniscriptError("andor: Y and Z should be B K or V") + + @property + def properties(self): + # props: z=zXzYzZ; o=zXoYoZ or oXzYzZ; u=uYuZ; d=dZ + props = "" + px, py, pz = [arg.properties for arg in self.args] + if "z" in px and "z" in py and "z" in pz: + props += "z" + if ("z" in px and "o" in py and "o" in pz) or ( + "o" in px and "z" in py and "z" in pz + ): + props += "o" + if "u" in py and "u" in pz: + props += "u" + if "d" in pz: + props += "d" + return props + + def inner_compile(self): + return ( + self.args[0].compile() + + b"\x64" + + self.args[2].compile() + + b"\x67" + + self.args[1].compile() + + b"\x68" + ) + + def __len__(self): + return self.len_args() + 3 + + +class AndV(Miniscript): + # [X] [Y] + NAME = "and_v" + NARGS = 2 + ARGCLS = Miniscript + + def inner_compile(self): + return self.args[0].compile() + self.args[1].compile() + + def __len__(self): + return self.len_args() + + def verify(self): + # X is V; Y is B, K, or V + super().verify() + if self.args[0].type != "V": + raise MiniscriptError("and_v: X should be 'V'") + if self.args[1].type not in "BKV": + raise MiniscriptError("and_v: Y should be B K or V") + + @property + def type(self): + # same as Y + return self.args[1].type + + @property + def properties(self): + # z=zXzY; o=zXoY or zYoX; n=nX or zXnY; u=uY + px, py = [arg.properties for arg in self.args] + props = "" + if "z" in px and "z" in py: + props += "z" + if ("z" in px and "o" in py) or ("z" in py and "o" in px): + props += "o" + if "n" in px or ("z" in px and "n" in py): + props += "n" + if "u" in py: + props += "u" + return props + + +class AndB(Miniscript): + # [X] [Y] BOOLAND + NAME = "and_b" + NARGS = 2 + ARGCLS = Miniscript + TYPE = "B" + + def inner_compile(self): + return self.args[0].compile() + self.args[1].compile() + b"\x9a" + + def __len__(self): + return self.len_args() + 1 + + def verify(self): + # X is B; Y is W + super().verify() + if self.args[0].type != "B": + raise MiniscriptError("and_b: X should be B") + if self.args[1].type != "W": + raise MiniscriptError("and_b: Y should be W") + + @property + def properties(self): + # z=zXzY; o=zXoY or zYoX; n=nX or zXnY; d=dXdY; u + px, py = [arg.properties for arg in self.args] + props = "" + if "z" in px and "z" in py: + props += "z" + if ("z" in px and "o" in py) or ("z" in py and "o" in px): + props += "o" + if "n" in px or ("z" in px and "n" in py): + props += "n" + if "d" in px and "d" in py: + props += "d" + props += "u" + return props + + +class AndN(Miniscript): + # [X] NOTIF 0 ELSE [Y] ENDIF + # andor(X,Y,0) + NAME = "and_n" + NARGS = 2 + ARGCLS = Miniscript + + def inner_compile(self): + return ( + self.args[0].compile() + + b"\x64" + + Number(0).compile() + + b"\x67" + + self.args[1].compile() + + b"\x68" + ) + + def __len__(self): + return self.len_args() + 4 + + @property + def type(self): + # same as Y/Z + return self.args[1].type + + def verify(self): + # requires: X is Bdu; Y and Z are both B, K, or V + super().verify() + if self.args[0].type != "B": + raise MiniscriptError("and_n: X should be 'B'") + px = self.args[0].properties + if "d" not in px and "u" not in px: + raise MiniscriptError("and_n: X should be 'du'") + if self.args[1].type != "B": + raise MiniscriptError("and_n: Y should be B") + + @property + def properties(self): + # props: z=zXzYzZ; o=zXoYoZ or oXzYzZ; u=uYuZ; d=dZ + props = "" + px, py = [arg.properties for arg in self.args] + pz = "zud" + if "z" in px and "z" in py and "z" in pz: + props += "z" + if ("z" in px and "o" in py and "o" in pz) or ( + "o" in px and "z" in py and "z" in pz + ): + props += "o" + if "u" in py and "u" in pz: + props += "u" + if "d" in pz: + props += "d" + return props + + +class OrB(Miniscript): + # [X] [Z] BOOLOR + NAME = "or_b" + NARGS = 2 + ARGCLS = Miniscript + TYPE = "B" + + def inner_compile(self): + return self.args[0].compile() + self.args[1].compile() + b"\x9b" + + def __len__(self): + return self.len_args() + 1 + + def verify(self): + # X is Bd; Z is Wd + super().verify() + if self.args[0].type != "B": + raise MiniscriptError("or_b: X should be B") + if "d" not in self.args[0].properties: + raise MiniscriptError("or_b: X should be d") + if self.args[1].type != "W": + raise MiniscriptError("or_b: Z should be W") + if "d" not in self.args[1].properties: + raise MiniscriptError("or_b: Z should be d") + + @property + def properties(self): + # z=zXzZ; o=zXoZ or zZoX; d; u + props = "" + px, pz = [arg.properties for arg in self.args] + if "z" in px and "z" in pz: + props += "z" + if ("z" in px and "o" in pz) or ("z" in pz and "o" in px): + props += "o" + props += "du" + return props + + +class OrC(Miniscript): + # [X] NOTIF [Z] ENDIF + NAME = "or_c" + NARGS = 2 + ARGCLS = Miniscript + TYPE = "V" + + def inner_compile(self): + return self.args[0].compile() + b"\x64" + self.args[1].compile() + b"\x68" + + def __len__(self): + return self.len_args() + 2 + + def verify(self): + # X is Bdu; Z is V + super().verify() + if self.args[0].type != "B": + raise MiniscriptError("or_c: X should be B") + if self.args[1].type != "V": + raise MiniscriptError("or_c: Z should be V") + px = self.args[0].properties + if "d" not in px or "u" not in px: + raise MiniscriptError("or_c: X should be du") + + @property + def properties(self): + # z=zXzZ; o=oXzZ + props = "" + px, pz = [arg.properties for arg in self.args] + if "z" in px and "z" in pz: + props += "z" + if "o" in px and "z" in pz: + props += "o" + return props + + +class OrD(Miniscript): + # [X] IFDUP NOTIF [Z] ENDIF + NAME = "or_d" + NARGS = 2 + ARGCLS = Miniscript + TYPE = "B" + + def inner_compile(self): + return self.args[0].compile() + b"\x73\x64" + self.args[1].compile() + b"\x68" + + def __len__(self): + return self.len_args() + 3 + + def verify(self): + # X is Bdu; Z is B + super().verify() + if self.args[0].type != "B": + raise MiniscriptError("or_d: X should be B") + if self.args[1].type != "B": + raise MiniscriptError("or_d: Z should be B") + px = self.args[0].properties + if "d" not in px or "u" not in px: + raise MiniscriptError("or_d: X should be du") + + @property + def properties(self): + # z=zXzZ; o=oXzZ; d=dZ; u=uZ + props = "" + px, pz = [arg.properties for arg in self.args] + if "z" in px and "z" in pz: + props += "z" + if "o" in px and "z" in pz: + props += "o" + if "d" in pz: + props += "d" + if "u" in pz: + props += "u" + return props + + +class OrI(Miniscript): + # IF [X] ELSE [Z] ENDIF + NAME = "or_i" + NARGS = 2 + ARGCLS = Miniscript + + def inner_compile(self): + return ( + b"\x63" + + self.args[0].compile() + + b"\x67" + + self.args[1].compile() + + b"\x68" + ) + + def __len__(self): + return self.len_args() + 3 + + def verify(self): + # both are B, K, or V + super().verify() + if self.args[0].type != self.args[1].type: + raise MiniscriptError("or_i: X and Z should be the same type") + if self.args[0].type not in "BKV": + raise MiniscriptError("or_i: X and Z should be B K or V") + + @property + def type(self): + return self.args[0].type + + @property + def properties(self): + # o=zXzZ; u=uXuZ; d=dX or dZ + props = "" + px, pz = [arg.properties for arg in self.args] + if "z" in px and "z" in pz: + props += "o" + if "u" in px and "u" in pz: + props += "u" + if "d" in px or "d" in pz: + props += "d" + return props + + +class Thresh(Miniscript): + # [X1] [X2] ADD ... [Xn] ADD ... EQUAL + NAME = "thresh" + NARGS = None + ARGCLS = (Number, Miniscript) + TYPE = "B" + + def inner_compile(self): + return ( + self.args[1].compile() + + b"".join([arg.compile() + b"\x93" for arg in self.args[2:]]) + + self.args[0].compile() + + b"\x87" + ) + + def __len__(self): + return self.len_args() + len(self.args) - 1 + + def verify(self): + # 1 <= k <= n; X1 is Bdu; others are Wdu + super().verify() + if self.args[0].num < 1 or self.args[0].num >= len(self.args): + raise MiniscriptError( + "thresh: Invalid k! Should be 1 <= k <= %d, got %d" + % (len(self.args) - 1, self.args[0].num) + ) + if self.args[1].type != "B": + raise MiniscriptError("thresh: X1 should be B") + px = self.args[1].properties + if "d" not in px or "u" not in px: + raise MiniscriptError("thresh: X1 should be du") + for i, arg in enumerate(self.args[2:]): + if arg.type != "W": + raise MiniscriptError("thresh: X%d should be W" % (i + 1)) + p = arg.properties + if "d" not in p or "u" not in p: + raise MiniscriptError("thresh: X%d should be du" % (i + 1)) + + @property + def properties(self): + # z=all are z; o=all are z except one is o; d; u + props = "" + parr = [arg.properties for arg in self.args[1:]] + zarr = ["z" for p in parr if "z" in p] + if len(zarr) == len(parr): + props += "z" + noz = [p for p in parr if "z" not in p] + if len(noz) == 1 and "o" in noz[0]: + props += "o" + props += "du" + return props + + +class Multi(Miniscript): + # ... CHECKMULTISIG + NAME = "multi" + NARGS = None + ARGCLS = (Number, Key) + TYPE = "B" + PROPS = "ndu" + _expected_taproot = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.taproot is not self._expected_taproot: + raise MiniscriptError( + "%s can't be used if taproot is %s" % (self.NAME, self.taproot) + ) + + def inner_compile(self): + return ( + b"".join([arg.compile() for arg in self.args]) + + Number(len(self.args) - 1).compile() + + b"\xae" + ) + + def __len__(self): + return self.len_args() + 2 + + def verify(self): + super().verify() + if self.args[0].num < 1 or self.args[0].num > (len(self.args) - 1): + raise MiniscriptError( + "multi: 1 <= k <= %d, got %d" % ((len(self.args) - 1), self.args[0].num) + ) + + +class Sortedmulti(Multi): + # ... CHECKMULTISIG + NAME = "sortedmulti" + + def inner_compile(self): + return ( + self.args[0].compile() + + b"".join(sorted([arg.compile() for arg in self.args[1:]])) + + Number(len(self.args) - 1).compile() + + b"\xae" + ) + + +class MultiA(Multi): + # CHECKSIG CHECKSIGADD ... CHECKSIGNADD NUMEQUAL + NAME = "multi_a" + _expected_taproot = True + + def inner_compile(self): + return ( + self.args[1].compile() + + b"\xac" + + b"".join([arg.compile() + b"\xba" for arg in self.args[2:]]) + + self.args[0].compile() + + b"\x9c" + ) + + def __len__(self): + return self.len_args() + len(self.args) + + +class SortedmultiA(MultiA): + # CHECKSIG CHECKSIGADD ... CHECKSIGNADD NUMEQUAL + NAME = "sortedmulti_a" + + def inner_compile(self): + keys = list(sorted([k.compile() for k in self.args[1:]])) + return ( + keys[0] + + b"\xac" + + b"".join([k + b"\xba" for k in keys[1:]]) + + self.args[0].compile() + + b"\x9c" + ) + + +class Pk(OneArg): + # CHECKSIG + NAME = "pk" + ARGCLS = Key + TYPE = "B" + PROPS = "ondu" + + def inner_compile(self): + return self.carg + b"\xac" + + def __len__(self): + return self.len_args() + 1 + + +class Pkh(OneArg): + # DUP HASH160 EQUALVERIFY CHECKSIG + NAME = "pkh" + ARGCLS = KeyHash + TYPE = "B" + PROPS = "ndu" + + def inner_compile(self): + return b"\x76\xa9" + self.carg + b"\x88\xac" + + def __len__(self): + return self.len_args() + 4 + + # TODO: 0, 1 - they are without brackets, so it should be different... + + +OPERATORS = [ + NumberZero, + NumberOne, + PkK, + PkH, + Older, + After, + Sha256, + Hash256, + Ripemd160, + Hash160, + AndOr, + AndV, + AndB, + AndN, + OrB, + OrC, + OrD, + OrI, + Thresh, + Multi, + Sortedmulti, + MultiA, + SortedmultiA, + Pk, + Pkh, +] +OPERATOR_NAMES = [cls.NAME for cls in OPERATORS] + + +class Wrapper(OneArg): + ARGCLS = Miniscript + + @property + def op(self): + return type(self).__name__.lower() + + def __str__(self): + # more wrappers follow + if isinstance(self.arg, Wrapper): + return self.op + str(self.arg) + # we are the last wrapper + return self.op + ":" + str(self.arg) + + +class A(Wrapper): + # TOALTSTACK [X] FROMALTSTACK + TYPE = "W" + + def inner_compile(self): + return b"\x6b" + self.carg + b"\x6c" + + def __len__(self): + return len(self.arg) + 2 + + def verify(self): + super().verify() + if self.arg.type != "B": + raise MiniscriptError("a: X should be B") + + @property + def properties(self): + props = "" + px = self.arg.properties + if "d" in px: + props += "d" + if "u" in px: + props += "u" + return props + + +class S(Wrapper): + # SWAP [X] + TYPE = "W" + + def inner_compile(self): + return b"\x7c" + self.carg + + def __len__(self): + return len(self.arg) + 1 + + def verify(self): + super().verify() + if self.arg.type != "B": + raise MiniscriptError("s: X should be B") + if "o" not in self.arg.properties: + raise MiniscriptError("s: X should be o") + + @property + def properties(self): + props = "" + px = self.arg.properties + if "d" in px: + props += "d" + if "u" in px: + props += "u" + return props + + +class C(Wrapper): + # [X] CHECKSIG + TYPE = "B" + + def inner_compile(self): + return self.carg + b"\xac" + + def __len__(self): + return len(self.arg) + 1 + + def verify(self): + super().verify() + if self.arg.type != "K": + raise MiniscriptError("c: X should be K") + + @property + def properties(self): + props = "" + px = self.arg.properties + for p in ["o", "n", "d"]: + if p in px: + props += p + props += "u" + return props + + +class T(Wrapper): + # [X] 1 + TYPE = "B" + + def inner_compile(self): + return self.carg + Number(1).compile() + + def __len__(self): + return len(self.arg) + 1 + + @property + def properties(self): + # z=zXzY; o=zXoY or zYoX; n=nX or zXnY; u=uY + px = self.arg.properties + py = "zu" + props = "" + if "z" in px and "z" in py: + props += "z" + if ("z" in px and "o" in py) or ("z" in py and "o" in px): + props += "o" + if "n" in px or ("z" in px and "n" in py): + props += "n" + if "u" in py: + props += "u" + return props + + +class D(Wrapper): + # DUP IF [X] ENDIF + TYPE = "B" + + def inner_compile(self): + return b"\x76\x63" + self.carg + b"\x68" + + def __len__(self): + return len(self.arg) + 3 + + def verify(self): + super().verify() + if self.arg.type != "V": + raise MiniscriptError("d: X should be V") + if "z" not in self.arg.properties: + raise MiniscriptError("d: X should be z") + + @property + def properties(self): + # https://github.com/bitcoin/bitcoin/pull/24906 + if self.taproot: + props = "ndu" + else: + props = "nd" + px = self.arg.properties + if "z" in px: + props += "o" + return props + + +class V(Wrapper): + # [X] VERIFY (or VERIFY version of last opcode in [X]) + TYPE = "V" + + def inner_compile(self): + """Checks last check code and makes it verify""" + if self.carg[-1] in [0xAC, 0xAE, 0x9C, 0x87]: + return self.carg[:-1] + bytes([self.carg[-1] + 1]) + return self.carg + b"\x69" + + def verify(self): + super().verify() + if self.arg.type != "B": + raise MiniscriptError("v: X should be B") + + @property + def properties(self): + props = "" + px = self.arg.properties + for p in ["z", "o", "n"]: + if p in px: + props += p + return props + + +class J(Wrapper): + # SIZE 0NOTEQUAL IF [X] ENDIF + TYPE = "B" + + def inner_compile(self): + return b"\x82\x92\x63" + self.carg + b"\x68" + + def verify(self): + super().verify() + if self.arg.type != "B": + raise MiniscriptError("j: X should be B") + if "n" not in self.arg.properties: + raise MiniscriptError("j: X should be n") + + @property + def properties(self): + props = "nd" + px = self.arg.properties + for p in ["o", "u"]: + if p in px: + props += p + return props + + +class N(Wrapper): + # [X] 0NOTEQUAL + TYPE = "B" + + def inner_compile(self): + return self.carg + b"\x92" + + def __len__(self): + return len(self.arg) + 1 + + def verify(self): + super().verify() + if self.arg.type != "B": + raise MiniscriptError("n: X should be B") + + @property + def properties(self): + props = "u" + px = self.arg.properties + for p in ["z", "o", "n", "d"]: + if p in px: + props += p + return props + + +class L(Wrapper): + # IF 0 ELSE [X] ENDIF + TYPE = "B" + + def inner_compile(self): + return b"\x63" + Number(0).compile() + b"\x67" + self.carg + b"\x68" + + def __len__(self): + return len(self.arg) + 4 + + def verify(self): + # both are B, K, or V + super().verify() + if self.arg.type != "B": + raise MiniscriptError("or_i: X and Z should be the same type") + + @property + def properties(self): + # o=zXzZ; u=uXuZ; d=dX or dZ + props = "d" + pz = self.arg.properties + if "z" in pz: + props += "o" + if "u" in pz: + props += "u" + return props + + +class U(L): + # IF [X] ELSE 0 ENDIF + def inner_compile(self): + return b"\x63" + self.carg + b"\x67" + Number(0).compile() + b"\x68" + + def __len__(self): + return len(self.arg) + 4 + + +WRAPPERS = [A, S, C, T, D, V, J, N, L, U] +WRAPPER_NAMES = [w.__name__.lower() for w in WRAPPERS] diff --git a/bitcoin_client/ledger_bitcoin/embit/descriptor/taptree.py b/bitcoin_client/ledger_bitcoin/embit/descriptor/taptree.py new file mode 100644 index 000000000..7f611e5ec --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/descriptor/taptree.py @@ -0,0 +1,151 @@ +from .errors import MiniscriptError +from .base import DescriptorBase +from .miniscript import Miniscript +from ..hashes import tagged_hash +from ..script import Script + + +class TapLeaf(DescriptorBase): + def __init__(self, miniscript=None, version=0xC0): + self.miniscript = miniscript + self.version = version + + def __str__(self): + return str(self.miniscript) + + @classmethod + def read_from(cls, s): + ms = Miniscript.read_from(s, taproot=True) + return cls(ms) + + def serialize(self): + if self.miniscript is None: + return b"" + return bytes([self.version]) + Script(self.miniscript.compile()).serialize() + + @property + def keys(self): + return self.miniscript.keys + + def derive(self, *args, **kwargs): + if self.miniscript is None: + return type(self)(None, version=self.version) + return type(self)( + self.miniscript.derive(*args, **kwargs), + self.version, + ) + + def branch(self, *args, **kwargs): + if self.miniscript is None: + return type(self)(None, version=self.version) + return type(self)( + self.miniscript.branch(*args, **kwargs), + self.version, + ) + + def to_public(self, *args, **kwargs): + if self.miniscript is None: + return type(self)(None, version=self.version) + return type(self)( + self.miniscript.to_public(*args, **kwargs), + self.version, + ) + + +def _tweak_helper(tree): + # https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs + if isinstance(tree, TapTree): + tree = tree.tree + if isinstance(tree, TapLeaf): + # one leaf on this branch + h = tagged_hash("TapLeaf", tree.serialize()) + return ([(tree, b"")], h) + left, left_h = _tweak_helper(tree[0]) + right, right_h = _tweak_helper(tree[1]) + ret = [(leaf, c + right_h) for leaf, c in left] + [ + (leaf, c + left_h) for leaf, c in right + ] + if right_h < left_h: + left_h, right_h = right_h, left_h + return (ret, tagged_hash("TapBranch", left_h + right_h)) + + +class TapTree(DescriptorBase): + def __init__(self, tree=None): + """tree can be None, TapLeaf or a tuple (taptree, taptree)""" + self.tree = tree + # make sure all keys are taproot + for k in self.keys: + k.taproot = True + + def __bool__(self): + return bool(self.tree) + + def tweak(self): + if self.tree is None: + return b"" + _, h = _tweak_helper(self.tree) + return h + + @property + def keys(self): + if self.tree is None: + return [] + if isinstance(self.tree, TapLeaf): + return self.tree.keys + left, right = self.tree + return left.keys + right.keys + + @classmethod + def read_from(cls, s): + c = s.read(1) + if len(c) == 0: + return cls() + if c == b"{": # more than one miniscript + left = cls.read_from(s) + c = s.read(1) + if c == b"}": + return left + if c != b",": + raise MiniscriptError("Invalid taptree syntax: expected ','") + right = cls.read_from(s) + if s.read(1) != b"}": + raise MiniscriptError("Invalid taptree syntax: expected '}'") + return cls((left, right)) + s.seek(-1, 1) + ms = TapLeaf.read_from(s) + return cls(ms) + + def derive(self, *args, **kwargs): + if self.tree is None: + return type(self)(None) + if isinstance(self.tree, TapLeaf): + return type(self)(self.tree.derive(*args, **kwargs)) + left, right = self.tree + return type(self)((left.derive(*args, **kwargs), right.derive(*args, **kwargs))) + + def branch(self, *args, **kwargs): + if self.tree is None: + return type(self)(None) + if isinstance(self.tree, TapLeaf): + return type(self)(self.tree.branch(*args, **kwargs)) + left, right = self.tree + return type(self)((left.branch(*args, **kwargs), right.branch(*args, **kwargs))) + + def to_public(self, *args, **kwargs): + if self.tree is None: + return type(self)(None) + if isinstance(self.tree, TapLeaf): + return type(self)(self.tree.to_public(*args, **kwargs)) + left, right = self.tree + return type(self)( + (left.to_public(*args, **kwargs), right.to_public(*args, **kwargs)) + ) + + def __str__(self): + if self.tree is None: + return "" + if isinstance(self.tree, TapLeaf): + return str(self.tree) + (left, right) = self.tree + return "{%s,%s}" % (left, right) diff --git a/bitcoin_client/ledger_bitcoin/embit/ec.py b/bitcoin_client/ledger_bitcoin/embit/ec.py new file mode 100644 index 000000000..a93fc7143 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/ec.py @@ -0,0 +1,263 @@ +from . import base58 +from . import hashes +from .misc import secp256k1 +from .networks import NETWORKS +from .base import EmbitBase, EmbitError, EmbitKey +from binascii import hexlify, unhexlify + + +class ECError(EmbitError): + pass + + +class Signature(EmbitBase): + def __init__(self, sig): + self._sig = sig + + def write_to(self, stream) -> int: + return stream.write(secp256k1.ecdsa_signature_serialize_der(self._sig)) + + @classmethod + def read_from(cls, stream): + der = stream.read(2) + der += stream.read(der[1]) + return cls(secp256k1.ecdsa_signature_parse_der(der)) + + +class SchnorrSig(EmbitBase): + def __init__(self, sig): + assert len(sig) == 64 + self._sig = sig + + def write_to(self, stream) -> int: + return stream.write(self._sig) + + @classmethod + def read_from(cls, stream): + return cls(stream.read(64)) + + +class PublicKey(EmbitKey): + def __init__(self, point: bytes, compressed: bool = True): + self._point = point + self.compressed = compressed + + @classmethod + def read_from(cls, stream): + b = stream.read(1) + if b not in [b"\x02", b"\x03", b"\x04"]: + raise ECError("Invalid public key") + if b == b"\x04": + b += stream.read(64) + else: + b += stream.read(32) + try: + point = secp256k1.ec_pubkey_parse(b) + except Exception as e: + raise ECError(str(e)) + compressed = b[0] != 0x04 + return cls(point, compressed) + + def sec(self) -> bytes: + """Sec representation of the key""" + flag = secp256k1.EC_COMPRESSED if self.compressed else secp256k1.EC_UNCOMPRESSED + return secp256k1.ec_pubkey_serialize(self._point, flag) + + def xonly(self) -> bytes: + return self.sec()[1:33] + + def taproot_tweak(self, h=b""): + """Returns a tweaked public key""" + x = self.xonly() + tweak = hashes.tagged_hash("TapTweak", x + h) + if not secp256k1.ec_seckey_verify(tweak): + raise EmbitError("Tweak is too large") + point = secp256k1.ec_pubkey_parse(b"\x02" + x) + pub = secp256k1.ec_pubkey_add(point, tweak) + sec = secp256k1.ec_pubkey_serialize(pub) + return PublicKey.from_xonly(sec[1:33]) + + def write_to(self, stream) -> int: + return stream.write(self.sec()) + + def serialize(self) -> bytes: + return self.sec() + + def verify(self, sig, msg_hash) -> bool: + return bool(secp256k1.ecdsa_verify(sig._sig, msg_hash, self._point)) + + def _xonly(self): + """Returns internal representation of the xonly-pubkey (64 bytes)""" + pub, _ = secp256k1.xonly_pubkey_from_pubkey(self._point) + return pub + + @classmethod + def from_xonly(cls, data: bytes): + assert len(data) == 32 + return cls.parse(b"\x02" + data) + + def schnorr_verify(self, sig, msg_hash) -> bool: + return bool(secp256k1.schnorrsig_verify(sig._sig, msg_hash, self._xonly())) + + @classmethod + def from_string(cls, s): + return cls.parse(unhexlify(s)) + + @property + def is_private(self) -> bool: + return False + + def to_string(self): + return hexlify(self.sec()).decode() + + def __lt__(self, other): + # for lexagraphic ordering + return self.sec() < other.sec() + + def __gt__(self, other): + # for lexagraphic ordering + return self.sec() > other.sec() + + def __eq__(self, other): + return self.sec() == other.sec() + + def __hash__(self): + return hash(self._point) + + +class PrivateKey(EmbitKey): + def __init__(self, secret, compressed: bool = True, network=NETWORKS["main"]): + """Creates a private key from 32-byte array""" + if len(secret) != 32: + raise ECError("Secret should be 32-byte array") + if not secp256k1.ec_seckey_verify(secret): + raise ECError("Secret is not valid (larger then N?)") + self.compressed = compressed + self._secret = secret + self.network = network + + def wif(self, network=None) -> str: + """Export private key as Wallet Import Format string. + Prefix 0x80 is used for mainnet, 0xEF for testnet. + This class doesn't store this information though. + """ + if network is None: + network = self.network + prefix = network["wif"] + b = prefix + self._secret + if self.compressed: + b += bytes([0x01]) + return base58.encode_check(b) + + @property + def secret(self): + return self._secret + + def sec(self) -> bytes: + """Sec representation of the corresponding public key""" + return self.get_public_key().sec() + + def xonly(self) -> bytes: + return self.sec()[1:] + + def taproot_tweak(self, h=b""): + """Returns a tweaked private key""" + sec = self.sec() + negate = sec[0] != 0x02 + x = sec[1:33] + tweak = hashes.tagged_hash("TapTweak", x + h) + if not secp256k1.ec_seckey_verify(tweak): + raise EmbitError("Tweak is too large") + if negate: + secret = secp256k1.ec_privkey_negate(self._secret) + else: + secret = self._secret + res = secp256k1.ec_privkey_add(secret, tweak) + pk = PrivateKey(res) + if pk.sec()[0] == 0x03: + pk = PrivateKey(secp256k1.ec_privkey_negate(res)) + return pk + + @classmethod + def from_wif(cls, s): + """Import private key from Wallet Import Format string.""" + b = base58.decode_check(s) + prefix = b[:1] + network = None + for net in NETWORKS: + if NETWORKS[net]["wif"] == prefix: + network = NETWORKS[net] + secret = b[1:33] + compressed = False + if len(b) not in [33, 34]: + raise ECError("Wrong WIF length") + if len(b) == 34: + if b[-1] == 0x01: + compressed = True + else: + raise ECError("Wrong WIF compressed flag") + return cls(secret, compressed, network) + + # to unify API + def to_base58(self, network=None) -> str: + return self.wif(network) + + @classmethod + def from_base58(cls, s): + return cls.from_wif(s) + + def get_public_key(self) -> PublicKey: + return PublicKey(secp256k1.ec_pubkey_create(self._secret), self.compressed) + + def to_public(self) -> PublicKey: + """Alias to get_public_key for API consistency""" + return self.get_public_key() + + def sign(self, msg_hash, grind=True) -> Signature: + sig = Signature(secp256k1.ecdsa_sign(msg_hash, self._secret)) + if grind: + counter = 1 + while len(sig.serialize()) > 70: + sig = Signature( + secp256k1.ecdsa_sign( + msg_hash, self._secret, None, counter.to_bytes(32, "little") + ) + ) + counter += 1 + # just in case we get in infinite loop for some reason + if counter > 200: + break + return sig + + def schnorr_sign(self, msg_hash) -> SchnorrSig: + return SchnorrSig(secp256k1.schnorrsig_sign(msg_hash, self._secret)) + + def verify(self, sig, msg_hash) -> bool: + return self.get_public_key().verify(sig, msg_hash) + + def schnorr_verify(self, sig, msg_hash) -> bool: + return self.get_public_key().schnorr_verify(sig, msg_hash) + + def write_to(self, stream) -> int: + # return a copy of the secret + return stream.write(self._secret) + + def ecdh(self, public_key: PublicKey, hashfn=None, data=None) -> bytes: + pubkey_point = secp256k1.ec_pubkey_parse(public_key.sec()) + return secp256k1.ecdh(pubkey_point, self._secret, hashfn, data) + + @classmethod + def read_from(cls, stream): + # just to unify the API + return cls(stream.read(32)) + + @property + def is_private(self) -> bool: + return True + + +# Nothing up my sleeve point for no-internal-key taproot +# see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs +NUMS_PUBKEY = PublicKey.from_string( + "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" +) diff --git a/bitcoin_client/ledger_bitcoin/embit/hashes.py b/bitcoin_client/ledger_bitcoin/embit/hashes.py new file mode 100644 index 000000000..c5edd081f --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/hashes.py @@ -0,0 +1,41 @@ +import hashlib + +try: + # this will work with micropython and python < 3.10 + # but will raise and exception if ripemd is not supported (python3.10, openssl 3) + hashlib.new("ripemd160") + + def ripemd160(msg: bytes) -> bytes: + return hashlib.new("ripemd160", msg).digest() + +except: + # otherwise use pure python implementation + from .util.py_ripemd160 import ripemd160 + + +def double_sha256(msg: bytes) -> bytes: + """sha256(sha256(msg)) -> bytes""" + return hashlib.sha256(hashlib.sha256(msg).digest()).digest() + + +def hash160(msg: bytes) -> bytes: + """ripemd160(sha256(msg)) -> bytes""" + return ripemd160(hashlib.sha256(msg).digest()) + + +def sha256(msg: bytes) -> bytes: + """one-line sha256(msg) -> bytes""" + return hashlib.sha256(msg).digest() + + +def tagged_hash(tag: str, data: bytes) -> bytes: + """BIP-Schnorr tag-specific key derivation""" + hashtag = hashlib.sha256(tag.encode()).digest() + return hashlib.sha256(hashtag + hashtag + data).digest() + + +def tagged_hash_init(tag: str, data: bytes = b""): + """Prepares a tagged hash function to digest extra data""" + hashtag = hashlib.sha256(tag.encode()).digest() + h = hashlib.sha256(hashtag + hashtag + data) + return h diff --git a/bitcoin_client/ledger_bitcoin/embit/misc.py b/bitcoin_client/ledger_bitcoin/embit/misc.py new file mode 100644 index 000000000..fc2c8046d --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/misc.py @@ -0,0 +1,70 @@ +"""Misc utility functions used across embit""" +import sys + +# implementation-specific functions and libraries: +if sys.implementation.name == "micropython": + from micropython import const + import secp256k1 +else: + from .util import secp256k1 + + def const(x): + return x + + +try: + # if urandom is available from os module: + from os import urandom as urandom +except ImportError: + # otherwise - try reading from /dev/urandom + def urandom(n: int) -> bytes: + with open("/dev/urandom", "rb") as f: + return f.read(n) + + +def getrandbits(k: int) -> int: + b = urandom(k // 8 + 1) + return int.from_bytes(b, "big") % (2**k) + + +def secure_randint(vmin: int, vmax: int) -> int: + """ + Normal random.randint uses PRNG that is not suitable + for cryptographic applications. + This one uses os.urandom for randomness. + """ + import math + + assert vmax > vmin + delta = vmax - vmin + nbits = math.ceil(math.log2(delta + 1)) + randn = getrandbits(nbits) + while randn > delta: + randn = getrandbits(nbits) + return vmin + randn + + +def copy(a: bytes) -> bytes: + """Ugly copy that works everywhere incl micropython""" + if len(a) == 0: + return b"" + return a[:1] + a[1:] + + +def read_until(s, chars=b",)(#"): + """Read from stream until one of `char` characters. + By default `chars=,)(#`. + + Return a tuple (result: bytes, char: bytes | None) + where result is bytes read from the stream until char, + char contains this character or None if the end of stream reached. + """ + res = b"" + chunk = b"" + while True: + chunk = s.read(1) + if len(chunk) == 0: + return res, None + if chunk in chars: + return res, chunk + res += chunk diff --git a/bitcoin_client/ledger_bitcoin/embit/networks.py b/bitcoin_client/ledger_bitcoin/embit/networks.py new file mode 100644 index 000000000..6f1a54180 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/networks.py @@ -0,0 +1,76 @@ +from .misc import const + +NETWORKS = { + "main": { + "name": "Mainnet", + "wif": b"\x80", + "p2pkh": b"\x00", + "p2sh": b"\x05", + "bech32": "bc", + "xprv": b"\x04\x88\xad\xe4", + "xpub": b"\x04\x88\xb2\x1e", + "yprv": b"\x04\x9d\x78\x78", + "zprv": b"\x04\xb2\x43\x0c", + "Yprv": b"\x02\x95\xb0\x05", + "Zprv": b"\x02\xaa\x7a\x99", + "ypub": b"\x04\x9d\x7c\xb2", + "zpub": b"\x04\xb2\x47\x46", + "Ypub": b"\x02\x95\xb4\x3f", + "Zpub": b"\x02\xaa\x7e\xd3", + "bip32": const(0), # coin type for bip32 derivation + }, + "test": { + "name": "Testnet", + "wif": b"\xEF", + "p2pkh": b"\x6F", + "p2sh": b"\xC4", + "bech32": "tb", + "xprv": b"\x04\x35\x83\x94", + "xpub": b"\x04\x35\x87\xcf", + "yprv": b"\x04\x4a\x4e\x28", + "zprv": b"\x04\x5f\x18\xbc", + "Yprv": b"\x02\x42\x85\xb5", + "Zprv": b"\x02\x57\x50\x48", + "ypub": b"\x04\x4a\x52\x62", + "zpub": b"\x04\x5f\x1c\xf6", + "Ypub": b"\x02\x42\x89\xef", + "Zpub": b"\x02\x57\x54\x83", + "bip32": const(1), + }, + "regtest": { + "name": "Regtest", + "wif": b"\xEF", + "p2pkh": b"\x6F", + "p2sh": b"\xC4", + "bech32": "bcrt", + "xprv": b"\x04\x35\x83\x94", + "xpub": b"\x04\x35\x87\xcf", + "yprv": b"\x04\x4a\x4e\x28", + "zprv": b"\x04\x5f\x18\xbc", + "Yprv": b"\x02\x42\x85\xb5", + "Zprv": b"\x02\x57\x50\x48", + "ypub": b"\x04\x4a\x52\x62", + "zpub": b"\x04\x5f\x1c\xf6", + "Ypub": b"\x02\x42\x89\xef", + "Zpub": b"\x02\x57\x54\x83", + "bip32": const(1), + }, + "signet": { + "name": "Signet", + "wif": b"\xEF", + "p2pkh": b"\x6F", + "p2sh": b"\xC4", + "bech32": "tb", + "xprv": b"\x04\x35\x83\x94", + "xpub": b"\x04\x35\x87\xcf", + "yprv": b"\x04\x4a\x4e\x28", + "zprv": b"\x04\x5f\x18\xbc", + "Yprv": b"\x02\x42\x85\xb5", + "Zprv": b"\x02\x57\x50\x48", + "ypub": b"\x04\x4a\x52\x62", + "zpub": b"\x04\x5f\x1c\xf6", + "Ypub": b"\x02\x42\x89\xef", + "Zpub": b"\x02\x57\x54\x83", + "bip32": const(1), + }, +} diff --git a/bitcoin_client/ledger_bitcoin/embit/script.py b/bitcoin_client/ledger_bitcoin/embit/script.py new file mode 100644 index 000000000..5cea7f98f --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/script.py @@ -0,0 +1,212 @@ +from .networks import NETWORKS +from . import base58 +from . import bech32 +from . import hashes +from . import compact +from .base import EmbitBase, EmbitError + +SIGHASH_ALL = 1 + + +class Script(EmbitBase): + def __init__(self, data=b""): + self.data = data + + def address(self, network=NETWORKS["main"]): + script_type = self.script_type() + data = self.data + + if script_type is None: + raise ValueError("This type of script doesn't have address representation") + + if script_type == "p2pkh": + d = network["p2pkh"] + data[3:23] + return base58.encode_check(d) + + if script_type == "p2sh": + d = network["p2sh"] + data[2:22] + return base58.encode_check(d) + + if script_type in ["p2wpkh", "p2wsh", "p2tr"]: + ver = data[0] + # FIXME: should be one of OP_N + if ver > 0: + ver = ver % 0x50 + return bech32.encode(network["bech32"], ver, data[2:]) + + # we should never get here + raise ValueError("Unsupported script type") + + def push(self, data): + self.data += compact.to_bytes(len(data)) + data + + def script_type(self): + data = self.data + # OP_DUP OP_HASH160 <20:hash160(pubkey)> OP_EQUALVERIFY OP_CHECKSIG + if len(data) == 25 and data[:3] == b"\x76\xa9\x14" and data[-2:] == b"\x88\xac": + return "p2pkh" + # OP_HASH160 <20:hash160(script)> OP_EQUAL + if len(data) == 23 and data[:2] == b"\xa9\x14" and data[-1] == 0x87: + return "p2sh" + # 0 <20:hash160(pubkey)> + if len(data) == 22 and data[:2] == b"\x00\x14": + return "p2wpkh" + # 0 <32:sha256(script)> + if len(data) == 34 and data[:2] == b"\x00\x20": + return "p2wsh" + # OP_1 + if len(data) == 34 and data[:2] == b"\x51\x20": + return "p2tr" + # unknown type + return None + + def write_to(self, stream): + res = stream.write(compact.to_bytes(len(self.data))) + res += stream.write(self.data) + return res + + @classmethod + def read_from(cls, stream): + l = compact.read_from(stream) + data = stream.read(l) + if len(data) != l: + raise ValueError("Cant read %d bytes" % l) + return cls(data) + + @classmethod + def from_address(cls, addr: str): + """ + Decodes a bitcoin address and returns corresponding scriptpubkey. + """ + return address_to_scriptpubkey(addr) + + def __eq__(self, other): + return self.data == other.data + + def __ne__(self, other): + return self.data != other.data + + def __hash__(self): + return hash(self.data) + + def __len__(self): + return len(self.data) + + +class Witness(EmbitBase): + def __init__(self, items=[]): + self.items = items[:] + + def write_to(self, stream): + res = stream.write(compact.to_bytes(len(self.items))) + for item in self.items: + res += stream.write(compact.to_bytes(len(item))) + res += stream.write(item) + return res + + @classmethod + def read_from(cls, stream): + num = compact.read_from(stream) + items = [] + for i in range(num): + l = compact.read_from(stream) + data = stream.read(l) + items.append(data) + return cls(items) + + def __hash__(self): + return hash(self.items) + + def __len__(self): + return len(self.items) + + +def p2pkh(pubkey): + """Return Pay-To-Pubkey-Hash ScriptPubkey""" + return Script(b"\x76\xa9\x14" + hashes.hash160(pubkey.sec()) + b"\x88\xac") + + +def p2sh(script): + """Return Pay-To-Script-Hash ScriptPubkey""" + return Script(b"\xa9\x14" + hashes.hash160(script.data) + b"\x87") + + +def p2wpkh(pubkey): + """Return Pay-To-Witness-Pubkey-Hash ScriptPubkey""" + return Script(b"\x00\x14" + hashes.hash160(pubkey.sec())) + + +def p2wsh(script): + """Return Pay-To-Witness-Pubkey-Hash ScriptPubkey""" + return Script(b"\x00\x20" + hashes.sha256(script.data)) + + +def p2tr(pubkey, script_tree=None): + """Return Pay-To-Taproot ScriptPubkey""" + if script_tree is None: + h = b"" + else: + h = script_tree.tweak() + output_pubkey = pubkey.taproot_tweak(h) + return Script(b"\x51\x20" + output_pubkey.xonly()) + + +def p2pkh_from_p2wpkh(script): + """Convert p2wpkh to p2pkh script""" + return Script(b"\x76\xa9" + script.serialize()[2:] + b"\x88\xac") + + +def multisig(m: int, pubkeys): + if m <= 0 or m > 16: + raise ValueError("m must be between 1 and 16") + n = len(pubkeys) + if n < m or n > 16: + raise ValueError("Number of pubkeys must be between %d and 16" % m) + data = bytes([80 + m]) + for pubkey in pubkeys: + sec = pubkey.sec() + data += bytes([len(sec)]) + sec + # OP_m ... OP_n OP_CHECKMULTISIG + data += bytes([80 + n, 0xAE]) + return Script(data) + + +def address_to_scriptpubkey(addr): + # try with base58 address + try: + data = base58.decode_check(addr) + prefix = data[:1] + for net in NETWORKS.values(): + if prefix == net["p2pkh"]: + return Script(b"\x76\xa9\x14" + data[1:] + b"\x88\xac") + elif prefix == net["p2sh"]: + return Script(b"\xa9\x14" + data[1:] + b"\x87") + except: + # fail - then it's bech32 address + hrp = addr.split("1")[0] + ver, data = bech32.decode(hrp, addr) + if ver not in [0, 1] or len(data) not in [20, 32]: + raise EmbitError("Invalid bech32 address") + if ver == 1 and len(data) != 32: + raise EmbitError("Invalid bech32 address") + # OP_1..OP_N + if ver > 0: + ver += 0x50 + return Script(bytes([ver, len(data)] + data)) + + +def script_sig_p2pkh(signature, pubkey, sighash=SIGHASH_ALL): + sec = pubkey.sec() + der = signature.serialize() + bytes([sighash]) + data = compact.to_bytes(len(der)) + der + compact.to_bytes(len(sec)) + sec + return Script(data) + + +def script_sig_p2sh(redeem_script): + """Creates scriptsig for p2sh""" + # FIXME: implement for legacy p2sh as well + return Script(redeem_script.serialize()) + + +def witness_p2wpkh(signature, pubkey, sighash=SIGHASH_ALL): + return Witness([signature.serialize() + bytes([sighash]), pubkey.sec()]) diff --git a/bitcoin_client/ledger_bitcoin/embit/util/__init__.py b/bitcoin_client/ledger_bitcoin/embit/util/__init__.py new file mode 100644 index 000000000..d2f2564a6 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/util/__init__.py @@ -0,0 +1,6 @@ +from . import secp256k1 + +try: + from micropython import const +except: + const = lambda x: x diff --git a/bitcoin_client/ledger_bitcoin/embit/util/ctypes_secp256k1.py b/bitcoin_client/ledger_bitcoin/embit/util/ctypes_secp256k1.py new file mode 100644 index 000000000..232abda18 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/util/ctypes_secp256k1.py @@ -0,0 +1,1202 @@ +import ctypes, os +import ctypes.util +import platform +import threading + +from ctypes import ( + cast, + byref, + c_char, + c_byte, + c_int, + c_uint, + c_char_p, + c_size_t, + c_void_p, + c_uint64, + create_string_buffer, + CFUNCTYPE, + POINTER, +) + +_lock = threading.Lock() + + +# @locked decorator +def locked(func): + def wrapper(*args, **kwargs): + with _lock: + return func(*args, **kwargs) + + return wrapper + + +# Flags to pass to context_create. +CONTEXT_VERIFY = 0b0100000001 +CONTEXT_SIGN = 0b1000000001 +CONTEXT_NONE = 0b0000000001 + +# Flags to pass to ec_pubkey_serialize +EC_COMPRESSED = 0b0100000010 +EC_UNCOMPRESSED = 0b0000000010 + + +def _copy(a: bytes) -> bytes: + """Ugly copy that works everywhere incl micropython""" + if len(a) == 0: + return b"" + return a[:1] + a[1:] + + +def _find_library(): + library_path = None + extension = "" + if platform.system() == "Darwin": + extension = ".dylib" + elif platform.system() == "Linux": + extension = ".so" + elif platform.system() == "Windows": + extension = ".dll" + + path = os.path.join( + os.path.dirname(__file__), + "prebuilt/libsecp256k1_%s_%s%s" + % (platform.system().lower(), platform.machine().lower(), extension), + ) + if os.path.isfile(path): + return path + # try searching + if not library_path: + library_path = ctypes.util.find_library("libsecp256k1") + if not library_path: + library_path = ctypes.util.find_library("secp256k1") + # library search failed + if not library_path: + if platform.system() == "Linux" and os.path.isfile( + "/usr/local/lib/libsecp256k1.so.0" + ): + library_path = "/usr/local/lib/libsecp256k1.so.0" + return library_path + + +@locked +def _init(flags=(CONTEXT_SIGN | CONTEXT_VERIFY)): + library_path = _find_library() + # meh, can't find library + if not library_path: + raise RuntimeError( + "Can't find libsecp256k1 library. Make sure to compile and install it." + ) + + secp256k1 = ctypes.cdll.LoadLibrary(library_path) + + secp256k1.secp256k1_context_create.argtypes = [c_uint] + secp256k1.secp256k1_context_create.restype = c_void_p + + secp256k1.secp256k1_context_randomize.argtypes = [c_void_p, c_char_p] + secp256k1.secp256k1_context_randomize.restype = c_int + + secp256k1.secp256k1_ec_seckey_verify.argtypes = [c_void_p, c_char_p] + secp256k1.secp256k1_ec_seckey_verify.restype = c_int + + secp256k1.secp256k1_ec_privkey_negate.argtypes = [c_void_p, c_char_p] + secp256k1.secp256k1_ec_privkey_negate.restype = c_int + + secp256k1.secp256k1_ec_pubkey_negate.argtypes = [c_void_p, c_char_p] + secp256k1.secp256k1_ec_pubkey_negate.restype = c_int + + secp256k1.secp256k1_ec_privkey_tweak_add.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_ec_privkey_tweak_add.restype = c_int + + secp256k1.secp256k1_ec_privkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_ec_privkey_tweak_mul.restype = c_int + + secp256k1.secp256k1_ec_pubkey_create.argtypes = [c_void_p, c_void_p, c_char_p] + secp256k1.secp256k1_ec_pubkey_create.restype = c_int + + secp256k1.secp256k1_ec_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p, c_int] + secp256k1.secp256k1_ec_pubkey_parse.restype = c_int + + secp256k1.secp256k1_ec_pubkey_serialize.argtypes = [ + c_void_p, + c_char_p, + c_void_p, + c_char_p, + c_uint, + ] + secp256k1.secp256k1_ec_pubkey_serialize.restype = c_int + + secp256k1.secp256k1_ec_pubkey_tweak_add.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_ec_pubkey_tweak_add.restype = c_int + + secp256k1.secp256k1_ec_pubkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_ec_pubkey_tweak_mul.restype = c_int + + secp256k1.secp256k1_ecdsa_signature_parse_compact.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_ecdsa_signature_parse_compact.restype = c_int + + secp256k1.secp256k1_ecdsa_signature_serialize_compact.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_ecdsa_signature_serialize_compact.restype = c_int + + secp256k1.secp256k1_ecdsa_signature_parse_der.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_uint, + ] + secp256k1.secp256k1_ecdsa_signature_parse_der.restype = c_int + + secp256k1.secp256k1_ecdsa_signature_serialize_der.argtypes = [ + c_void_p, + c_char_p, + c_void_p, + c_char_p, + ] + secp256k1.secp256k1_ecdsa_signature_serialize_der.restype = c_int + + secp256k1.secp256k1_ecdsa_sign.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_char_p, + c_void_p, + c_char_p, + ] + secp256k1.secp256k1_ecdsa_sign.restype = c_int + + secp256k1.secp256k1_ecdsa_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p] + secp256k1.secp256k1_ecdsa_verify.restype = c_int + + secp256k1.secp256k1_ec_pubkey_combine.argtypes = [ + c_void_p, + c_char_p, + c_void_p, + c_size_t, + ] + secp256k1.secp256k1_ec_pubkey_combine.restype = c_int + + # ecdh + try: + secp256k1.secp256k1_ecdh.argtypes = [ + c_void_p, # ctx + c_char_p, # output + c_char_p, # point + c_char_p, # scalar + CFUNCTYPE, # hashfp + c_void_p, # data + ] + secp256k1.secp256k1_ecdh.restype = c_int + except: + pass + + # schnorr sig + try: + secp256k1.secp256k1_xonly_pubkey_from_pubkey.argtypes = [ + c_void_p, # ctx + c_char_p, # xonly pubkey + POINTER(c_int), # parity + c_char_p, # pubkey + ] + secp256k1.secp256k1_xonly_pubkey_from_pubkey.restype = c_int + + secp256k1.secp256k1_schnorrsig_verify.argtypes = [ + c_void_p, # ctx + c_char_p, # sig + c_char_p, # msg + c_char_p, # pubkey + ] + secp256k1.secp256k1_schnorrsig_verify.restype = c_int + + secp256k1.secp256k1_schnorrsig_sign.argtypes = [ + c_void_p, # ctx + c_char_p, # sig + c_char_p, # msg + c_char_p, # keypair + c_void_p, # nonce_function + c_char_p, # extra data + ] + secp256k1.secp256k1_schnorrsig_sign.restype = c_int + + secp256k1.secp256k1_keypair_create.argtypes = [ + c_void_p, # ctx + c_char_p, # keypair + c_char_p, # secret + ] + secp256k1.secp256k1_keypair_create.restype = c_int + except: + pass + + # recoverable module + try: + secp256k1.secp256k1_ecdsa_sign_recoverable.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_char_p, + c_void_p, + c_void_p, + ] + secp256k1.secp256k1_ecdsa_sign_recoverable.restype = c_int + + secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_int, + ] + secp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact.restype = c_int + + secp256k1.secp256k1_ecdsa_recoverable_signature_serialize_compact.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_ecdsa_recoverable_signature_serialize_compact.restype = ( + c_int + ) + + secp256k1.secp256k1_ecdsa_recoverable_signature_convert.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_ecdsa_recoverable_signature_convert.restype = c_int + + secp256k1.secp256k1_ecdsa_recover.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_ecdsa_recover.restype = c_int + except: + pass + + # zkp modules + try: + # generator module + secp256k1.secp256k1_generator_parse.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_generator_parse.restype = c_int + + secp256k1.secp256k1_generator_serialize.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_generator_serialize.restype = c_int + + secp256k1.secp256k1_generator_generate.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_generator_generate.restype = c_int + + secp256k1.secp256k1_generator_generate_blinded.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_generator_generate_blinded.restype = c_int + + # pederson commitments + secp256k1.secp256k1_pedersen_commitment_parse.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_pedersen_commitment_parse.restype = c_int + + secp256k1.secp256k1_pedersen_commitment_serialize.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + ] + secp256k1.secp256k1_pedersen_commitment_serialize.restype = c_int + + secp256k1.secp256k1_pedersen_commit.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_uint64, + c_char_p, + ] + secp256k1.secp256k1_pedersen_commit.restype = c_int + + secp256k1.secp256k1_pedersen_blind_generator_blind_sum.argtypes = [ + c_void_p, # const secp256k1_context* ctx, + POINTER(c_uint64), # const uint64_t *value, + c_void_p, # const unsigned char* const* generator_blind, + c_void_p, # unsigned char* const* blinding_factor, + c_size_t, # size_t n_total, + c_size_t, # size_t n_inputs + ] + secp256k1.secp256k1_pedersen_blind_generator_blind_sum.restype = c_int + + secp256k1.secp256k1_pedersen_verify_tally.argtypes = [ + c_void_p, + c_void_p, + c_size_t, + c_void_p, + c_size_t, + ] + secp256k1.secp256k1_pedersen_verify_tally.restype = c_int + + # rangeproof + secp256k1.secp256k1_rangeproof_rewind.argtypes = [ + c_void_p, # ctx + c_char_p, # vbf out + POINTER(c_uint64), # value out + c_char_p, # message out + POINTER(c_uint64), # msg out len + c_char_p, # nonce + POINTER(c_uint64), # min value + POINTER(c_uint64), # max value + c_char_p, # pedersen commitment + c_char_p, # range proof + c_uint64, # proof len + c_char_p, # extra commitment (scriptpubkey) + c_uint64, # extra len + c_char_p, # generator + ] + secp256k1.secp256k1_rangeproof_rewind.restype = c_int + + secp256k1.secp256k1_rangeproof_verify.argtypes = [ + c_void_p, # ctx + POINTER(c_uint64), # min value + POINTER(c_uint64), # max value + c_char_p, # pedersen commitment + c_char_p, # proof + c_uint64, # proof len + c_char_p, # extra + c_uint64, # extra len + c_char_p, # generator + ] + secp256k1.secp256k1_rangeproof_verify.restype = c_int + + secp256k1.secp256k1_rangeproof_sign.argtypes = [ + c_void_p, # ctx + c_char_p, # proof + POINTER(c_uint64), # plen + c_uint64, # min_value + c_char_p, # commit + c_char_p, # blind + c_char_p, # nonce + c_int, # exp + c_int, # min_bits + c_uint64, # value + c_char_p, # message + c_uint64, # msg_len + c_char_p, # extra_commit + c_uint64, # extra_commit_len + c_char_p, # gen + ] + secp256k1.secp256k1_rangeproof_sign.restype = c_int + + # musig + secp256k1.secp256k1_musig_pubkey_combine.argtypes = [ + c_void_p, + c_void_p, + c_char_p, + c_void_p, + c_void_p, + c_size_t, + ] + secp256k1.secp256k1_musig_pubkey_combine.restype = c_int + + # surjection proofs + secp256k1.secp256k1_surjectionproof_initialize.argtypes = [ + c_void_p, # const secp256k1_context* ctx, + c_char_p, # secp256k1_surjectionproof* proof, + POINTER(c_size_t), # size_t *input_index, + c_void_p, # c_char_p, # const secp256k1_fixed_asset_tag* fixed_input_tags, + c_size_t, # const size_t n_input_tags, + c_size_t, # const size_t n_input_tags_to_use, + c_char_p, # const secp256k1_fixed_asset_tag* fixed_output_tag, + c_size_t, # const size_t n_max_iterations, + c_char_p, # const unsigned char *random_seed32 + ] + secp256k1.secp256k1_surjectionproof_initialize.restype = c_int + + secp256k1.secp256k1_surjectionproof_generate.argtypes = [ + c_void_p, # const secp256k1_context* ctx, + c_char_p, # secp256k1_surjectionproof* proof, + c_char_p, # const secp256k1_generator* ephemeral_input_tags, + c_size_t, # size_t n_ephemeral_input_tags, + c_char_p, # const secp256k1_generator* ephemeral_output_tag, + c_size_t, # size_t input_index, + c_char_p, # const unsigned char *input_blinding_key, + c_char_p, # const unsigned char *output_blinding_key + ] + secp256k1.secp256k1_surjectionproof_generate.restype = c_int + + secp256k1.secp256k1_surjectionproof_verify.argtypes = [ + c_void_p, # const secp256k1_context* ctx, + c_char_p, # const secp256k1_surjectionproof* proof, + c_char_p, # const secp256k1_generator* ephemeral_input_tags, + c_size_t, # size_t n_ephemeral_input_tags, + c_char_p, # const secp256k1_generator* ephemeral_output_tag + ] + secp256k1.secp256k1_surjectionproof_verify.restype = c_int + + secp256k1.secp256k1_surjectionproof_serialize.argtypes = [ + c_void_p, # const secp256k1_context* ctx, + c_char_p, # unsigned char *output, + POINTER(c_size_t), # size_t *outputlen, + c_char_p, # const secp256k1_surjectionproof *proof + ] + secp256k1.secp256k1_surjectionproof_serialize.restype = c_int + + secp256k1.secp256k1_surjectionproof_serialized_size.argtypes = [ + c_void_p, # const secp256k1_context* ctx, + c_char_p, # const secp256k1_surjectionproof* proof + ] + secp256k1.secp256k1_surjectionproof_serialized_size.restype = c_size_t + + secp256k1.secp256k1_surjectionproof_parse.argtypes = [ + c_void_p, + c_char_p, + c_char_p, + c_size_t, + ] + secp256k1.secp256k1_surjectionproof_parse.restype = c_int + + except: + pass + + secp256k1.ctx = secp256k1.secp256k1_context_create(flags) + + r = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) + + return secp256k1 + + +_secp = _init() + + +# bindings equal to ones in micropython +@locked +def context_randomize(seed, context=_secp.ctx): + if len(seed) != 32: + raise ValueError("Seed should be 32 bytes long") + if _secp.secp256k1_context_randomize(context, seed) == 0: + raise RuntimeError("Failed to randomize context") + + +@locked +def ec_pubkey_create(secret, context=_secp.ctx): + if len(secret) != 32: + raise ValueError("Private key should be 32 bytes long") + pub = bytes(64) + r = _secp.secp256k1_ec_pubkey_create(context, pub, secret) + if r == 0: + raise ValueError("Invalid private key") + return pub + + +@locked +def ec_pubkey_parse(sec, context=_secp.ctx): + if len(sec) != 33 and len(sec) != 65: + raise ValueError("Serialized pubkey should be 33 or 65 bytes long") + if len(sec) == 33: + if sec[0] != 0x02 and sec[0] != 0x03: + raise ValueError("Compressed pubkey should start with 0x02 or 0x03") + else: + if sec[0] != 0x04: + raise ValueError("Uncompressed pubkey should start with 0x04") + pub = bytes(64) + r = _secp.secp256k1_ec_pubkey_parse(context, pub, sec, len(sec)) + if r == 0: + raise ValueError("Failed parsing public key") + return pub + + +@locked +def ec_pubkey_serialize(pubkey, flag=EC_COMPRESSED, context=_secp.ctx): + if len(pubkey) != 64: + raise ValueError("Pubkey should be 64 bytes long") + if flag not in [EC_COMPRESSED, EC_UNCOMPRESSED]: + raise ValueError("Invalid flag") + sec = bytes(33) if (flag == EC_COMPRESSED) else bytes(65) + sz = c_size_t(len(sec)) + r = _secp.secp256k1_ec_pubkey_serialize(context, sec, byref(sz), pubkey, flag) + if r == 0: + raise ValueError("Failed to serialize pubkey") + return sec + + +@locked +def ecdsa_signature_parse_compact(compact_sig, context=_secp.ctx): + if len(compact_sig) != 64: + raise ValueError("Compact signature should be 64 bytes long") + sig = bytes(64) + r = _secp.secp256k1_ecdsa_signature_parse_compact(context, sig, compact_sig) + if r == 0: + raise ValueError("Failed parsing compact signature") + return sig + + +@locked +def ecdsa_signature_parse_der(der, context=_secp.ctx): + sig = bytes(64) + r = _secp.secp256k1_ecdsa_signature_parse_der(context, sig, der, len(der)) + if r == 0: + raise ValueError("Failed parsing compact signature") + return sig + + +@locked +def ecdsa_signature_serialize_der(sig, context=_secp.ctx): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + der = bytes(78) # max + sz = c_size_t(len(der)) + r = _secp.secp256k1_ecdsa_signature_serialize_der(context, der, byref(sz), sig) + if r == 0: + raise ValueError("Failed serializing der signature") + return der[: sz.value] + + +@locked +def ecdsa_signature_serialize_compact(sig, context=_secp.ctx): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + ser = bytes(64) + r = _secp.secp256k1_ecdsa_signature_serialize_compact(context, ser, sig) + if r == 0: + raise ValueError("Failed serializing der signature") + return ser + + +@locked +def ecdsa_signature_normalize(sig, context=_secp.ctx): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + sig2 = bytes(64) + r = _secp.secp256k1_ecdsa_signature_normalize(context, sig2, sig) + return sig2 + + +@locked +def ecdsa_verify(sig, msg, pub, context=_secp.ctx): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + if len(msg) != 32: + raise ValueError("Message should be 32 bytes long") + if len(pub) != 64: + raise ValueError("Public key should be 64 bytes long") + r = _secp.secp256k1_ecdsa_verify(context, sig, msg, pub) + return bool(r) + + +@locked +def ecdsa_sign(msg, secret, nonce_function=None, extra_data=None, context=_secp.ctx): + if len(msg) != 32: + raise ValueError("Message should be 32 bytes long") + if len(secret) != 32: + raise ValueError("Secret key should be 32 bytes long") + if extra_data and len(extra_data) != 32: + raise ValueError("Extra data should be 32 bytes long") + sig = bytes(64) + r = _secp.secp256k1_ecdsa_sign( + context, sig, msg, secret, nonce_function, extra_data + ) + if r == 0: + raise ValueError("Failed to sign") + return sig + + +@locked +def ec_seckey_verify(secret, context=_secp.ctx): + if len(secret) != 32: + raise ValueError("Secret should be 32 bytes long") + return bool(_secp.secp256k1_ec_seckey_verify(context, secret)) + + +@locked +def ec_privkey_negate(secret, context=_secp.ctx): + if len(secret) != 32: + raise ValueError("Secret should be 32 bytes long") + b = _copy(secret) + _secp.secp256k1_ec_privkey_negate(context, b) + return b + + +@locked +def ec_pubkey_negate(pubkey, context=_secp.ctx): + if len(pubkey) != 64: + raise ValueError("Pubkey should be a 64-byte structure") + pub = _copy(pubkey) + r = _secp.secp256k1_ec_pubkey_negate(context, pub) + if r == 0: + raise ValueError("Failed to negate pubkey") + return pub + + +@locked +def ec_privkey_tweak_add(secret, tweak, context=_secp.ctx): + if len(secret) != 32 or len(tweak) != 32: + raise ValueError("Secret and tweak should both be 32 bytes long") + t = _copy(tweak) + if _secp.secp256k1_ec_privkey_tweak_add(context, secret, tweak) == 0: + raise ValueError("Failed to tweak the secret") + return None + + +@locked +def ec_pubkey_tweak_add(pub, tweak, context=_secp.ctx): + if len(pub) != 64: + raise ValueError("Public key should be 64 bytes long") + if len(tweak) != 32: + raise ValueError("Tweak should be 32 bytes long") + t = _copy(tweak) + if _secp.secp256k1_ec_pubkey_tweak_add(context, pub, tweak) == 0: + raise ValueError("Failed to tweak the public key") + return None + + +@locked +def ec_privkey_add(secret, tweak, context=_secp.ctx): + if len(secret) != 32 or len(tweak) != 32: + raise ValueError("Secret and tweak should both be 32 bytes long") + # ugly copy that works in mpy and py + s = _copy(secret) + t = _copy(tweak) + if _secp.secp256k1_ec_privkey_tweak_add(context, s, t) == 0: + raise ValueError("Failed to tweak the secret") + return s + + +@locked +def ec_pubkey_add(pub, tweak, context=_secp.ctx): + if len(pub) != 64: + raise ValueError("Public key should be 64 bytes long") + if len(tweak) != 32: + raise ValueError("Tweak should be 32 bytes long") + p = _copy(pub) + if _secp.secp256k1_ec_pubkey_tweak_add(context, p, tweak) == 0: + raise ValueError("Failed to tweak the public key") + return p + + +@locked +def ec_privkey_tweak_mul(secret, tweak, context=_secp.ctx): + if len(secret) != 32 or len(tweak) != 32: + raise ValueError("Secret and tweak should both be 32 bytes long") + if _secp.secp256k1_ec_privkey_tweak_mul(context, secret, tweak) == 0: + raise ValueError("Failed to tweak the secret") + + +@locked +def ec_pubkey_tweak_mul(pub, tweak, context=_secp.ctx): + if len(pub) != 64: + raise ValueError("Public key should be 64 bytes long") + if len(tweak) != 32: + raise ValueError("Tweak should be 32 bytes long") + if _secp.secp256k1_ec_pubkey_tweak_mul(context, pub, tweak) == 0: + raise ValueError("Failed to tweak the public key") + + +@locked +def ec_pubkey_combine(*args, context=_secp.ctx): + pub = bytes(64) + pubkeys = (c_char_p * len(args))(*args) + r = _secp.secp256k1_ec_pubkey_combine(context, pub, pubkeys, len(args)) + if r == 0: + raise ValueError("Failed to combine pubkeys") + return pub + + +# ecdh +@locked +def ecdh(pubkey, scalar, hashfn=None, data=None, context=_secp.ctx): + if not len(pubkey) == 64: + raise ValueError("Pubkey should be 64 bytes long") + if not len(scalar) == 32: + raise ValueError("Scalar should be 32 bytes long") + secret = bytes(32) + if hashfn is None: + res = _secp.secp256k1_ecdh(context, secret, pubkey, scalar, None, None) + else: + + def _hashfn(out, x, y): + x = ctypes.string_at(x, 32) + y = ctypes.string_at(y, 32) + try: + res = hashfn(x, y, data) + except Exception as e: + return 0 + out = cast(out, POINTER(c_char * 32)) + out.contents.value = res + return 1 + + HASHFN = CFUNCTYPE(c_int, c_void_p, c_void_p, c_void_p) + res = _secp.secp256k1_ecdh( + context, secret, pubkey, scalar, HASHFN(_hashfn), data + ) + if res != 1: + raise RuntimeError("Failed to compute the shared secret") + return secret + + +# schnorrsig +@locked +def xonly_pubkey_from_pubkey(pubkey, context=_secp.ctx): + if len(pubkey) != 64: + raise ValueError("Pubkey should be 64 bytes long") + pointer = POINTER(c_int) + parity = pointer(c_int(0)) + xonly_pub = bytes(64) + res = _secp.secp256k1_xonly_pubkey_from_pubkey(context, xonly_pub, parity, pubkey) + if res != 1: + raise RuntimeError("Failed to convert the pubkey") + return xonly_pub, bool(parity.contents.value) + + +@locked +def schnorrsig_verify(sig, msg, pubkey, context=_secp.ctx): + assert len(sig) == 64 + assert len(msg) == 32 + assert len(pubkey) == 64 + res = _secp.secp256k1_schnorrsig_verify(context, sig, msg, pubkey) + return bool(res) + + +@locked +def keypair_create(secret, context=_secp.ctx): + assert len(secret) == 32 + keypair = bytes(96) + r = _secp.secp256k1_keypair_create(context, keypair, secret) + if r == 0: + raise ValueError("Failed to create keypair") + return keypair + + +# not @locked because it uses keypair_create inside +def schnorrsig_sign( + msg, keypair, nonce_function=None, extra_data=None, context=_secp.ctx +): + assert len(msg) == 32 + if len(keypair) == 32: + keypair = keypair_create(keypair, context=context) + with _lock: + assert len(keypair) == 96 + sig = bytes(64) + r = _secp.secp256k1_schnorrsig_sign( + context, sig, msg, keypair, nonce_function, extra_data + ) + if r == 0: + raise ValueError("Failed to sign") + return sig + + +# recoverable +@locked +def ecdsa_sign_recoverable(msg, secret, context=_secp.ctx): + if len(msg) != 32: + raise ValueError("Message should be 32 bytes long") + if len(secret) != 32: + raise ValueError("Secret key should be 32 bytes long") + sig = bytes(65) + r = _secp.secp256k1_ecdsa_sign_recoverable(context, sig, msg, secret, None, None) + if r == 0: + raise ValueError("Failed to sign") + return sig + + +@locked +def ecdsa_recoverable_signature_serialize_compact(sig, context=_secp.ctx): + if len(sig) != 65: + raise ValueError("Recoverable signature should be 65 bytes long") + ser = bytes(64) + idx = bytes(1) + r = _secp.secp256k1_ecdsa_recoverable_signature_serialize_compact( + context, ser, idx, sig + ) + if r == 0: + raise ValueError("Failed serializing der signature") + return ser, idx[0] + + +@locked +def ecdsa_recoverable_signature_parse_compact(compact_sig, recid, context=_secp.ctx): + if len(compact_sig) != 64: + raise ValueError("Signature should be 64 bytes long") + sig = bytes(65) + r = _secp.secp256k1_ecdsa_recoverable_signature_parse_compact( + context, sig, compact_sig, recid + ) + if r == 0: + raise ValueError("Failed parsing compact signature") + return sig + + +@locked +def ecdsa_recoverable_signature_convert(sigin, context=_secp.ctx): + if len(sigin) != 65: + raise ValueError("Recoverable signature should be 65 bytes long") + sig = bytes(64) + r = _secp.secp256k1_ecdsa_recoverable_signature_convert(context, sig, sigin) + if r == 0: + raise ValueError("Failed converting signature") + return sig + + +@locked +def ecdsa_recover(sig, msghash, context=_secp.ctx): + if len(sig) != 65: + raise ValueError("Recoverable signature should be 65 bytes long") + if len(msghash) != 32: + raise ValueError("Message should be 32 bytes long") + pub = bytes(64) + r = _secp.secp256k1_ecdsa_recover(context, pub, sig, msghash) + if r == 0: + raise ValueError("Failed to recover public key") + return pub + + +# zkp modules + + +@locked +def pedersen_commitment_parse(inp, context=_secp.ctx): + if len(inp) != 33: + raise ValueError("Serialized commitment should be 33 bytes long") + commit = bytes(64) + r = _secp.secp256k1_pedersen_commitment_parse(context, commit, inp) + if r == 0: + raise ValueError("Failed to parse commitment") + return commit + + +@locked +def pedersen_commitment_serialize(commit, context=_secp.ctx): + if len(commit) != 64: + raise ValueError("Commitment should be 64 bytes long") + sec = bytes(33) + r = _secp.secp256k1_pedersen_commitment_serialize(context, sec, commit) + if r == 0: + raise ValueError("Failed to serialize commitment") + return sec + + +@locked +def pedersen_commit(vbf, value, gen, context=_secp.ctx): + if len(gen) != 64: + raise ValueError("Generator should be 64 bytes long") + if len(vbf) != 32: + raise ValueError(f"Blinding factor should be 32 bytes long, not {len(vbf)}") + commit = bytes(64) + r = _secp.secp256k1_pedersen_commit(context, commit, vbf, value, gen) + if r == 0: + raise ValueError("Failed to create commitment") + return commit + + +@locked +def pedersen_blind_generator_blind_sum( + values, gens, vbfs, num_inputs, context=_secp.ctx +): + vals = (c_uint64 * len(values))(*values) + vbf = bytes(vbfs[-1]) + p = c_char_p(vbf) # obtain a pointer of various types + address = cast(p, c_void_p).value + + vbfs_joined = (c_char_p * len(vbfs))(*vbfs[:-1], address) + gens_joined = (c_char_p * len(gens))(*gens) + res = _secp.secp256k1_pedersen_blind_generator_blind_sum( + context, vals, gens_joined, vbfs_joined, len(values), num_inputs + ) + if res == 0: + raise ValueError("Failed to get the last blinding factor.") + res = (c_char * 32).from_address(address).raw + assert len(res) == 32 + return res + + +@locked +def pedersen_verify_tally(ins, outs, context=_secp.ctx): + in_ptr = (c_char_p * len(ins))(*ins) + out_ptr = (c_char_p * len(outs))(*outs) + res = _secp.secp256k1_pedersen_verify_tally( + context, in_ptr, len(in_ptr), out_ptr, len(out_ptr) + ) + return bool(res) + + +# generator +@locked +def generator_parse(inp, context=_secp.ctx): + if len(inp) != 33: + raise ValueError("Serialized generator should be 33 bytes long") + gen = bytes(64) + r = _secp.secp256k1_generator_parse(context, gen, inp) + if r == 0: + raise ValueError("Failed to parse generator") + return gen + + +@locked +def generator_generate(asset, context=_secp.ctx): + if len(asset) != 32: + raise ValueError("Asset should be 32 bytes long") + gen = bytes(64) + r = _secp.secp256k1_generator_generate(context, gen, asset) + if r == 0: + raise ValueError("Failed to generate generator") + return gen + + +@locked +def generator_generate_blinded(asset, abf, context=_secp.ctx): + if len(asset) != 32: + raise ValueError("Asset should be 32 bytes long") + if len(abf) != 32: + raise ValueError("Asset blinding factor should be 32 bytes long") + gen = bytes(64) + r = _secp.secp256k1_generator_generate_blinded(context, gen, asset, abf) + if r == 0: + raise ValueError("Failed to generate generator") + return gen + + +@locked +def generator_serialize(generator, context=_secp.ctx): + if len(generator) != 64: + raise ValueError("Generator should be 64 bytes long") + sec = bytes(33) + if _secp.secp256k1_generator_serialize(context, sec, generator) == 0: + raise RuntimeError("Failed to serialize generator") + return sec + + +# rangeproof +@locked +def rangeproof_rewind( + proof, + nonce, + value_commitment, + script_pubkey, + generator, + message_length=64, + context=_secp.ctx, +): + if len(generator) != 64: + raise ValueError("Generator should be 64 bytes long") + if len(nonce) != 32: + raise ValueError("Nonce should be 32 bytes long") + if len(value_commitment) != 64: + raise ValueError("Value commitment should be 64 bytes long") + + pointer = POINTER(c_uint64) + + msg = b"\x00" * message_length + msglen = pointer(c_uint64(len(msg))) + + vbf_out = b"\x00" * 32 + value_out = pointer(c_uint64(0)) + min_value = pointer(c_uint64(0)) + max_value = pointer(c_uint64(0)) + res = _secp.secp256k1_rangeproof_rewind( + context, + vbf_out, + value_out, + msg, + msglen, + nonce, + min_value, + max_value, + value_commitment, + proof, + len(proof), + script_pubkey, + len(script_pubkey), + generator, + ) + if res != 1: + raise RuntimeError("Failed to rewind the proof") + return ( + value_out.contents.value, + vbf_out, + msg[: msglen.contents.value], + min_value.contents.value, + max_value.contents.value, + ) + + +# rangeproof + + +@locked +def rangeproof_verify( + proof, value_commitment, script_pubkey, generator, context=_secp.ctx +): + if len(generator) != 64: + raise ValueError("Generator should be 64 bytes long") + if len(value_commitment) != 64: + raise ValueError("Value commitment should be 64 bytes long") + + pointer = POINTER(c_uint64) + min_value = pointer(c_uint64(0)) + max_value = pointer(c_uint64(0)) + res = _secp.secp256k1_rangeproof_verify( + context, + min_value, + max_value, + value_commitment, + proof, + len(proof), + script_pubkey, + len(script_pubkey), + generator, + ) + if res != 1: + raise RuntimeError("Failed to verify the proof") + return min_value.contents.value, max_value.contents.value + + +@locked +def rangeproof_sign( + nonce, + value, + value_commitment, + vbf, + message, + extra, + gen, + min_value=1, + exp=0, + min_bits=52, + context=_secp.ctx, +): + if value == 0: + min_value = 0 + if len(gen) != 64: + raise ValueError("Generator should be 64 bytes long") + if len(nonce) != 32: + raise ValueError("Nonce should be 32 bytes long") + if len(value_commitment) != 64: + raise ValueError("Value commitment should be 64 bytes long") + if len(vbf) != 32: + raise ValueError("Value blinding factor should be 32 bytes long") + proof = bytes(5134) + pointer = POINTER(c_uint64) + prooflen = pointer(c_uint64(len(proof))) + res = _secp.secp256k1_rangeproof_sign( + context, + proof, + prooflen, + min_value, + value_commitment, + vbf, + nonce, + exp, + min_bits, + value, + message, + len(message), + extra, + len(extra), + gen, + ) + if res != 1: + raise RuntimeError("Failed to generate the proof") + return bytes(proof[: prooflen.contents.value]) + + +@locked +def musig_pubkey_combine(*args, context=_secp.ctx): + pub = bytes(64) + # TODO: strange that behaviour is different from pubkey_combine... + pubkeys = b"".join(args) # (c_char_p * len(args))(*args) + res = _secp.secp256k1_musig_pubkey_combine( + context, None, pub, None, pubkeys, len(args) + ) + if res == 0: + raise ValueError("Failed to combine pubkeys") + return pub + + +# surjection proof +@locked +def surjectionproof_initialize( + in_tags, out_tag, seed, tags_to_use=None, iterations=100, context=_secp.ctx +): + if tags_to_use is None: + tags_to_use = min(3, len(in_tags)) + if seed is None: + seed = os.urandom(32) + proof = bytes(4 + 8 + 256 // 8 + 32 * 257) + pointer = POINTER(c_size_t) + input_index = pointer(c_size_t(0)) + input_tags = b"".join(in_tags) + res = _secp.secp256k1_surjectionproof_initialize( + context, + proof, + input_index, + input_tags, + len(in_tags), + tags_to_use, + out_tag, + iterations, + seed, + ) + if res == 0: + raise RuntimeError("Failed to initialize the proof") + return proof, input_index.contents.value + + +@locked +def surjectionproof_generate( + proof, in_idx, in_tags, out_tag, in_abf, out_abf, context=_secp.ctx +): + res = _secp.secp256k1_surjectionproof_generate( + context, + proof, + b"".join(in_tags), + len(in_tags), + out_tag, + in_idx, + in_abf, + out_abf, + ) + if not res: + raise RuntimeError("Failed to generate surjection proof") + return proof + + +@locked +def surjectionproof_verify(proof, in_tags, out_tag, context=_secp.ctx): + res = _secp.secp256k1_surjectionproof_verify( + context, proof, b"".join(in_tags), len(in_tags), out_tag + ) + return bool(res) + + +@locked +def surjectionproof_serialize(proof, context=_secp.ctx): + s = _secp.secp256k1_surjectionproof_serialized_size(context, proof) + b = bytes(s) + pointer = POINTER(c_size_t) + sz = pointer(c_size_t(s)) + _secp.secp256k1_surjectionproof_serialize(context, b, sz, proof) + if s != sz.contents.value: + raise RuntimeError("Failed to serialize surjection proof - size mismatch") + return b + + +@locked +def surjectionproof_parse(proof, context=_secp.ctx): + parsed_proof = bytes(4 + 8 + 256 // 8 + 32 * 257) + res = _secp.secp256k1_surjectionproof_parse( + context, parsed_proof, proof, len(proof) + ) + if res == 0: + raise RuntimeError("Failed to parse surjection proof") + return parsed_proof diff --git a/bitcoin_client/ledger_bitcoin/embit/util/key.py b/bitcoin_client/ledger_bitcoin/embit/util/key.py new file mode 100644 index 000000000..13b01d955 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/util/key.py @@ -0,0 +1,597 @@ +""" +Copy-paste from key.py in bitcoin test_framework. +This is a fallback option if the library can't do ctypes bindings to secp256k1 library. +""" +import random +import hmac +import hashlib + + +def TaggedHash(tag, data): + ss = hashlib.sha256(tag.encode("utf-8")).digest() + ss += ss + ss += data + return hashlib.sha256(ss).digest() + + +def modinv(a, n): + """Compute the modular inverse of a modulo n using the extended Euclidean + Algorithm. See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers. + """ + # TODO: Change to pow(a, -1, n) available in Python 3.8 + t1, t2 = 0, 1 + r1, r2 = n, a + while r2 != 0: + q = r1 // r2 + t1, t2 = t2, t1 - q * t2 + r1, r2 = r2, r1 - q * r2 + if r1 > 1: + return None + if t1 < 0: + t1 += n + return t1 + + +def xor_bytes(b0, b1): + return bytes(x ^ y for (x, y) in zip(b0, b1)) + + +def jacobi_symbol(n, k): + """Compute the Jacobi symbol of n modulo k + + See http://en.wikipedia.org/wiki/Jacobi_symbol + + For our application k is always prime, so this is the same as the Legendre symbol. + """ + assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k" + n %= k + t = 0 + while n != 0: + while n & 1 == 0: + n >>= 1 + r = k & 7 + t ^= r == 3 or r == 5 + n, k = k, n + t ^= n & k & 3 == 3 + n = n % k + if k == 1: + return -1 if t else 1 + return 0 + + +def modsqrt(a, p): + """Compute the square root of a modulo p when p % 4 = 3. + + The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm + + Limiting this function to only work for p % 4 = 3 means we don't need to + iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd + is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4) + + secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4. + """ + if p % 4 != 3: + raise NotImplementedError("modsqrt only implemented for p % 4 = 3") + sqrt = pow(a, (p + 1) // 4, p) + if pow(sqrt, 2, p) == a % p: + return sqrt + return None + + +class EllipticCurve: + def __init__(self, p, a, b): + """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p).""" + self.p = p + self.a = a % p + self.b = b % p + + def affine(self, p1): + """Convert a Jacobian point tuple p1 to affine form, or None if at infinity. + + An affine point is represented as the Jacobian (x, y, 1)""" + x1, y1, z1 = p1 + if z1 == 0: + return None + inv = modinv(z1, self.p) + inv_2 = (inv**2) % self.p + inv_3 = (inv_2 * inv) % self.p + return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1) + + def has_even_y(self, p1): + """Whether the point p1 has an even Y coordinate when expressed in affine coordinates.""" + return not (p1[2] == 0 or self.affine(p1)[1] & 1) + + def negate(self, p1): + """Negate a Jacobian point tuple p1.""" + x1, y1, z1 = p1 + return (x1, (self.p - y1) % self.p, z1) + + def on_curve(self, p1): + """Determine whether a Jacobian tuple p is on the curve (and not infinity)""" + x1, y1, z1 = p1 + z2 = pow(z1, 2, self.p) + z4 = pow(z2, 2, self.p) + return ( + z1 != 0 + and ( + pow(x1, 3, self.p) + + self.a * x1 * z4 + + self.b * z2 * z4 + - pow(y1, 2, self.p) + ) + % self.p + == 0 + ) + + def is_x_coord(self, x): + """Test whether x is a valid X coordinate on the curve.""" + x_3 = pow(x, 3, self.p) + return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1 + + def lift_x(self, x): + """Given an X coordinate on the curve, return a corresponding affine point for which the Y coordinate is even.""" + x_3 = pow(x, 3, self.p) + v = x_3 + self.a * x + self.b + y = modsqrt(v, self.p) + if y is None: + return None + return (x, self.p - y if y & 1 else y, 1) + + def double(self, p1): + """Double a Jacobian tuple p1 + + See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling + """ + x1, y1, z1 = p1 + if z1 == 0: + return (0, 1, 0) + y1_2 = (y1**2) % self.p + y1_4 = (y1_2**2) % self.p + x1_2 = (x1**2) % self.p + s = (4 * x1 * y1_2) % self.p + m = 3 * x1_2 + if self.a: + m += self.a * pow(z1, 4, self.p) + m = m % self.p + x2 = (m**2 - 2 * s) % self.p + y2 = (m * (s - x2) - 8 * y1_4) % self.p + z2 = (2 * y1 * z1) % self.p + return (x2, y2, z2) + + def add_mixed(self, p1, p2): + """Add a Jacobian tuple p1 and an affine tuple p2 + + See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point) + """ + x1, y1, z1 = p1 + x2, y2, z2 = p2 + assert z2 == 1 + # Adding to the point at infinity is a no-op + if z1 == 0: + return p2 + z1_2 = (z1**2) % self.p + z1_3 = (z1_2 * z1) % self.p + u2 = (x2 * z1_2) % self.p + s2 = (y2 * z1_3) % self.p + if x1 == u2: + if y1 != s2: + # p1 and p2 are inverses. Return the point at infinity. + return (0, 1, 0) + # p1 == p2. The formulas below fail when the two points are equal. + return self.double(p1) + h = u2 - x1 + r = s2 - y1 + h_2 = (h**2) % self.p + h_3 = (h_2 * h) % self.p + u1_h_2 = (x1 * h_2) % self.p + x3 = (r**2 - h_3 - 2 * u1_h_2) % self.p + y3 = (r * (u1_h_2 - x3) - y1 * h_3) % self.p + z3 = (h * z1) % self.p + return (x3, y3, z3) + + def add(self, p1, p2): + """Add two Jacobian tuples p1 and p2 + + See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition + """ + x1, y1, z1 = p1 + x2, y2, z2 = p2 + # Adding the point at infinity is a no-op + if z1 == 0: + return p2 + if z2 == 0: + return p1 + # Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1 + if z1 == 1: + return self.add_mixed(p2, p1) + if z2 == 1: + return self.add_mixed(p1, p2) + z1_2 = (z1**2) % self.p + z1_3 = (z1_2 * z1) % self.p + z2_2 = (z2**2) % self.p + z2_3 = (z2_2 * z2) % self.p + u1 = (x1 * z2_2) % self.p + u2 = (x2 * z1_2) % self.p + s1 = (y1 * z2_3) % self.p + s2 = (y2 * z1_3) % self.p + if u1 == u2: + if s1 != s2: + # p1 and p2 are inverses. Return the point at infinity. + return (0, 1, 0) + # p1 == p2. The formulas below fail when the two points are equal. + return self.double(p1) + h = u2 - u1 + r = s2 - s1 + h_2 = (h**2) % self.p + h_3 = (h_2 * h) % self.p + u1_h_2 = (u1 * h_2) % self.p + x3 = (r**2 - h_3 - 2 * u1_h_2) % self.p + y3 = (r * (u1_h_2 - x3) - s1 * h_3) % self.p + z3 = (h * z1 * z2) % self.p + return (x3, y3, z3) + + def mul(self, ps): + """Compute a (multi) point multiplication + + ps is a list of (Jacobian tuple, scalar) pairs. + """ + r = (0, 1, 0) + for i in range(255, -1, -1): + r = self.double(r) + for p, n in ps: + if (n >> i) & 1: + r = self.add(r, p) + return r + + +SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977 +SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7) +SECP256K1_G = ( + 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, + 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, + 1, +) +SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 + + +class ECPubKey: + """A secp256k1 public key""" + + def __init__(self): + """Construct an uninitialized public key""" + self.valid = False + + def set(self, data): + """Construct a public key from a serialization in compressed or uncompressed format""" + if len(data) == 65 and data[0] == 0x04: + p = ( + int.from_bytes(data[1:33], "big"), + int.from_bytes(data[33:65], "big"), + 1, + ) + self.valid = SECP256K1.on_curve(p) + if self.valid: + self.p = p + self.compressed = False + elif len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03): + x = int.from_bytes(data[1:33], "big") + if SECP256K1.is_x_coord(x): + p = SECP256K1.lift_x(x) + # Make the Y coordinate odd if required (lift_x always produces + # a point with an even Y coordinate). + if data[0] & 1: + p = SECP256K1.negate(p) + self.p = p + self.valid = True + self.compressed = True + else: + self.valid = False + else: + self.valid = False + + @property + def is_compressed(self): + return self.compressed + + @property + def is_valid(self): + return self.valid + + def get_bytes(self): + assert self.valid + p = SECP256K1.affine(self.p) + if p is None: + return None + if self.compressed: + return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, "big") + else: + return bytes([0x04]) + p[0].to_bytes(32, "big") + p[1].to_bytes(32, "big") + + def verify_ecdsa(self, sig, msg, low_s=True): + """Verify a strictly DER-encoded ECDSA signature against this pubkey. + + See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the + ECDSA verifier algorithm""" + assert self.valid + + # Extract r and s from the DER formatted signature. Return false for + # any DER encoding errors. + if sig[1] + 2 != len(sig): + return False + if len(sig) < 4: + return False + if sig[0] != 0x30: + return False + if sig[2] != 0x02: + return False + rlen = sig[3] + if len(sig) < 6 + rlen: + return False + if rlen < 1 or rlen > 33: + return False + if sig[4] >= 0x80: + return False + if rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80): + return False + r = int.from_bytes(sig[4 : 4 + rlen], "big") + if sig[4 + rlen] != 0x02: + return False + slen = sig[5 + rlen] + if slen < 1 or slen > 33: + return False + if len(sig) != 6 + rlen + slen: + return False + if sig[6 + rlen] >= 0x80: + return False + if slen > 1 and (sig[6 + rlen] == 0) and not (sig[7 + rlen] & 0x80): + return False + s = int.from_bytes(sig[6 + rlen : 6 + rlen + slen], "big") + + # Verify that r and s are within the group order + if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER: + return False + if low_s and s >= SECP256K1_ORDER_HALF: + return False + z = int.from_bytes(msg, "big") + + # Run verifier algorithm on r, s + w = modinv(s, SECP256K1_ORDER) + u1 = z * w % SECP256K1_ORDER + u2 = r * w % SECP256K1_ORDER + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)])) + if R is None or (R[0] % SECP256K1_ORDER) != r: + return False + return True + + +def generate_privkey(): + """Generate a valid random 32-byte private key.""" + return random.randrange(1, SECP256K1_ORDER).to_bytes(32, "big") + + +class ECKey: + """A secp256k1 private key""" + + def __init__(self): + self.valid = False + + def set(self, secret, compressed): + """Construct a private key object with given 32-byte secret and compressed flag.""" + assert len(secret) == 32 + secret = int.from_bytes(secret, "big") + self.valid = secret > 0 and secret < SECP256K1_ORDER + if self.valid: + self.secret = secret + self.compressed = compressed + + def generate(self, compressed=True): + """Generate a random private key (compressed or uncompressed).""" + self.set(generate_privkey(), compressed) + + def get_bytes(self): + """Retrieve the 32-byte representation of this key.""" + assert self.valid + return self.secret.to_bytes(32, "big") + + @property + def is_valid(self): + return self.valid + + @property + def is_compressed(self): + return self.compressed + + def get_pubkey(self): + """Compute an ECPubKey object for this secret key.""" + assert self.valid + ret = ECPubKey() + p = SECP256K1.mul([(SECP256K1_G, self.secret)]) + ret.p = p + ret.valid = True + ret.compressed = self.compressed + return ret + + def sign_ecdsa(self, msg, nonce_function=None, extra_data=None, low_s=True): + """Construct a DER-encoded ECDSA signature with this key. + + See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the + ECDSA signer algorithm.""" + assert self.valid + z = int.from_bytes(msg, "big") + if nonce_function is None: + nonce_function = deterministic_k + k = nonce_function(self.secret, z, extra_data=extra_data) + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) + r = R[0] % SECP256K1_ORDER + s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER + if low_s and s > SECP256K1_ORDER_HALF: + s = SECP256K1_ORDER - s + # Represent in DER format. The byte representations of r and s have + # length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33 + # bytes). + rb = r.to_bytes((r.bit_length() + 8) // 8, "big") + sb = s.to_bytes((s.bit_length() + 8) // 8, "big") + return ( + b"\x30" + + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + + rb + + bytes([2, len(sb)]) + + sb + ) + + +def deterministic_k(secret, z, extra_data=None): + # RFC6979, optimized for secp256k1 + k = b"\x00" * 32 + v = b"\x01" * 32 + if z > SECP256K1_ORDER: + z -= SECP256K1_ORDER + z_bytes = z.to_bytes(32, "big") + secret_bytes = secret.to_bytes(32, "big") + if extra_data is not None: + z_bytes += extra_data + k = hmac.new(k, v + b"\x00" + secret_bytes + z_bytes, "sha256").digest() + v = hmac.new(k, v, "sha256").digest() + k = hmac.new(k, v + b"\x01" + secret_bytes + z_bytes, "sha256").digest() + v = hmac.new(k, v, "sha256").digest() + while True: + v = hmac.new(k, v, "sha256").digest() + candidate = int.from_bytes(v, "big") + if candidate >= 1 and candidate < SECP256K1_ORDER: + return candidate + k = hmac.new(k, v + b"\x00", "sha256").digest() + v = hmac.new(k, v, "sha256").digest() + + +def compute_xonly_pubkey(key): + """Compute an x-only (32 byte) public key from a (32 byte) private key. + + This also returns whether the resulting public key was negated. + """ + + assert len(key) == 32 + x = int.from_bytes(key, "big") + if x == 0 or x >= SECP256K1_ORDER: + return (None, None) + P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, x)])) + return (P[0].to_bytes(32, "big"), not SECP256K1.has_even_y(P)) + + +def tweak_add_privkey(key, tweak): + """Tweak a private key (after negating it if needed).""" + + assert len(key) == 32 + assert len(tweak) == 32 + + x = int.from_bytes(key, "big") + if x == 0 or x >= SECP256K1_ORDER: + return None + if not SECP256K1.has_even_y(SECP256K1.mul([(SECP256K1_G, x)])): + x = SECP256K1_ORDER - x + t = int.from_bytes(tweak, "big") + if t >= SECP256K1_ORDER: + return None + x = (x + t) % SECP256K1_ORDER + if x == 0: + return None + return x.to_bytes(32, "big") + + +def tweak_add_pubkey(key, tweak): + """Tweak a public key and return whether the result had to be negated.""" + + assert len(key) == 32 + assert len(tweak) == 32 + + x_coord = int.from_bytes(key, "big") + if x_coord >= SECP256K1_FIELD_SIZE: + return None + P = SECP256K1.lift_x(x_coord) + if P is None: + return None + t = int.from_bytes(tweak, "big") + if t >= SECP256K1_ORDER: + return None + Q = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, t), (P, 1)])) + if Q is None: + return None + return (Q[0].to_bytes(32, "big"), not SECP256K1.has_even_y(Q)) + + +def verify_schnorr(key, sig, msg): + """Verify a Schnorr signature (see BIP 340). + - key is a 32-byte xonly pubkey (computed using compute_xonly_pubkey). + - sig is a 64-byte Schnorr signature + - msg is a 32-byte message + """ + assert len(key) == 32 + assert len(msg) == 32 + assert len(sig) == 64 + + x_coord = int.from_bytes(key, "big") + if x_coord == 0 or x_coord >= SECP256K1_FIELD_SIZE: + return False + P = SECP256K1.lift_x(x_coord) + if P is None: + return False + r = int.from_bytes(sig[0:32], "big") + if r >= SECP256K1_FIELD_SIZE: + return False + s = int.from_bytes(sig[32:64], "big") + if s >= SECP256K1_ORDER: + return False + e = ( + int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), "big") + % SECP256K1_ORDER + ) + R = SECP256K1.mul([(SECP256K1_G, s), (P, SECP256K1_ORDER - e)]) + if not SECP256K1.has_even_y(R): + return False + if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]: + return False + return True + + +def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False): + """Create a Schnorr signature (see BIP 340).""" + + assert len(key) == 32 + assert len(msg) == 32 + if aux is not None: + assert len(aux) == 32 + + sec = int.from_bytes(key, "big") + if sec == 0 or sec >= SECP256K1_ORDER: + return None + P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)])) + if SECP256K1.has_even_y(P) == flip_p: + sec = SECP256K1_ORDER - sec + if aux is not None: + t = (sec ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), "big")).to_bytes( + 32, "big" + ) + else: + t = sec.to_bytes(32, "big") + kp = ( + int.from_bytes( + TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, "big") + msg), "big" + ) + % SECP256K1_ORDER + ) + assert kp != 0 + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)])) + k = kp if SECP256K1.has_even_y(R) != flip_r else SECP256K1_ORDER - kp + e = ( + int.from_bytes( + TaggedHash( + "BIP0340/challenge", + R[0].to_bytes(32, "big") + P[0].to_bytes(32, "big") + msg, + ), + "big", + ) + % SECP256K1_ORDER + ) + return R[0].to_bytes(32, "big") + ((k + e * sec) % SECP256K1_ORDER).to_bytes( + 32, "big" + ) diff --git a/bitcoin_client/ledger_bitcoin/embit/util/py_ripemd160.py b/bitcoin_client/ledger_bitcoin/embit/util/py_ripemd160.py new file mode 100644 index 000000000..7eeaa56ca --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/util/py_ripemd160.py @@ -0,0 +1,407 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Pure Python RIPEMD160 implementation.""" + +# Message schedule indexes for the left path. +ML = [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 7, + 4, + 13, + 1, + 10, + 6, + 15, + 3, + 12, + 0, + 9, + 5, + 2, + 14, + 11, + 8, + 3, + 10, + 14, + 4, + 9, + 15, + 8, + 1, + 2, + 7, + 0, + 6, + 13, + 11, + 5, + 12, + 1, + 9, + 11, + 10, + 0, + 8, + 12, + 4, + 13, + 3, + 7, + 15, + 14, + 5, + 6, + 2, + 4, + 0, + 5, + 9, + 7, + 12, + 2, + 10, + 14, + 1, + 3, + 8, + 11, + 6, + 15, + 13, +] + +# Message schedule indexes for the right path. +MR = [ + 5, + 14, + 7, + 0, + 9, + 2, + 11, + 4, + 13, + 6, + 15, + 8, + 1, + 10, + 3, + 12, + 6, + 11, + 3, + 7, + 0, + 13, + 5, + 10, + 14, + 15, + 8, + 12, + 4, + 9, + 1, + 2, + 15, + 5, + 1, + 3, + 7, + 14, + 6, + 9, + 11, + 8, + 12, + 2, + 10, + 0, + 4, + 13, + 8, + 6, + 4, + 1, + 3, + 11, + 15, + 0, + 5, + 12, + 2, + 13, + 9, + 7, + 10, + 14, + 12, + 15, + 10, + 4, + 1, + 5, + 8, + 7, + 6, + 2, + 13, + 14, + 0, + 3, + 9, + 11, +] + +# Rotation counts for the left path. +RL = [ + 11, + 14, + 15, + 12, + 5, + 8, + 7, + 9, + 11, + 13, + 14, + 15, + 6, + 7, + 9, + 8, + 7, + 6, + 8, + 13, + 11, + 9, + 7, + 15, + 7, + 12, + 15, + 9, + 11, + 7, + 13, + 12, + 11, + 13, + 6, + 7, + 14, + 9, + 13, + 15, + 14, + 8, + 13, + 6, + 5, + 12, + 7, + 5, + 11, + 12, + 14, + 15, + 14, + 15, + 9, + 8, + 9, + 14, + 5, + 6, + 8, + 6, + 5, + 12, + 9, + 15, + 5, + 11, + 6, + 8, + 13, + 12, + 5, + 12, + 13, + 14, + 11, + 8, + 5, + 6, +] + +# Rotation counts for the right path. +RR = [ + 8, + 9, + 9, + 11, + 13, + 15, + 15, + 5, + 7, + 7, + 8, + 11, + 14, + 14, + 12, + 6, + 9, + 13, + 15, + 7, + 12, + 8, + 9, + 11, + 7, + 7, + 12, + 7, + 6, + 15, + 13, + 11, + 9, + 7, + 15, + 11, + 8, + 6, + 6, + 14, + 12, + 13, + 5, + 14, + 13, + 13, + 7, + 5, + 15, + 5, + 8, + 11, + 14, + 14, + 6, + 14, + 6, + 9, + 12, + 9, + 12, + 5, + 15, + 8, + 8, + 5, + 12, + 9, + 12, + 5, + 14, + 6, + 8, + 13, + 6, + 5, + 15, + 13, + 11, + 11, +] + +# K constants for the left path. +KL = [0, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E] + +# K constants for the right path. +KR = [0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0] + + +def fi(x, y, z, i): + """The f1, f2, f3, f4, and f5 functions from the specification.""" + if i == 0: + return x ^ y ^ z + elif i == 1: + return (x & y) | (~x & z) + elif i == 2: + return (x | ~y) ^ z + elif i == 3: + return (x & z) | (y & ~z) + elif i == 4: + return x ^ (y | ~z) + else: + assert False + + +def rol(x, i): + """Rotate the bottom 32 bits of x left by i bits.""" + return ((x << i) | ((x & 0xFFFFFFFF) >> (32 - i))) & 0xFFFFFFFF + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [int.from_bytes(block[4 * i : 4 * (i + 1)], "little") for i in range(16)] + + # Iterate over the 80 rounds of the compression. + for j in range(80): + rnd = j >> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + state = compress(*state, data[64 * b : 64 * (b + 1)]) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63 :] + pad + (8 * len(data)).to_bytes(8, "little") + # Process final blocks. + for b in range(len(fin) >> 6): + state = compress(*state, fin[64 * b : 64 * (b + 1)]) + # Produce output. + return b"".join((h & 0xFFFFFFFF).to_bytes(4, "little") for h in state) diff --git a/bitcoin_client/ledger_bitcoin/embit/util/py_secp256k1.py b/bitcoin_client/ledger_bitcoin/embit/util/py_secp256k1.py new file mode 100644 index 000000000..851408635 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/util/py_secp256k1.py @@ -0,0 +1,384 @@ +""" +This is a fallback option if the library can't do ctypes bindings to secp256k1 library. +Mimics the micropython bindings and internal representation of data structs in secp256k1. +""" + +from . import key as _key + +# Flags to pass to context_create. +CONTEXT_VERIFY = 0b0100000001 +CONTEXT_SIGN = 0b1000000001 +CONTEXT_NONE = 0b0000000001 + +# Flags to pass to ec_pubkey_serialize +EC_COMPRESSED = 0b0100000010 +EC_UNCOMPRESSED = 0b0000000010 + + +def context_randomize(seed, context=None): + pass + + +def _reverse64(b): + """Converts (a,b) from big to little endian to be consistent with secp256k1""" + x = b[:32] + y = b[32:] + return x[::-1] + y[::-1] + + +def _pubkey_serialize(pub): + """Returns pubkey representation like secp library""" + b = pub.get_bytes()[1:] + return _reverse64(b) + + +def _pubkey_parse(b): + """Returns pubkey class instance""" + pub = _key.ECPubKey() + pub.set(b"\x04" + _reverse64(b)) + return pub + + +def ec_pubkey_create(secret, context=None): + if len(secret) != 32: + raise ValueError("Private key should be 32 bytes long") + pk = _key.ECKey() + pk.set(secret, compressed=False) + if not pk.is_valid: + raise ValueError("Invalid private key") + return _pubkey_serialize(pk.get_pubkey()) + + +def ec_pubkey_parse(sec, context=None): + if len(sec) != 33 and len(sec) != 65: + raise ValueError("Serialized pubkey should be 33 or 65 bytes long") + if len(sec) == 33: + if sec[0] != 0x02 and sec[0] != 0x03: + raise ValueError("Compressed pubkey should start with 0x02 or 0x03") + else: + if sec[0] != 0x04: + raise ValueError("Uncompressed pubkey should start with 0x04") + pub = _key.ECPubKey() + pub.set(sec) + pub.compressed = False + if not pub.is_valid: + raise ValueError("Failed parsing public key") + return _pubkey_serialize(pub) + + +def ec_pubkey_serialize(pubkey, flag=EC_COMPRESSED, context=None): + if len(pubkey) != 64: + raise ValueError("Pubkey should be 64 bytes long") + if flag not in [EC_COMPRESSED, EC_UNCOMPRESSED]: + raise ValueError("Invalid flag") + pub = _pubkey_parse(pubkey) + if not pub.is_valid: + raise ValueError("Failed to serialize pubkey") + if flag == EC_COMPRESSED: + pub.compressed = True + return pub.get_bytes() + + +def ecdsa_signature_parse_compact(compact_sig, context=None): + if len(compact_sig) != 64: + raise ValueError("Compact signature should be 64 bytes long") + sig = _reverse64(compact_sig) + return sig + + +def ecdsa_signature_parse_der(der, context=None): + if der[1] + 2 != len(der): + raise ValueError("Failed parsing compact signature") + if len(der) < 4: + raise ValueError("Failed parsing compact signature") + if der[0] != 0x30: + raise ValueError("Failed parsing compact signature") + if der[2] != 0x02: + raise ValueError("Failed parsing compact signature") + rlen = der[3] + if len(der) < 6 + rlen: + raise ValueError("Failed parsing compact signature") + if rlen < 1 or rlen > 33: + raise ValueError("Failed parsing compact signature") + if der[4] >= 0x80: + raise ValueError("Failed parsing compact signature") + if rlen > 1 and (der[4] == 0) and not (der[5] & 0x80): + raise ValueError("Failed parsing compact signature") + r = int.from_bytes(der[4 : 4 + rlen], "big") + if der[4 + rlen] != 0x02: + raise ValueError("Failed parsing compact signature") + slen = der[5 + rlen] + if slen < 1 or slen > 33: + raise ValueError("Failed parsing compact signature") + if len(der) != 6 + rlen + slen: + raise ValueError("Failed parsing compact signature") + if der[6 + rlen] >= 0x80: + raise ValueError("Failed parsing compact signature") + if slen > 1 and (der[6 + rlen] == 0) and not (der[7 + rlen] & 0x80): + raise ValueError("Failed parsing compact signature") + s = int.from_bytes(der[6 + rlen : 6 + rlen + slen], "big") + + # Verify that r and s are within the group order + if r < 1 or s < 1 or r >= _key.SECP256K1_ORDER or s >= _key.SECP256K1_ORDER: + raise ValueError("Failed parsing compact signature") + if s >= _key.SECP256K1_ORDER_HALF: + raise ValueError("Failed parsing compact signature") + + return r.to_bytes(32, "little") + s.to_bytes(32, "little") + + +def ecdsa_signature_serialize_der(sig, context=None): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + r = int.from_bytes(sig[:32], "little") + s = int.from_bytes(sig[32:], "little") + rb = r.to_bytes((r.bit_length() + 8) // 8, "big") + sb = s.to_bytes((s.bit_length() + 8) // 8, "big") + return ( + b"\x30" + + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + + rb + + bytes([2, len(sb)]) + + sb + ) + + +def ecdsa_signature_serialize_compact(sig, context=None): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + return _reverse64(sig) + + +def ecdsa_signature_normalize(sig, context=None): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + r = int.from_bytes(sig[:32], "little") + s = int.from_bytes(sig[32:], "little") + if s >= _key.SECP256K1_ORDER_HALF: + s = _key.SECP256K1_ORDER - s + return r.to_bytes(32, "little") + s.to_bytes(32, "little") + + +def ecdsa_verify(sig, msg, pub, context=None): + if len(sig) != 64: + raise ValueError("Signature should be 64 bytes long") + if len(msg) != 32: + raise ValueError("Message should be 32 bytes long") + if len(pub) != 64: + raise ValueError("Public key should be 64 bytes long") + pubkey = _pubkey_parse(pub) + return pubkey.verify_ecdsa(ecdsa_signature_serialize_der(sig), msg) + + +def ecdsa_sign(msg, secret, nonce_function=None, extra_data=None, context=None): + if len(msg) != 32: + raise ValueError("Message should be 32 bytes long") + if len(secret) != 32: + raise ValueError("Secret key should be 32 bytes long") + pk = _key.ECKey() + pk.set(secret, False) + sig = pk.sign_ecdsa(msg, nonce_function, extra_data) + return ecdsa_signature_parse_der(sig) + + +def ec_seckey_verify(secret, context=None): + if len(secret) != 32: + raise ValueError("Secret should be 32 bytes long") + pk = _key.ECKey() + pk.set(secret, compressed=False) + return pk.is_valid + + +def ec_privkey_negate(secret, context=None): + # negate in place + if len(secret) != 32: + raise ValueError("Secret should be 32 bytes long") + s = int.from_bytes(secret, "big") + s2 = _key.SECP256K1_ORDER - s + return s2.to_bytes(32, "big") + + +def ec_pubkey_negate(pubkey, context=None): + if len(pubkey) != 64: + raise ValueError("Pubkey should be a 64-byte structure") + sec = ec_pubkey_serialize(pubkey) + return ec_pubkey_parse(bytes([0x05 - sec[0]]) + sec[1:]) + + +def ec_privkey_tweak_add(secret, tweak, context=None): + res = ec_privkey_add(secret, tweak) + for i in range(len(secret)): + secret[i] = res[i] + + +def ec_pubkey_tweak_add(pub, tweak, context=None): + res = ec_pubkey_add(pub, tweak) + for i in range(len(pub)): + pub[i] = res[i] + + +def ec_privkey_add(secret, tweak, context=None): + if len(secret) != 32 or len(tweak) != 32: + raise ValueError("Secret and tweak should both be 32 bytes long") + s = int.from_bytes(secret, "big") + t = int.from_bytes(tweak, "big") + r = (s + t) % _key.SECP256K1_ORDER + return r.to_bytes(32, "big") + + +def ec_pubkey_add(pub, tweak, context=None): + if len(pub) != 64: + raise ValueError("Public key should be 64 bytes long") + if len(tweak) != 32: + raise ValueError("Tweak should be 32 bytes long") + pubkey = _pubkey_parse(pub) + pubkey.compressed = True + t = int.from_bytes(tweak, "big") + Q = _key.SECP256K1.affine( + _key.SECP256K1.mul([(_key.SECP256K1_G, t), (pubkey.p, 1)]) + ) + if Q is None: + return None + return Q[0].to_bytes(32, "little") + Q[1].to_bytes(32, "little") + + +# def ec_privkey_tweak_mul(secret, tweak, context=None): +# if len(secret)!=32 or len(tweak)!=32: +# raise ValueError("Secret and tweak should both be 32 bytes long") +# s = int.from_bytes(secret, 'big') +# t = int.from_bytes(tweak, 'big') +# if t > _key.SECP256K1_ORDER or s > _key.SECP256K1_ORDER: +# raise ValueError("Failed to tweak the secret") +# r = pow(s, t, _key.SECP256K1_ORDER) +# res = r.to_bytes(32, 'big') +# for i in range(len(secret)): +# secret[i] = res[i] + +# def ec_pubkey_tweak_mul(pub, tweak, context=None): +# if len(pub)!=64: +# raise ValueError("Public key should be 64 bytes long") +# if len(tweak)!=32: +# raise ValueError("Tweak should be 32 bytes long") +# if _secp.secp256k1_ec_pubkey_tweak_mul(context, pub, tweak) == 0: +# raise ValueError("Failed to tweak the public key") + +# def ec_pubkey_combine(*args, context=None): +# pub = bytes(64) +# pubkeys = (c_char_p * len(args))(*args) +# r = _secp.secp256k1_ec_pubkey_combine(context, pub, pubkeys, len(args)) +# if r == 0: +# raise ValueError("Failed to negate pubkey") +# return pub + +# schnorrsig + + +def xonly_pubkey_from_pubkey(pubkey, context=None): + if len(pubkey) != 64: + raise ValueError("Pubkey should be 64 bytes long") + sec = ec_pubkey_serialize(pubkey) + parity = sec[0] == 0x03 + pub = ec_pubkey_parse(b"\x02" + sec[1:33]) + return pub, parity + + +def schnorrsig_verify(sig, msg, pubkey, context=None): + assert len(sig) == 64 + assert len(msg) == 32 + assert len(pubkey) == 64 + sec = ec_pubkey_serialize(pubkey) + return _key.verify_schnorr(sec[1:33], sig, msg) + + +def keypair_create(secret, context=None): + pub = ec_pubkey_create(secret) + pub2, parity = xonly_pubkey_from_pubkey(pub) + keypair = secret + pub + return keypair + + +def schnorrsig_sign(msg, keypair, nonce_function=None, extra_data=None, context=None): + assert len(msg) == 32 + if len(keypair) == 32: + keypair = keypair_create(keypair, context=context) + assert len(keypair) == 96 + return _key.sign_schnorr(keypair[:32], msg, extra_data) + + +# recoverable + + +def ecdsa_sign_recoverable(msg, secret, context=None): + sig = ecdsa_sign(msg, secret) + pub = ec_pubkey_create(secret) + # Search for correct index. Not efficient but I am lazy. + # For efficiency use c-bindings to libsecp256k1 + for i in range(4): + if ecdsa_recover(sig + bytes([i]), msg) == pub: + return sig + bytes([i]) + raise ValueError("Failed to sign") + + +def ecdsa_recoverable_signature_serialize_compact(sig, context=None): + if len(sig) != 65: + raise ValueError("Recoverable signature should be 65 bytes long") + compact = ecdsa_signature_serialize_compact(sig[:64]) + return compact, sig[64] + + +def ecdsa_recoverable_signature_parse_compact(compact_sig, recid, context=None): + if len(compact_sig) != 64: + raise ValueError("Signature should be 64 bytes long") + # TODO: also check r value so recid > 2 makes sense + if recid < 0 or recid > 4: + raise ValueError("Failed parsing compact signature") + return ecdsa_signature_parse_compact(compact_sig) + bytes([recid]) + + +def ecdsa_recoverable_signature_convert(sigin, context=None): + if len(sigin) != 65: + raise ValueError("Recoverable signature should be 65 bytes long") + return sigin[:64] + + +def ecdsa_recover(sig, msghash, context=None): + if len(sig) != 65: + raise ValueError("Recoverable signature should be 65 bytes long") + if len(msghash) != 32: + raise ValueError("Message should be 32 bytes long") + idx = sig[-1] + r = int.from_bytes(sig[:32], "little") + s = int.from_bytes(sig[32:64], "little") + z = int.from_bytes(msghash, "big") + # r = Rx mod N, so R can be 02x, 03x, 02(N+x), 03(N+x) + # two latter cases only if N+x < P + r_candidates = [ + b"\x02" + r.to_bytes(32, "big"), + b"\x03" + r.to_bytes(32, "big"), + ] + if r + _key.SECP256K1_ORDER < _key.SECP256K1_FIELD_SIZE: + r2 = r + _key.SECP256K1_ORDER + r_candidates = r_candidates + [ + b"\x02" + r2.to_bytes(32, "big"), + b"\x03" + r2.to_bytes(32, "big"), + ] + if idx >= len(r_candidates): + raise ValueError("Failed to recover public key") + R = _key.ECPubKey() + R.set(r_candidates[idx]) + # s = (z + d * r)/k + # (R*s/r - z/r*G) = P + rinv = _key.modinv(r, _key.SECP256K1_ORDER) + u1 = (s * rinv) % _key.SECP256K1_ORDER + u2 = (z * rinv) % _key.SECP256K1_ORDER + P1 = _key.SECP256K1.mul([(R.p, u1)]) + P2 = _key.SECP256K1.negate(_key.SECP256K1.mul([(_key.SECP256K1_G, u2)])) + P = _key.SECP256K1.affine(_key.SECP256K1.add(P1, P2)) + result = P[0].to_bytes(32, "little") + P[1].to_bytes(32, "little") + # verify signature at the end + pubkey = _pubkey_parse(result) + if not pubkey.is_valid: + raise ValueError("Failed to recover public key") + if not ecdsa_verify(sig[:64], msghash, result): + raise ValueError("Failed to recover public key") + return result diff --git a/bitcoin_client/ledger_bitcoin/embit/util/secp256k1.py b/bitcoin_client/ledger_bitcoin/embit/util/secp256k1.py new file mode 100644 index 000000000..a3ed8d9a7 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/embit/util/secp256k1.py @@ -0,0 +1,12 @@ +try: + # if it's micropython + from micropython import const + from secp256k1 import * +except: + # we are in python + try: + # try ctypes bindings + from .ctypes_secp256k1 import * + except: + # fallback to python version + from .py_secp256k1 import * diff --git a/bitcoin_client/ledger_bitcoin/exception/device_exception.py b/bitcoin_client/ledger_bitcoin/exception/device_exception.py index b20a57cc7..7596420ad 100644 --- a/bitcoin_client/ledger_bitcoin/exception/device_exception.py +++ b/bitcoin_client/ledger_bitcoin/exception/device_exception.py @@ -6,12 +6,14 @@ class DeviceException(Exception): # pylint: disable=too-few-public-methods exc: Dict[int, Any] = { + 0x5515: SecurityStatusNotSatisfiedError, # returned by sdk in recent versions 0x6985: DenyError, - 0x6982: SecurityStatusNotSatisfiedError, + 0x6982: SecurityStatusNotSatisfiedError, # used in older app versions 0x6A80: IncorrectDataError, 0x6A82: NotSupportedError, 0x6A86: WrongP1P2Error, 0x6A87: WrongDataLengthError, + 0x6B00: SwapError, 0x6D00: InsNotSupportedError, 0x6E00: ClaNotSupportedError, 0xB000: WrongResponseLengthError, diff --git a/bitcoin_client/ledger_bitcoin/exception/errors.py b/bitcoin_client/ledger_bitcoin/exception/errors.py index e875d08bb..d2ec53aa3 100644 --- a/bitcoin_client/ledger_bitcoin/exception/errors.py +++ b/bitcoin_client/ledger_bitcoin/exception/errors.py @@ -26,6 +26,10 @@ class WrongDataLengthError(Exception): pass +class SwapError(Exception): + pass + + class InsNotSupportedError(Exception): pass diff --git a/bitcoin_client/ledger_bitcoin/psbt.py b/bitcoin_client/ledger_bitcoin/psbt.py index abc40128f..57dac5869 100644 --- a/bitcoin_client/ledger_bitcoin/psbt.py +++ b/bitcoin_client/ledger_bitcoin/psbt.py @@ -19,6 +19,7 @@ Sequence, Set, Tuple, + Union, ) from .key import KeyOriginInfo @@ -1181,7 +1182,6 @@ def convert_to_v0(self) -> None: self.tx = self.get_unsigned_tx() self.explicit_version = False - def _deserialize_proprietary_record(self, f: Readable, key: bytes) -> bool: """ :meta private: @@ -1204,3 +1204,20 @@ def _serialize_proprietary_records(self) -> bytes: :returns: The serialized records or an empty byte string if there are none. """ return b"" + + +def normalize_psbt(psbt: Union[PSBT, bytes, str]) -> PSBT: + """ + Deserializes a psbt given as an argument from a string or a byte array, if necessary. + + :param psbt: Either an instance of PSBT, or binary-encoded psbt as `bytes`, or a base64-encoded psbt as a `str`. + :returns: the deserialized PSBT object. If `psbt` was already a `PSBT`, it is returned directly (without cloning). + """ + if isinstance(psbt, bytes): + psbt = base64.b64encode(psbt).decode() + + if isinstance(psbt, str): + psbt_obj = PSBT() + psbt_obj.deserialize(psbt) + psbt = psbt_obj + return psbt diff --git a/tests-legacy/bitcoin_client/hwi/__init__.py b/bitcoin_client/ledger_bitcoin/py.typed similarity index 100% rename from tests-legacy/bitcoin_client/hwi/__init__.py rename to bitcoin_client/ledger_bitcoin/py.typed diff --git a/bitcoin_client/ledger_bitcoin/ripemd.py b/bitcoin_client/ledger_bitcoin/ripemd.py new file mode 100644 index 000000000..ee08cc387 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/ripemd.py @@ -0,0 +1,115 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Taken from https://github.com/bitcoin/bitcoin/blob/124e75a41ea0f3f0e90b63b0c41813184ddce2ab/test/functional/test_framework/ripemd160.py + +""" +Pure Python RIPEMD160 implementation. + +WARNING: This implementation is NOT constant-time. +Do not use without understanding the implications. +""" + +# Message schedule indexes for the left path. +ML = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +] + +# Message schedule indexes for the right path. +MR = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +] + +# Rotation counts for the left path. +RL = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +] + +# Rotation counts for the right path. +RR = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +] + +# K constants for the left path. +KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] + +# K constants for the right path. +KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] + + +def fi(x, y, z, i): + """The f1, f2, f3, f4, and f5 functions from the specification.""" + if i == 0: + return x ^ y ^ z + elif i == 1: + return (x & y) | (~x & z) + elif i == 2: + return (x | ~y) ^ z + elif i == 3: + return (x & z) | (y & ~z) + elif i == 4: + return x ^ (y | ~z) + else: + assert False + + +def rol(x, i): + """Rotate the bottom 32 bits of x left by i bits.""" + return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] + + # Iterate over the 80 rounds of the compression. + for j in range(80): + rnd = j >> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + state = compress(*state, data[64*b:64*(b+1)]) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') + # Process final blocks. + for b in range(len(fin) >> 6): + state = compress(*state, fin[64*b:64*(b+1)]) + # Produce output. + return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) diff --git a/bitcoin_client/ledger_bitcoin/segwit_addr.py b/bitcoin_client/ledger_bitcoin/segwit_addr.py new file mode 100644 index 000000000..ef4174773 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/segwit_addr.py @@ -0,0 +1,137 @@ +# Copyright (c) 2017, 2020 Pieter Wuille +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" + + +from enum import Enum + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32M_CONST = 0x2bc830a3 + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1ffffff) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + const = bech32_polymod(bech32_hrp_expand(hrp) + data) + if const == 1: + return Encoding.BECH32 + if const == BECH32M_CONST: + return Encoding.BECH32M + return None + +def bech32_create_checksum(hrp, data, spec): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + const = BECH32M_CONST if spec == Encoding.BECH32M else 1 + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(hrp, data, spec): + """Compute a Bech32 string given HRP and data values.""" + combined = data + bech32_create_checksum(hrp, data, spec) + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) + +def bech32_decode(bech): + """Validate a Bech32/Bech32m string, and determine HRP and data.""" + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): + return (None, None, None) + bech = bech.lower() + pos = bech.rfind('1') + if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: + return (None, None, None) + if not all(x in CHARSET for x in bech[pos+1:]): + return (None, None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos+1:]] + spec = bech32_verify_checksum(hrp, data) + if spec is None: + return (None, None, None) + return (hrp, data[:-6], spec) + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + hrpgot, data, spec = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2 or len(decoded) > 40: + return (None, None) + if data[0] > 16: + return (None, None) + if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: + return (None, None) + if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M: + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec) + if decode(hrp, ret) == (None, None): + return None + return ret \ No newline at end of file diff --git a/bitcoin_client/ledger_bitcoin/transport.py b/bitcoin_client/ledger_bitcoin/transport.py new file mode 100644 index 000000000..ed0b9bf43 --- /dev/null +++ b/bitcoin_client/ledger_bitcoin/transport.py @@ -0,0 +1,263 @@ +# extracted from ledgercomm in order to add the `path` parameter to the constructor. + +import enum +import logging +import struct +from typing import Union, Tuple, Optional, Literal, cast + +from ledgercomm.interfaces.tcp_client import TCPClient +from ledgercomm.interfaces.hid_device import HID +from ledgercomm.log import LOG + + +class TransportType(enum.Enum): + """Type of interface available.""" + + HID = 1 + TCP = 2 + + +class Transport: + """Transport class to send APDUs. + + Allow to communicate using HID device such as Nano S/X or through TCP + socket with the Speculos emulator. + + Parameters + ---------- + interface : str + Either "hid" or "tcp" for the underlying communication interface. + server : str + IP address of the TCP server if interface is "tcp". + port : int + Port of the TCP server if interface is "tcp". + path : Optional[str] + The path to use with HID if interface is "hid"; defaults to `None`. + hid : Optional[HID] + The HID instance to use if interface is "hid"; defaults to `None`. + If not None, the instance is already presumed open. + debug : bool + Whether you want debug logs or not. + + Attributes + ---------- + interface : TransportType + Either TransportType.HID or TransportType.TCP. + com : Union[TCPClient, HID] + Communication interface to send/receive APDUs. + + """ + + def __init__(self, + interface: Literal["hid", "tcp"] = "tcp", + server: str = "127.0.0.1", + port: int = 9999, + path: Optional[str] = None, + hid: Optional[HID] = None, + debug: bool = False) -> None: + """Init constructor of Transport.""" + if debug: + LOG.setLevel(logging.DEBUG) + + self.inferface: TransportType + + try: + self.interface = TransportType[interface.upper()] + except KeyError as exc: + raise Exception(f"Unknown interface '{interface}'!") from exc + + if self.interface == TransportType.TCP: + self.com = TCPClient( + server=server, port=port) + self.com.open() + else: + if hid is not None: + self.com = hid + # we assume the instance is already open, when the `hid` parameter is given + else: + self.com = HID() + self.com.path = path + self.com.open() + + @staticmethod + def apdu_header(cla: int, + ins: Union[int, enum.IntEnum], + p1: int = 0, + p2: int = 0, + opt: Optional[int] = None, + lc: int = 0) -> bytes: + """Pack the APDU header as bytes. + + Parameters + ---------- + cla : int + Instruction class: CLA (1 byte) + ins : Union[int, IntEnum] + Instruction code: INS (1 byte) + p1 : int + Instruction parameter: P1 (1 byte). + p2 : int + Instruction parameter: P2 (1 byte). + opt : Optional[int] + Optional parameter: Opt (1 byte). + lc : int + Number of bytes in the payload: Lc (1 byte). + + Returns + ------- + bytes + APDU header packed with parameters. + + """ + ins = cast(int, ins.value) if isinstance( + ins, enum.IntEnum) else cast(int, ins) + + if opt: + return struct.pack("BBBBBB", + cla, + ins, + p1, + p2, + 1 + lc, # add option to length + opt) + + return struct.pack("BBBBB", + cla, + ins, + p1, + p2, + lc) + + def send(self, + cla: int, + ins: Union[int, enum.IntEnum], + p1: int = 0, + p2: int = 0, + option: Optional[int] = None, + cdata: bytes = b"") -> int: + """Send structured APDUs through `self.com`. + + Parameters + ---------- + cla : int + Instruction class: CLA (1 byte) + ins : Union[int, IntEnum] + Instruction code: INS (1 byte) + p1 : int + Instruction parameter: P1 (1 byte). + p2 : int + Instruction parameter: P2 (1 byte). + option : Optional[int] + Optional parameter: Opt (1 byte). + cdata : bytes + Command data (variable length). + + Returns + ------- + int + Total lenght of the APDU sent. + + """ + header: bytes = Transport.apdu_header( + cla, ins, p1, p2, option, len(cdata)) + + return self.com.send(header + cdata) + + def send_raw(self, apdu: Union[str, bytes]) -> int: + """Send raw bytes `apdu` through `self.com`. + + Parameters + ---------- + apdu : Union[str, bytes] + Hexstring or bytes within APDU to be sent through `self.com`. + + Returns + ------- + Optional[int] + Total lenght of APDU sent if any. + + """ + if isinstance(apdu, str): + apdu = bytes.fromhex(apdu) + + return self.com.send(apdu) + + def recv(self) -> Tuple[int, bytes]: + """Receive data from `self.com`. + + Blocking IO. + + Returns + ------- + Tuple[int, bytes] + A pair (sw, rdata) for the status word (2 bytes represented + as int) and the reponse data (variable lenght). + + """ + return self.com.recv() + + def exchange(self, + cla: int, + ins: Union[int, enum.IntEnum], + p1: int = 0, + p2: int = 0, + option: Optional[int] = None, + cdata: bytes = b"") -> Tuple[int, bytes]: + """Send structured APDUs and wait to receive datas from `self.com`. + + Parameters + ---------- + cla : int + Instruction class: CLA (1 byte) + ins : Union[int, IntEnum] + Instruction code: INS (1 byte) + p1 : int + Instruction parameter: P1 (1 byte). + p2 : int + Instruction parameter: P2 (1 byte). + option : Optional[int] + Optional parameter: Opt (1 byte). + cdata : bytes + Command data (variable length). + + Returns + ------- + Tuple[int, bytes] + A pair (sw, rdata) for the status word (2 bytes represented + as int) and the reponse data (bytes of variable lenght). + + """ + header: bytes = Transport.apdu_header( + cla, ins, p1, p2, option, len(cdata)) + + return self.com.exchange(header + cdata) + + def exchange_raw(self, apdu: Union[str, bytes]) -> Tuple[int, bytes]: + """Send raw bytes `apdu` and wait to receive datas from `self.com`. + + Parameters + ---------- + apdu : Union[str, bytes] + Hexstring or bytes within APDU to send through `self.com`. + + Returns + ------- + Tuple[int, bytes] + A pair (sw, rdata) for the status word (2 bytes represented + as int) and the reponse (bytes of variable lenght). + + """ + if isinstance(apdu, str): + apdu = bytes.fromhex(apdu) + + return self.com.exchange(apdu) + + def close(self) -> None: + """Close `self.com` interface. + + Returns + ------- + None + + """ + self.com.close() diff --git a/bitcoin_client/ledger_bitcoin/tx.py b/bitcoin_client/ledger_bitcoin/tx.py index 8cb1710f1..91b1215ff 100644 --- a/bitcoin_client/ledger_bitcoin/tx.py +++ b/bitcoin_client/ledger_bitcoin/tx.py @@ -138,7 +138,7 @@ def is_witness(self) -> Tuple[bool, int, bytes]: def __repr__(self) -> str: return "CTxOut(nValue=%i.%08i scriptPubKey=%s)" \ - % (self.nValue, self.nValue, self.scriptPubKey.hex()) + % (self.nValue // 100_000_000, self.nValue % 100_000_000, self.scriptPubKey.hex()) class CScriptWitness(object): diff --git a/bitcoin_client/ledger_bitcoin/wallet.py b/bitcoin_client/ledger_bitcoin/wallet.py index 9a102355f..6005e96b3 100644 --- a/bitcoin_client/ledger_bitcoin/wallet.py +++ b/bitcoin_client/ledger_bitcoin/wallet.py @@ -1,3 +1,5 @@ +import re + from enum import IntEnum from typing import List @@ -8,18 +10,22 @@ from .merkle import MerkleTree, element_hash class WalletType(IntEnum): - POLICYMAP = 1 + WALLET_POLICY_V1 = 1 + WALLET_POLICY_V2 = 2 # should not be instantiated directly -class Wallet: - def __init__(self, name: str, wallet_type: WalletType) -> None: +class WalletPolicyBase: + def __init__(self, name: str, version: WalletType) -> None: self.name = name - self.type = wallet_type + self.version = version + + if (version != WalletType.WALLET_POLICY_V1 and version != WalletType.WALLET_POLICY_V2): + raise ValueError("Invalid wallet policy version") def serialize(self) -> bytes: return b"".join([ - self.type.value.to_bytes(1, byteorder="big"), + self.version.value.to_bytes(1, byteorder="big"), serialize_str(self.name) ]) @@ -27,28 +33,28 @@ def serialize(self) -> bytes: def id(self) -> bytes: return sha256(self.serialize()).digest() - def hmac(self, wallet_registration_key: bytes | bytearray) -> bytes: + def hmac(self, wallet_registration_key) -> bytes: return hmac.new(wallet_registration_key, self.id, sha256).digest() -class PolicyMapWallet(Wallet): +class WalletPolicy(WalletPolicyBase): """ - Represents a wallet stored with a policy map and a number of keys_info. - The wallet is serialized as follows: - - 1 byte : wallet type - - 1 byte : length of the wallet name (max 16) + Represents a wallet stored with a wallet policy. + For version V2, the wallet is serialized as follows: + - 1 byte : wallet version + - 1 byte : length of the wallet name (max 64) - (var) : wallet name (ASCII string) - - (varint) : length of the policy map, at most 74 bytes at this time - - (var) : policy map + - (varint) : length of the descriptor template + - 32-bytes : sha256 hash of the descriptor template - (varint) : number of keys (not larger than 252) - 32-bytes : root of the Merkle tree of all the keys information. The specific format of the keys is deferred to subclasses. """ - def __init__(self, name: str, policy_map: str, keys_info: List[str]): - super().__init__(name, WalletType.POLICYMAP) - self.policy_map = policy_map + def __init__(self, name: str, descriptor_template: str, keys_info: List[str], version: WalletType = WalletType.WALLET_POLICY_V2): + super().__init__(name, version) + self.descriptor_template = descriptor_template self.keys_info = keys_info @property @@ -56,30 +62,39 @@ def n_keys(self) -> int: return len(self.keys_info) def serialize(self) -> bytes: - keys_info_hashes = map(lambda k: element_hash(k.encode("latin-1")), self.keys_info) + keys_info_hashes = map(lambda k: element_hash(k.encode()), self.keys_info) + + descriptor_template_sha256 = sha256(self.descriptor_template.encode()).digest() return b"".join([ super().serialize(), - write_varint(len(self.policy_map)), - self.policy_map.encode("latin-1"), + write_varint(len(self.descriptor_template.encode())), + self.descriptor_template.encode() if self.version == WalletType.WALLET_POLICY_V1 else descriptor_template_sha256, write_varint(len(self.keys_info)), MerkleTree(keys_info_hashes).root ]) def get_descriptor(self, change: bool) -> str: - desc = self.policy_map + desc = self.descriptor_template for i in reversed(range(self.n_keys)): key = self.keys_info[i] - if "/**" in key: - key = key.replace("/**", f"/{1 if change else 0}/*") desc = desc.replace(f"@{i}", key) + + # in V1, /** is part of the key; in V2, it's part of the policy map. This handles either + desc = desc.replace("/**", f"/{1 if change else 0}/*") + + if self.version == WalletType.WALLET_POLICY_V2: + # V2, the / syntax is supported. Replace with M if not change, or with N if change + regex = r"/<(\d+);(\d+)>" + desc = re.sub(regex, "/\\2" if change else "/\\1", desc) + return desc -class MultisigWallet(PolicyMapWallet): - def __init__(self, name: str, address_type: AddressType, threshold: int, keys_info: List[str], sorted: bool = True) -> None: +class MultisigWallet(WalletPolicy): + def __init__(self, name: str, address_type: AddressType, threshold: int, keys_info: List[str], sorted: bool = True, version: WalletType = WalletType.WALLET_POLICY_V2) -> None: n_keys = len(keys_info) - if not (1 <= threshold <= n_keys <= 15): + if not (1 <= threshold <= n_keys <= 16): raise ValueError("Invalid threshold or number of keys") multisig_op = "sortedmulti" if sorted else "multi" @@ -96,14 +111,16 @@ def __init__(self, name: str, address_type: AddressType, threshold: int, keys_in else: raise ValueError(f"Unexpected address type: {address_type}") - policy_map = "".join([ + key_placeholder_suffix = "/**" if version == WalletType.WALLET_POLICY_V2 else "" + + descriptor_template = "".join([ policy_prefix, str(threshold) + ",", - ",".join("@" + str(l) for l in range(n_keys)), + ",".join("@" + str(l) + key_placeholder_suffix for l in range(n_keys)), policy_suffix ]) - super().__init__(name, policy_map, keys_info) + super().__init__(name, descriptor_template, keys_info, version) self.threshold = threshold @@ -117,7 +134,7 @@ def wrap_ct(policy_map: str, blinding_key: str = ""): return "".join([f"ct({blinding_key},", policy_map, ")"]) -class BlindedWallet(PolicyMapWallet): +class BlindedWallet(WalletPolicy): """Blinded wallet for Liquid application""" def __init__(self, name: str, blinding_key: str, policy_map: str, keys_info: List[str]): diff --git a/bitcoin_client/pyproject.toml b/bitcoin_client/pyproject.toml index f7473453b..5020eb701 100644 --- a/bitcoin_client/pyproject.toml +++ b/bitcoin_client/pyproject.toml @@ -1,8 +1,8 @@ [build-system] requires = [ - "typing-extensions>=3.7", "ledgercomm>=1.1.0", "setuptools>=42", + "typing-extensions>=3.7", "wheel" ] build-backend = "setuptools.build_meta" diff --git a/bitcoin_client/setup.cfg b/bitcoin_client/setup.cfg index 8ed37f9ce..9d44335f8 100644 --- a/bitcoin_client/setup.cfg +++ b/bitcoin_client/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ledger_bitcoin -version = 0.0.3 +version = attr: ledger_bitcoin.__version__ author = Ledger author_email = hello@ledger.fr description = Client for Ledger Nano Bitcoin application @@ -16,10 +16,14 @@ classifiers = [options] packages = find: -python_requires = >=3.6 +python_requires = >=3.7 install_requires= typing-extensions>=3.7 ledgercomm>=1.1.0 + packaging>=21.3 + +[options.package_data] +* = py.typed [options.extras_require] hid = hidapi>=0.9.0.post3 diff --git a/bitcoin_client/tests/requirements.txt b/bitcoin_client/tests/requirements.txt index 58945f429..23a57de0e 100644 --- a/bitcoin_client/tests/requirements.txt +++ b/bitcoin_client/tests/requirements.txt @@ -1,7 +1,8 @@ pytest>=6.1.1,<7.0.0 +pytest-timeout>=2.1.0,<3.0.0 ledgercomm>=1.1.0,<1.2.0 ecdsa>=0.16.1,<0.17.0 typing-extensions>=3.7,<4.0 -embit>=0.4.10,<0.5.0 +embit>=0.7.0,<0.8.0 mnemonic==0.20 -bip32>=2.1,<3.0 \ No newline at end of file +bip32>=3.4,<4.0 \ No newline at end of file diff --git a/bitcoin_client/tests/test_client_legacy.py b/bitcoin_client/tests/test_client_legacy.py new file mode 100644 index 000000000..03e7df928 --- /dev/null +++ b/bitcoin_client/tests/test_client_legacy.py @@ -0,0 +1,13 @@ +from pathlib import Path + +from bitcoin_client.ledger_bitcoin import Client +from bitcoin_client.ledger_bitcoin.client_legacy import LegacyClient + + +tests_root: Path = Path(__file__).parent + + +def test_client_legacy(client: Client): + # tests that the library correctly instatiates the LegacyClient and not the new one, + # since the version of the app binary being tested is an old one + assert isinstance(client, LegacyClient) diff --git a/bitcoin_client/tests/test_get_wallet_address_legacyapp.py b/bitcoin_client/tests/test_get_wallet_address_legacyapp.py index 8caaf05f9..efa4e05e5 100644 --- a/bitcoin_client/tests/test_get_wallet_address_legacyapp.py +++ b/bitcoin_client/tests/test_get_wallet_address_legacyapp.py @@ -1,13 +1,13 @@ -from bitcoin_client.ledger_bitcoin import Client, PolicyMapWallet +from bitcoin_client.ledger_bitcoin import Client, WalletPolicy def test_get_wallet_address_singlesig_legacy(client: Client): # legacy address (P2PKH) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[ - f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ) assert client.get_wallet_address(wallet, None, 0, 0, False) == "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm" @@ -16,11 +16,11 @@ def test_get_wallet_address_singlesig_legacy(client: Client): def test_get_wallet_address_singlesig_wit(client: Client): # bech32 address (P2WPKH) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="wpkh(@0)", + descriptor_template="wpkh(@0/**)", keys_info=[ - f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", + f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P", ], ) assert client.get_wallet_address(wallet, None, 0, 0, False) == "tb1qzdr7s2sr0dwmkwx033r4nujzk86u0cy6fmzfjk" @@ -29,11 +29,11 @@ def test_get_wallet_address_singlesig_wit(client: Client): def test_get_wallet_address_singlesig_sh_wit(client: Client): # wrapped segwit addresses (P2SH-P2WPKH) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="sh(wpkh(@0))", + descriptor_template="sh(wpkh(@0/**))", keys_info=[ - f"[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**", + f"[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3", ], ) assert client.get_wallet_address(wallet, None, 0, 0, False) == "2MyHkbusvLomaarGYMqyq7q9pSBYJRwWcsw" diff --git a/bitcoin_client/tests/test_ripemd160.py b/bitcoin_client/tests/test_ripemd160.py new file mode 100644 index 000000000..f4a403d0e --- /dev/null +++ b/bitcoin_client/tests/test_ripemd160.py @@ -0,0 +1,24 @@ +from bitcoin_client.ledger_bitcoin.ripemd import ripemd160 + + +def test_ripemd160(): + """RIPEMD-160 test vectors.""" + # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html + for msg, hexout in [ + (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), + (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), + (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), + (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), + (b"abcdefghijklmnopqrstuvwxyz", "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), + ( + b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "12a053384a9c0c88e405a06c27dcf49ada62eb2b", + ), + ( + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "b0e20b6e3116640286ed3a87a5713079b21f5189", + ), + (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), + (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528"), + ]: + assert ripemd160(msg).hex() == hexout diff --git a/bitcoin_client/tests/test_sign_psbt_legacyapp.py b/bitcoin_client/tests/test_sign_psbt_legacyapp.py index e0d60d202..d52100b7c 100644 --- a/bitcoin_client/tests/test_sign_psbt_legacyapp.py +++ b/bitcoin_client/tests/test_sign_psbt_legacyapp.py @@ -1,6 +1,6 @@ from pathlib import Path -from bitcoin_client.ledger_bitcoin import Client, PolicyMapWallet +from bitcoin_client.ledger_bitcoin import Client, WalletPolicy, PartialSignature from bitcoin_client.ledger_bitcoin.psbt import PSBT @@ -26,11 +26,11 @@ def test_sign_psbt_singlesig_pkh_1to1(client: Client): # PSBT for a legacy 1-input 1-output spend (no change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/pkh-1to1.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "pkh(@0)", + "pkh(@0/**)", [ - "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**" + "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT" ], ) @@ -40,11 +40,15 @@ def test_sign_psbt_singlesig_pkh_1to1(client: Client): # "signature" : "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" result = client.sign_psbt(psbt, wallet, None) - assert result == { - 0: bytes.fromhex( - "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718"), + signature=bytes.fromhex( + "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + ) ) - } + )] @has_automation("automations/sign_with_wallet_accept.json") @@ -53,11 +57,11 @@ def test_sign_psbt_singlesig_sh_wpkh_1to2(client: Client): # PSBT for a wrapped segwit 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/sh-wpkh-1to2.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "sh(wpkh(@0))", + "sh(wpkh(@0/**))", [ - "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**" + "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3" ], ) @@ -67,11 +71,17 @@ def test_sign_psbt_singlesig_sh_wpkh_1to2(client: Client): # "signature" : "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" result = client.sign_psbt(psbt, wallet, None) - assert result == { - 0: bytes.fromhex( - "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" + assert len(result) == 1 + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67"), + signature=bytes.fromhex( + "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" + ) ) - } + )] @has_automation("automations/sign_with_wallet_accept.json") @@ -80,11 +90,11 @@ def test_sign_psbt_singlesig_wpkh_1to2(client: Client): # PSBT for a legacy 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) @@ -95,11 +105,15 @@ def test_sign_psbt_singlesig_wpkh_1to2(client: Client): # "pubkey" : "03ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d3068", # "signature" : "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" - assert result == { - 0: bytes.fromhex( - "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d3068"), + signature=bytes.fromhex( + "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" + ) ) - } + )] @has_automation("automations/sign_with_wallet_accept.json") @@ -108,21 +122,30 @@ def test_sign_psbt_singlesig_wpkh_2to2(client: Client): psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-2to2.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) result = client.sign_psbt(psbt, wallet, None) - assert result == { - 0: bytes.fromhex( - "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" - ), - 1: bytes.fromhex( - "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" - ), - } + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3"), + signature=bytes.fromhex( + "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" + ) + ) + ), ( + 1, + PartialSignature( + pubkey=bytes.fromhex("0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0"), + signature=bytes.fromhex( + "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" + ) + ) + )] diff --git a/bitcoin_client_js/README.md b/bitcoin_client_js/README.md index 8a1f487d9..efd4ab53c 100644 --- a/bitcoin_client_js/README.md +++ b/bitcoin_client_js/README.md @@ -2,15 +2,21 @@ ## Overview -TypeScript client for Ledger Bitcoin application. Supports versions 2.0.0 and above of the app. +TypeScript client for Ledger Bitcoin application. Supports versions 2.1.0 and above of the app. Main repository and documentation: https://github.com/LedgerHQ/app-bitcoin-new - +```bash +$ yarn add ledger-bitcoin +``` + +Or if you prefer using npm: + +```bash +$ npm install ledger-bitcoin +``` ## Building @@ -24,10 +30,12 @@ $ yarn build The following example showcases all the main methods of the `Client`'s interface. -Testing the `signPsbt` method requires a valid PSBTv2, and provide the corresponding wallet policy; it is skipped by default in the following example. +More examples can be found in the [test suite](src/__tests__/appClient.test.ts). + +Testing the `signPsbt` method requires a valid PSBT, and provide the corresponding wallet policy; it is skipped by default in the following example. ```javascript -import { AppClient, DefaultWalletPolicy, WalletPolicy, PsbtV2 } from 'ledger-bitcoin'; +import { AppClient, DefaultWalletPolicy, WalletPolicy } from 'ledger-bitcoin'; import Transport from '@ledgerhq/hw-transport-node-hid'; // This examples assumes the Bitcoin Testnet app is running. @@ -44,8 +52,8 @@ async function main(transport) { // ==> Get and display on screen the first taproot address const firstTaprootAccountPubkey = await app.getExtendedPubkey("m/86'/1'/0'"); const firstTaprootAccountPolicy = new DefaultWalletPolicy( - "tr(@0)", - `[${fpr}/86'/1'/0']${firstTaprootAccountPubkey}/**` + "tr(@0/**)", + `[${fpr}/86'/1'/0']${firstTaprootAccountPubkey}` ); const firstTaprootAccountAddress = await app.getWalletAddress( @@ -61,12 +69,12 @@ async function main(transport) { // ==> Register a multisig wallet named "Cold storage" const ourPubkey = await app.getExtendedPubkey("m/48'/1'/0'/2'"); - const ourKeyInfo = `[${fpr}/48'/1'/0'/2']${ourPubkey}/**`; - const otherKeyInfo = "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**"; + const ourKeyInfo = `[${fpr}/48'/1'/0'/2']${ourPubkey}`; + const otherKeyInfo = "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF"; const multisigPolicy = new WalletPolicy( "Cold storage", - "wsh(sortedmulti(2,@0,@1))", // a 2-of-2 multisig policy template + "wsh(sortedmulti(2,@0/**,@1/**))", // a 2-of-2 multisig policy template [ otherKeyInfo, // some other bitcoiner ourKeyInfo, // that's us @@ -87,18 +95,19 @@ async function main(transport) { // ==> Sign a psbt // TODO: set a wallet policy and a valid psbt file in order to test psbt signing - const rawPsbtBase64 = null; // a base64-encoded psbt file to sign + const psbt = null; // a base64-encoded psbt, or a binary psbt in a Buffer const signingPolicy = null; // an instance of WalletPolicy const signingPolicyHmac = null; // if not a default wallet policy, this must also be set - if (!rawPsbtBase64 || !signingPolicy) { + if (!psbt || !signingPolicy) { console.log("Nothing to sign :("); await transport.close(); return; } - const psbt = new PsbtV2(); - psbt.deserialize(rawPsbtBase64); - + // result will be a list of triples [i, partialSig], where: + // - i is the input index + // - partialSig is an instance of PartialSignature; it contains a pubkey and a signature, + // and it might contain a tapleaf_hash. const result = await app.signPsbt(psbt, signingPolicy, signingPolicyHmac); console.log("Returned signatures:"); @@ -110,4 +119,4 @@ async function main(transport) { Transport.default.create() .then(main) .catch(console.log); -``` \ No newline at end of file +``` diff --git a/bitcoin_client_js/package.json b/bitcoin_client_js/package.json index 848e09c56..362df99f0 100644 --- a/bitcoin_client_js/package.json +++ b/bitcoin_client_js/package.json @@ -1,10 +1,10 @@ { "name": "ledger-bitcoin", - "version": "0.0.1", + "version": "0.2.3", "description": "Ledger Hardware Wallet Bitcoin Application Client", "main": "build/main/index.js", "typings": "build/main/index.d.ts", - "repository": "https://github.com/LedgerHW/app-bitcoin-new", + "repository": "https://github.com/LedgerHQ/app-bitcoin-new", "license": "Apache-2.0", "keywords": [ "Ledger", @@ -29,10 +29,11 @@ "node": ">=14" }, "dependencies": { + "@bitcoinerlab/descriptors": "^1.0.2", + "@bitcoinerlab/secp256k1": "^1.0.5", "@ledgerhq/hw-transport": "^6.20.0", "bip32-path": "^0.4.2", - "bitcoinjs-lib": "^6.0.1", - "tiny-secp256k1": "^2.1.2" + "bitcoinjs-lib": "^6.1.3" }, "devDependencies": { "@ledgerhq/hw-transport-node-speculos-http": "^6.24.1", @@ -71,4 +72,4 @@ "prettier": { "singleQuote": true } -} \ No newline at end of file +} diff --git a/bitcoin_client_js/src/__tests__/appClient.test.ts b/bitcoin_client_js/src/__tests__/appClient.test.ts index 9ea60f469..a1784646b 100644 --- a/bitcoin_client_js/src/__tests__/appClient.test.ts +++ b/bitcoin_client_js/src/__tests__/appClient.test.ts @@ -103,6 +103,7 @@ describe("test AppClient", () => { sp = spawn(speculos_path, [ repoRootPath + "/bin/app.elf", '-k', '2.1', + '--model', 'nanos', '--display', 'headless' ]); @@ -127,6 +128,11 @@ describe("test AppClient", () => { await killProcess(sp); }); + it("can retrieve the app's version", async () => { + const result = await app.getAppAndVersion(); + expect(result.name).toEqual("Bitcoin Test"); + expect(result.version.split(".")[0]).toEqual("2") + }); it("can retrieve the master fingerprint", async () => { const result = await app.getMasterFingerprint(); @@ -149,64 +155,64 @@ describe("test AppClient", () => { }[] = [ // legacy { - policy: new DefaultWalletPolicy("pkh(@0)", "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**"), + policy: new DefaultWalletPolicy("pkh(@0/**)", "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT"), change: 0, addrIndex: 0, expResult: "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm", }, { - policy: new DefaultWalletPolicy("pkh(@0)", "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**"), + policy: new DefaultWalletPolicy("pkh(@0/**)", "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT"), change: 1, addrIndex: 15, expResult: "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT", }, // native segwit { - policy: new DefaultWalletPolicy("wpkh(@0)", "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**"), + policy: new DefaultWalletPolicy("wpkh(@0/**)", "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P"), change: 0, addrIndex: 0, expResult: "tb1qzdr7s2sr0dwmkwx033r4nujzk86u0cy6fmzfjk", }, { - policy: new DefaultWalletPolicy("wpkh(@0)", "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**"), + policy: new DefaultWalletPolicy("wpkh(@0/**)", "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P"), change: 1, addrIndex: 15, expResult: "tb1qlrvzyx8jcjfj2xuy69du9trtxnsvjuped7e289", }, // wrapped segwit { - policy: new DefaultWalletPolicy("sh(wpkh(@0))", "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**"), + policy: new DefaultWalletPolicy("sh(wpkh(@0/**))", "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3"), change: 0, addrIndex: 0, expResult: "2MyHkbusvLomaarGYMqyq7q9pSBYJRwWcsw", }, { - policy: new DefaultWalletPolicy("sh(wpkh(@0))", "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**"), + policy: new DefaultWalletPolicy("sh(wpkh(@0/**))", "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3"), change: 1, addrIndex: 15, expResult: "2NAbM4FSeBQG4o85kbXw2YNfKypcnEZS9MR", }, // taproot { - policy: new DefaultWalletPolicy("tr(@0)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**"), + policy: new DefaultWalletPolicy("tr(@0/**)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U"), change: 0, addrIndex: 0, expResult: "tb1pws8wvnj99ca6acf8kq7pjk7vyxknah0d9mexckh5s0vu2ccy68js9am6u7", }, { - policy: new DefaultWalletPolicy("tr(@0)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**"), + policy: new DefaultWalletPolicy("tr(@0/**)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U"), change: 0, addrIndex: 9, expResult: "tb1psl7eyk2jyjzq6evqvan854fts7a5j65rth25yqahkd2a765yvj0qggs5ne", }, { - policy: new DefaultWalletPolicy("tr(@0)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**"), + policy: new DefaultWalletPolicy("tr(@0/**)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U"), change: 1, addrIndex: 0, expResult: "tb1pmr60r5vfjmdkrwcu4a2z8h39mzs7a6wf2rfhuml6qgcp940x9cxs7t9pdy", }, { - policy: new DefaultWalletPolicy("tr(@0)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**"), + policy: new DefaultWalletPolicy("tr(@0/**)", "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U"), change: 1, addrIndex: 9, expResult: "tb1p98d6s9jkf0la8ras4nnm72zme5r03fexn29e3pgz4qksdy84ndpqgjak72", @@ -215,16 +221,16 @@ describe("test AppClient", () => { { policy: new WalletPolicy( "Cold storage", - "wsh(sortedmulti(2,@0,@1))", + "wsh(sortedmulti(2,@0/**,@1/**))", [ - "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ] ), change: 0, addrIndex: 0, expResult: "tb1qmyauyzn08cduzdqweexgna2spwd0rndj55fsrkefry2cpuyt4cpsn2pg28", - walletHmac: Buffer.from("d6434852fb3caa7edbd1165084968f1691444b3cfc10cf1e431acbbc7f48451f", "hex") + walletHmac: Buffer.from("d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb371", "hex") }, ]; @@ -238,10 +244,75 @@ describe("test AppClient", () => { it("can register a multisig wallet", async () => { const walletPolicy = new WalletPolicy( "Cold storage", - "wsh(sortedmulti(2,@0,@1))", + "wsh(sortedmulti(2,@0/**,@1/**))", + [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ] + ); + + const automation = JSON.parse(fs.readFileSync('src/__tests__/automations/register_wallet_accept.json').toString()); + await setSpeculosAutomation(transport, automation); + + const [walletId, walletHmac] = await app.registerWallet(walletPolicy); + + expect(walletId).toEqual(walletPolicy.getId()); + expect(walletHmac.length).toEqual(32); + }); + + //https://wizardsardine.com/blog/ledger-vulnerability-disclosure/ + it('can generate a correct address or throw on a:X', async () => { + for (const template of [ + 'wsh(and_b(pk(@0/**),a:1))', + 'wsh(and_b(pk(@0/<0;1>/*),a:1))' + ]) { + try { + const walletPolicy = new WalletPolicy('Fixed Vulnerability', template, [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" + ]); + + const automation = JSON.parse( + fs + .readFileSync( + 'src/__tests__/automations/register_wallet_accept.json' + ) + .toString() + ); + await setSpeculosAutomation(transport, automation); + + const [walletId, walletHmac] = await app.registerWallet(walletPolicy); + + expect(walletId).toEqual(walletPolicy.getId()); + expect(walletHmac.length).toEqual(32); + + const address = await app.getWalletAddress( + walletPolicy, + walletHmac, + 0, + 0, + false + ); + //version > 2.1.1 + expect(address).toEqual( + 'tb1q5lyn9807ygs7pc52980mdeuwl9wrq5c8n3kntlhy088h6fqw4gzspw9t9m' + ); + } catch (error) { + //version <= 2.1.1 + expect(error.message).toMatch( + /^Third party address validation mismatch/ + ); + } + } + }); + + it("can register a miniscript wallet", async () => { + const walletPolicy = new WalletPolicy( + "Decaying 3-of-3", + "wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@2/**),sln:older(12960)))", [ - "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDCoDDpHR1MYXcFrarTcwBufQvWPXSSZpGxjnhRaW612TMxs5TWDEPdbYRHtQdZ9z1UqtKGQKVQ4FqejzbFSdvQvJsD75yrgh7thVoFho6jE", ] ); @@ -265,23 +336,121 @@ describe("test AppClient", () => { await setSpeculosAutomation(transport, automation); const walletPolicy = new DefaultWalletPolicy( - "wpkh(@0)", - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "wpkh(@0/**)", + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ); const psbt = new PsbtV2(); psbt.deserialize(psbtBuf); const result = await app.signPsbt(psbt, walletPolicy, null, () => {}); - expect(result.size).toEqual(2); - expect(result.get(0)).toEqual(Buffer.from( + expect(result.length).toEqual(2); + + expect(result[0][0]).toEqual(0); + expect(result[0][1].pubkey).toEqual(Buffer.from( + "03455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3", + "hex" + )); + expect(result[0][1].signature).toEqual(Buffer.from( "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01", "hex" )); - expect(result.get(1)).toEqual(Buffer.from( + + + expect(result[1][0]).toEqual(1); + expect(result[1][1].pubkey).toEqual(Buffer.from( + "0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0", + "hex" + )); + expect(result[1][1].signature).toEqual(Buffer.from( "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001", "hex" )); + expect(result[1][1].tapleafHash).toBeUndefined(); + }); + + it("can sign a psbt for a taproot script path", async () => { + // psbt from test_sign_psbt_tr_script_pk_sighash_all in the main test suite, converted to PSBTv2 + const psbtBuf = Buffer.from( + "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQEBAfsEAgAAAAABAStMBgAAAAAAACJRIPwKENMIx+QbS7w2Qvj9isKJhTsc51WgxtDUlfA9ny2kAQMEAQAAACIVwVAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1IyBrFujB+Xn6TMDwW2owCv//lBRZtvIN533lWwFg745MrKzAIRZQFxCL7OqN7cUwC7IjhrOrMDZ6wmL5zl5YMm64Pjoi9R0AdiI6bjAAAIABAACAAAAAgAIAAIAAAAAAAAAAACEWaxbowfl5+kzA8FtqMAr//5QUWbbyDed95VsBYO+OTKw9AQku2gM2F+IQ7n99DjeKQErqHEi1aqEDAivs93RuRwCk9azC/TAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAEXIFAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1ARggCS7aAzYX4hDuf30ON4pASuocSLVqoQMCK+z3dG5HAKQBDiAfwcxXccuDhgzFbZS8/tk4YIwX9jZiQ1tB6cRP/P0xQgEPBAEAAAABEAT9////AAEDCDkFAAAAAAAAAQQWABSqjvN0yvrfynaQLdtc9hxgu/2dhQA=", + "base64" + ); + + const automation = JSON.parse(fs.readFileSync('src/__tests__/automations/sign_with_wallet_accept.json').toString()); + await setSpeculosAutomation(transport, automation); + + const walletPolicy = new WalletPolicy( + "Taproot foreign internal key, and our script key", + "tr(@0/**,pk(@1/**))", + [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ] + ); + + const psbt = new PsbtV2(); + psbt.deserialize(psbtBuf); + const hmac = Buffer.from("dae925660e20859ed8833025d46444483ce264fdb77e34569aabe9d590da8fb7", "hex"); + const result = await app.signPsbt(psbt, walletPolicy, hmac); + + expect(result.length).toEqual(1); + + expect(result[0][0]).toEqual(0); + expect(result[0][1].pubkey).toEqual(Buffer.from( + "6b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac", + "hex" + )); + expect(result[0][1].tapleafHash).toEqual(Buffer.from( + "092eda033617e210ee7f7d0e378a404aea1c48b56aa103022becf7746e4700a4", + "hex" + )); + + // We could test the validity of the signature, but this is already done in the corresponding python test. + // Here we're only interested in testing that the JS library returns the correct values. + expect(result[0][1].signature.length).toEqual(65); // 65 because it's SIGHASH_ALL and not SIGHASH_DEFAULT + }); + + it("can sign a psbt passed as a base64 string", async () => { + const automation = JSON.parse(fs.readFileSync('src/__tests__/automations/sign_with_wallet_accept.json').toString()); + await setSpeculosAutomation(transport, automation); + + const walletPolicy = new WalletPolicy( + "Taproot foreign internal key, and our script key", + "tr(@0/**,pk(@1/**))", + [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ] + ); + + const hmac = Buffer.from("dae925660e20859ed8833025d46444483ce264fdb77e34569aabe9d590da8fb7", "hex"); + const psbtBase64 = "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQEBAfsEAgAAAAABAStMBgAAAAAAACJRIPwKENMIx+QbS7w2Qvj9isKJhTsc51WgxtDUlfA9ny2kAQMEAQAAACIVwVAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1IyBrFujB+Xn6TMDwW2owCv//lBRZtvIN533lWwFg745MrKzAIRZQFxCL7OqN7cUwC7IjhrOrMDZ6wmL5zl5YMm64Pjoi9R0AdiI6bjAAAIABAACAAAAAgAIAAIAAAAAAAAAAACEWaxbowfl5+kzA8FtqMAr//5QUWbbyDed95VsBYO+OTKw9AQku2gM2F+IQ7n99DjeKQErqHEi1aqEDAivs93RuRwCk9azC/TAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAEXIFAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1ARggCS7aAzYX4hDuf30ON4pASuocSLVqoQMCK+z3dG5HAKQBDiAfwcxXccuDhgzFbZS8/tk4YIwX9jZiQ1tB6cRP/P0xQgEPBAEAAAABEAT9////AAEDCDkFAAAAAAAAAQQWABSqjvN0yvrfynaQLdtc9hxgu/2dhQA=" + const result = await app.signPsbt(psbtBase64, walletPolicy, hmac); + + expect(result.length).toEqual(1); + }); + + it("can sign a psbt passed as binary buffer string", async () => { + const automation = JSON.parse(fs.readFileSync('src/__tests__/automations/sign_with_wallet_accept.json').toString()); + await setSpeculosAutomation(transport, automation); + + const walletPolicy = new WalletPolicy( + "Taproot foreign internal key, and our script key", + "tr(@0/**,pk(@1/**))", + [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ] + ); + + const hmac = Buffer.from("dae925660e20859ed8833025d46444483ce264fdb77e34569aabe9d590da8fb7", "hex"); + const psbtBuf = Buffer.from( + "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQEBAfsEAgAAAAABAStMBgAAAAAAACJRIPwKENMIx+QbS7w2Qvj9isKJhTsc51WgxtDUlfA9ny2kAQMEAQAAACIVwVAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1IyBrFujB+Xn6TMDwW2owCv//lBRZtvIN533lWwFg745MrKzAIRZQFxCL7OqN7cUwC7IjhrOrMDZ6wmL5zl5YMm64Pjoi9R0AdiI6bjAAAIABAACAAAAAgAIAAIAAAAAAAAAAACEWaxbowfl5+kzA8FtqMAr//5QUWbbyDed95VsBYO+OTKw9AQku2gM2F+IQ7n99DjeKQErqHEi1aqEDAivs93RuRwCk9azC/TAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAEXIFAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1ARggCS7aAzYX4hDuf30ON4pASuocSLVqoQMCK+z3dG5HAKQBDiAfwcxXccuDhgzFbZS8/tk4YIwX9jZiQ1tB6cRP/P0xQgEPBAEAAAABEAT9////AAEDCDkFAAAAAAAAAQQWABSqjvN0yvrfynaQLdtc9hxgu/2dhQA=", + "base64" + ); + const result = await app.signPsbt(psbtBuf, walletPolicy, hmac); + + expect(result.length).toEqual(1); }); it("can sign a message", async () => { @@ -294,4 +463,4 @@ describe("test AppClient", () => { const result = await app.signMessage(Buffer.from(msg, "ascii"), path) expect(result).toEqual("H4frM6TYm5ty1MAf9o/Zz9Qiy3VEldAYFY91SJ/5nYMAZY1UUB97fiRjKW8mJit2+V4OCa1YCqjDqyFnD9Fw75k="); }); -}); \ No newline at end of file +}); diff --git a/bitcoin_client_js/src/__tests__/automations/register_wallet_accept.json b/bitcoin_client_js/src/__tests__/automations/register_wallet_accept.json index 6371aa704..fa2d0db14 100644 --- a/bitcoin_client_js/src/__tests__/automations/register_wallet_accept.json +++ b/bitcoin_client_js/src/__tests__/automations/register_wallet_accept.json @@ -2,7 +2,7 @@ "version": 1, "rules": [ { - "regexp": "Register wallet|Policy map|Key", + "regexp": "Register wallet|Wallet name|Wallet policy|Key", "actions": [ ["button", 2, true], ["button", 2, false] diff --git a/bitcoin_client_js/src/__tests__/automations/sign_message_accept.json b/bitcoin_client_js/src/__tests__/automations/sign_message_accept.json index 505f5e39c..a72520e17 100644 --- a/bitcoin_client_js/src/__tests__/automations/sign_message_accept.json +++ b/bitcoin_client_js/src/__tests__/automations/sign_message_accept.json @@ -2,7 +2,7 @@ "version": 1, "rules": [ { - "regexp": "Path|Message hash", + "regexp": "Path|Message hash|Message content", "actions": [ ["button", 2, true], ["button", 2, false] diff --git a/bitcoin_client_js/src/__tests__/automations/sign_with_wallet_accept.json b/bitcoin_client_js/src/__tests__/automations/sign_with_wallet_accept.json index 37a08b741..81c5db84c 100644 --- a/bitcoin_client_js/src/__tests__/automations/sign_with_wallet_accept.json +++ b/bitcoin_client_js/src/__tests__/automations/sign_with_wallet_accept.json @@ -2,14 +2,14 @@ "version": 1, "rules": [ { - "regexp": "Spend from|Review|Amount|Address|Confirm|Fees", + "regexp": "Spend from|Wallet name|Review|Amount|Address|Fees", "actions": [ ["button", 2, true], ["button", 2, false] ] }, { - "regexp": "Approve|Accept", + "regexp": "Approve|Continue|Sign", "actions": [ [ "button", 1, true ], [ "button", 2, true ], diff --git a/bitcoin_client_js/src/__tests__/psbtv2.test.ts b/bitcoin_client_js/src/__tests__/psbtv2.test.ts new file mode 100644 index 000000000..3aa2b103b --- /dev/null +++ b/bitcoin_client_js/src/__tests__/psbtv2.test.ts @@ -0,0 +1,33 @@ +import { PsbtV2 } from "../lib/psbtv2"; + +describe("PsbtV2", () => { + it("deserializes a psbtV2 and reserializes it unchanged", async () => { + const psbtBuf = Buffer.from( + "cHNidP8BAAoBAAAAAAAAAAAAAQIEAgAAAAEDBAAAAAABBAECAQUBAgH7BAIAAAAAAQBxAgAAAAGTarLgEHL3k8/kyXdU3hth/gPn22U2yLLyHdC1dCxIRQEAAAAA/v///wLe4ccAAAAAABYAFOt418QL8QY7Dj/OKcNWW2ichVmrECcAAAAAAAAWABQjGNZvhP71xIdfkzsDjcY4MfjaE/mXHgABAR8QJwAAAAAAABYAFCMY1m+E/vXEh1+TOwONxjgx+NoTIgYDRV7nztyXsLpDW4AGb8ksljo0xgAxeYHRNTMMTuQ6x6MY9azC/VQAAIABAACAAAAAgAAAAAABAAAAAQ4gniz+J/Cth7eKI31ddAXUowZmyjYdWFpGew3+QiYrTbQBDwQBAAAAARAE/f///wESBAAAAAAAAQBxAQAAAAEORx706Sway1HvyGYPjT9pk26pybK/9y/5vIHFHvz0ZAEAAAAAAAAAAAJgrgoAAAAAABYAFDXG4N1tPISxa6iF3Kc6yGPQtZPsrwYyAAAAAAAWABTcKG4M0ua9N86+nsNJ+18IkFZy/AAAAAABAR9grgoAAAAAABYAFDXG4N1tPISxa6iF3Kc6yGPQtZPsIgYCcbW3ea2HCDhYd5e89vDHrsWr52pwnXJPSNLibPh08KAY9azC/VQAAIABAACAAAAAgAEAAAAAAAAAAQ4gr7+uBlkPdB/xr1m2rEYRJjNqTEqC21U99v76tzesM/MBDwQAAAAAARAE/f///wESBAAAAAAAIgICKexHcnEx7SWIogxG7amrt9qm9J/VC6/nC5xappYcTswY9azC/VQAAIABAACAAAAAgAEAAAAKAAAAAQMIqDoGAAAAAAABBBYAFOs4+puBKPgfJule2wxf+uqDaQ/kAAEDCOCTBAAAAAAAAQQiACA/qWbJ3c3C/ZbkpeG8dlufr2zos+tPEQSq1r33cyTlvgA=", + "base64" + ); + + const psbt = new PsbtV2(); + psbt.deserialize(psbtBuf); + + expect(psbt.serialize()).toEqual(psbtBuf); + }); + + it("deserializes a psbtV0 and reserializes it as a valid psbtV2", async () => { + const psbtV0 = Buffer.from( + "cHNidP8BAFICAAAAAR/BzFdxy4OGDMVtlLz+2ThgjBf2NmJDW0HpxE/8/TFCAQAAAAD9////ATkFAAAAAAAAFgAUqo7zdMr638p2kC3bXPYcYLv9nYUAAAAAAAEBK0wGAAAAAAAAIlEg/AoQ0wjH5BtLvDZC+P2KwomFOxznVaDG0NSV8D2fLaQBAwQBAAAAIhXBUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUjIGsW6MH5efpMwPBbajAK//+UFFm28g3nfeVbAWDvjkysrMAhFlAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1HQB2IjpuMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIRZrFujB+Xn6TMDwW2owCv//lBRZtvIN533lWwFg745MrD0BCS7aAzYX4hDuf30ON4pASuocSLVqoQMCK+z3dG5HAKT1rML9MAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAARcgUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUBGCAJLtoDNhfiEO5/fQ43ikBK6hxItWqhAwIr7Pd0bkcApAAA", + "base64" + ); + + // the same psbt converted to V2, with keys sorted in lexicographical order + const psbtV2 = Buffer.from( + "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQEBBQEBAfsEAgAAAAABAStMBgAAAAAAACJRIPwKENMIx+QbS7w2Qvj9isKJhTsc51WgxtDUlfA9ny2kAQMEAQAAAAEOIB/BzFdxy4OGDMVtlLz+2ThgjBf2NmJDW0HpxE/8/TFCAQ8EAQAAAAEQBP3///8iFcFQFxCL7OqN7cUwC7IjhrOrMDZ6wmL5zl5YMm64Pjoi9SMgaxbowfl5+kzA8FtqMAr//5QUWbbyDed95VsBYO+OTKyswCEWUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUdAHYiOm4wAACAAQAAgAAAAIACAACAAAAAAAAAAAAhFmsW6MH5efpMwPBbajAK//+UFFm28g3nfeVbAWDvjkysPQEJLtoDNhfiEO5/fQ43ikBK6hxItWqhAwIr7Pd0bkcApPWswv0wAACAAQAAgAAAAIACAACAAAAAAAAAAAABFyBQFxCL7OqN7cUwC7IjhrOrMDZ6wmL5zl5YMm64Pjoi9QEYIAku2gM2F+IQ7n99DjeKQErqHEi1aqEDAivs93RuRwCkAAEDCDkFAAAAAAAAAQQWABSqjvN0yvrfynaQLdtc9hxgu/2dhQA=", + "base64" + ); + + const psbt = new PsbtV2(); + psbt.deserialize(psbtV0); + + expect(psbt.serialize()).toEqual(psbtV2); + }); +}); diff --git a/bitcoin_client_js/src/index.ts b/bitcoin_client_js/src/index.ts index 7f2f4f995..01a2be10c 100644 --- a/bitcoin_client_js/src/index.ts +++ b/bitcoin_client_js/src/index.ts @@ -1,7 +1,18 @@ -import AppClient from './lib/appClient'; -import { DefaultWalletPolicy, WalletPolicy } from './lib/policy'; +import AppClient, { PartialSignature } from './lib/appClient'; +import { + DefaultDescriptorTemplate, + DefaultWalletPolicy, + WalletPolicy +} from './lib/policy'; import { PsbtV2 } from './lib/psbtv2'; -export { AppClient, PsbtV2, DefaultWalletPolicy, WalletPolicy }; +export { + AppClient, + PsbtV2, + DefaultDescriptorTemplate, + DefaultWalletPolicy, + PartialSignature, + WalletPolicy +}; export default AppClient; diff --git a/bitcoin_client_js/src/lib/appClient.ts b/bitcoin_client_js/src/lib/appClient.ts index 664779d84..bb369779f 100644 --- a/bitcoin_client_js/src/lib/appClient.ts +++ b/bitcoin_client_js/src/lib/appClient.ts @@ -1,4 +1,8 @@ +import * as descriptors from '@bitcoinerlab/descriptors'; +import * as secp256k1 from '@bitcoinerlab/secp256k1'; +const { Descriptor } = descriptors.DescriptorsFactory(secp256k1); import Transport from '@ledgerhq/hw-transport'; +import { networks } from 'bitcoinjs-lib'; import { pathElementsToBuffer, pathStringToArray } from './bip32'; import { ClientCommandInterpreter } from './clientCommands'; @@ -6,11 +10,13 @@ import { MerkelizedPsbt } from './merkelizedPsbt'; import { hashLeaf, Merkle } from './merkle'; import { WalletPolicy } from './policy'; import { PsbtV2 } from './psbtv2'; -import { createVarint } from './varint'; +import { createVarint, parseVarint } from './varint'; const CLA_BTC = 0xe1; const CLA_FRAMEWORK = 0xf8; +const CURRENT_PROTOCOL_VERSION = 1; // supported from version 2.1.0 of the app + enum BitcoinIns { GET_PUBKEY = 0x00, REGISTER_WALLET = 0x02, @@ -24,6 +30,42 @@ enum FrameworkIns { CONTINUE_INTERRUPTED = 0x01, } +/** + * This class represents a partial signature produced by the app during signing. + * It always contains the `signature` and the corresponding `pubkey` whose private key + * was used for signing; in the case of taproot script paths, it also contains the + * tapleaf hash. + */ +export class PartialSignature { + readonly pubkey: Buffer; + readonly signature: Buffer; + readonly tapleafHash?: Buffer; + + constructor(pubkey: Buffer, signature: Buffer, tapleafHash?: Buffer) { + this.pubkey = pubkey; + this.signature = signature; + this.tapleafHash = tapleafHash; + } +} + +/** + * Creates an instance of `PartialSignature` from the returned raw augmented pubkey and signature. + * @param pubkeyAugm the public key, concatenated with the tapleaf hash in the case of taproot script path spend. + * @param signature the signature + * @returns an instance of `PartialSignature`. + */ +function makePartialSignature(pubkeyAugm: Buffer, signature: Buffer): PartialSignature { + if (pubkeyAugm.length == 64) { + // tapscript spend: concatenation of 32-bytes x-only pubkey and 32-bytes tapleaf_hash + return new PartialSignature(pubkeyAugm.slice(0, 32), signature, pubkeyAugm.slice(32, 64)); + } else if (pubkeyAugm.length == 32 || pubkeyAugm.length == 33) { + // legacy, segwit or taproot keypath spend: pubkeyAugm is just the pubkey + return new PartialSignature(pubkeyAugm, signature); + } else { + throw new Error(`Invalid length for pubkeyAugm: ${pubkeyAugm.length} bytes.`); + } +} + /** * This class encapsulates the APDU protocol documented at * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/bitcoin.md @@ -44,7 +86,7 @@ export class AppClient { CLA_BTC, ins, 0, - 0, + CURRENT_PROTOCOL_VERSION, data, [0x9000, 0xe000] ); @@ -68,6 +110,34 @@ export class AppClient { return response.slice(0, -2); // drop the status word (can only be 0x9000 at this point) } + /** + * Returns an object containing the currently running app's name, version and the device status flags. + * + * @returns an object with app name, version and device status flags. + */ + public async getAppAndVersion(): Promise<{ + name: string; + version: string; + flags: number | Buffer; + }> { + const r = await this.transport.send(0xb0, 0x01, 0x00, 0x00); + let i = 0; + const format = r[i++]; + if (format !== 1) throw new Error("Unexpected response") + + const nameLength = r[i++]; + const name = r.slice(i, (i += nameLength)).toString("ascii"); + const versionLength = r[i++]; + const version = r.slice(i, (i += versionLength)).toString("ascii"); + const flagLength = r[i++]; + const flags = r.slice(i, (i += flagLength)); + return { + name, + version, + flags, + }; + }; + /** * Requests the BIP-32 extended pubkey to the hardware wallet. * If `display` is `false`, only standard paths will be accepted; an error is returned if an unusual path is @@ -109,14 +179,12 @@ export class AppClient { async registerWallet( walletPolicy: WalletPolicy ): Promise { - const serializedWalletPolicy = walletPolicy.serialize(); const clientInterpreter = new ClientCommandInterpreter(); - clientInterpreter.addKnownPreimage(serializedWalletPolicy); - clientInterpreter.addKnownList( - walletPolicy.keys.map((k) => Buffer.from(k, 'ascii')) - ); + clientInterpreter.addKnownWalletPolicy(walletPolicy); + + const serializedWalletPolicy = walletPolicy.serialize(); const response = await this.makeRequest( BitcoinIns.REGISTER_WALLET, Buffer.concat([ @@ -131,8 +199,20 @@ export class AppClient { `Invalid response length. Expected 64 bytes, got ${response.length}` ); } + const walletId = response.subarray(0, 32); + const walletHMAC = response.subarray(32); + + // sanity check: derive and validate the first address with a 3rd party + const firstAddrDevice = await this.getWalletAddress( + walletPolicy, + walletHMAC, + 0, + 0, + false + ); + await this.validateAddress(firstAddrDevice, walletPolicy, 0, 0); - return [response.subarray(0, 32), response.subarray(32)]; + return [walletId, walletHMAC]; } /** @@ -163,10 +243,8 @@ export class AppClient { } const clientInterpreter = new ClientCommandInterpreter(); - clientInterpreter.addKnownList( - walletPolicy.keys.map((k) => Buffer.from(k, 'ascii')) - ); - clientInterpreter.addKnownPreimage(walletPolicy.serialize()); + + clientInterpreter.addKnownWalletPolicy(walletPolicy); const addressIndexBuffer = Buffer.alloc(4); addressIndexBuffer.writeUInt32BE(addressIndex, 0); @@ -183,27 +261,41 @@ export class AppClient { clientInterpreter ); - return response.toString('ascii'); + const address = response.toString('ascii'); + await this.validateAddress(address, walletPolicy, change, addressIndex); + return address; } /** * Signs a psbt using a (standard or registered) `WalletPolicy`. This is an interactive command, as user validation * is necessary using the device's secure screen. * On success, a map of input indexes and signatures is returned. - * @param psbt an instance of `PsbtV2` + * @param psbt a base64-encoded string, or a psbt in a binary Buffer. Using the `PsbtV2` type is deprecated. * @param walletPolicy the `WalletPolicy` to use for signing * @param walletHMAC the 32-byte hmac obtained during wallet policy registration, or `null` for a standard policy * @param progressCallback optionally, a callback that will be called every time a signature is produced during * the signing process. The callback does not receive any argument, but can be used to track progress. - * @returns a map from numbers to signatures. For each input index `i` that is a key of the returned map, the - * corresponding value is the signature for the `i`-th input of the `psbt`. + * @returns an array of of tuples with 2 elements containing: + * - the index of the input being signed; + * - an instance of PartialSignature */ async signPsbt( - psbt: PsbtV2, + psbt: PsbtV2 | string | Buffer, walletPolicy: WalletPolicy, walletHMAC: Buffer | null, progressCallback?: () => void - ): Promise> { + ): Promise<[number, PartialSignature][]> { + + if (typeof psbt === 'string') { + psbt = Buffer.from(psbt, "base64"); + } + + if (Buffer.isBuffer(psbt)) { + const psbtObj = new PsbtV2() + psbtObj.deserialize(psbt); + psbt = psbtObj; + } + const merkelizedPsbt = new MerkelizedPsbt(psbt); if (walletHMAC != null && walletHMAC.length != 32) { @@ -213,10 +305,7 @@ export class AppClient { const clientInterpreter = new ClientCommandInterpreter(progressCallback); // prepare ClientCommandInterpreter - clientInterpreter.addKnownList( - walletPolicy.keys.map((k) => Buffer.from(k, 'ascii')) - ); - clientInterpreter.addKnownPreimage(walletPolicy.serialize()); + clientInterpreter.addKnownWalletPolicy(walletPolicy); clientInterpreter.addKnownMapping(merkelizedPsbt.globalMerkleMap); for (const map of merkelizedPsbt.inputMerkleMaps) { @@ -251,9 +340,18 @@ export class AppClient { const yielded = clientInterpreter.getYielded(); - const ret: Map = new Map(); + const ret: [number, PartialSignature][] = []; for (const inputAndSig of yielded) { - ret.set(inputAndSig[0], inputAndSig.slice(1)); + // inputAndSig contains: + // + const [inputIndex, inputIndexLen] = parseVarint(inputAndSig, 0); + const pubkeyAugmLen = inputAndSig[inputIndexLen]; + const pubkeyAugm = inputAndSig.subarray(inputIndexLen + 1, inputIndexLen + 1 + pubkeyAugmLen); + const signature = inputAndSig.subarray(inputIndexLen + 1 + pubkeyAugmLen) + + const partialSig = makePartialSignature(pubkeyAugm, signature); + + ret.push([Number(inputIndex), partialSig]); } return ret; } @@ -308,6 +406,77 @@ export class AppClient { return result.toString('base64'); } + + /* Performs any additional check on the generated address before returning it.*/ + private async validateAddress( + address: string, + walletPolicy: WalletPolicy, + change: number, + addressIndex: number + ) { + if (change !== 0 && change !== 1) + throw new Error('Change can only be 0 or 1'); + const isChange: boolean = change === 1; + if (addressIndex < 0 || !Number.isInteger(addressIndex)) + throw new Error('Invalid address index'); + const appAndVer = await this.getAppAndVersion(); + let network; + if (appAndVer.name === 'Bitcoin Test') { + network = networks.testnet; + } else if (appAndVer.name === 'Bitcoin') { + network = networks.bitcoin; + } else { + throw new Error( + `Invalid network: ${appAndVer.name}. Expected 'Bitcoin Test' or 'Bitcoin'.` + ); + } + let expression = walletPolicy.descriptorTemplate; + // Replace change: + expression = expression.replace(/\/\*\*/g, `/<0;1>/*`); + const regExpMN = new RegExp(`/<(\\d+);(\\d+)>`, 'g'); + let matchMN; + while ((matchMN = regExpMN.exec(expression)) !== null) { + const [M, N] = [parseInt(matchMN[1], 10), parseInt(matchMN[2], 10)]; + expression = expression.replace(`/<${M};${N}>`, `/${isChange ? N : M}`); + } + // Replace index: + expression = expression.replace(/\/\*/g, `/${addressIndex}`); + // Replace origin in reverse order to prevent + // misreplacements, e.g., @10 being mistaken for @1 and leaving a 0. + for (let i = walletPolicy.keys.length - 1; i >= 0; i--) + expression = expression.replace( + new RegExp(`@${i}`, 'g'), + walletPolicy.keys[i] + ); + let thirdPartyValidationApplicable = true; + let thirdPartyGeneratedAddress: string; + try { + thirdPartyGeneratedAddress = new Descriptor({ + expression, + network + }).getAddress(); + } catch (err) { + // Note: @bitcoinerlab/descriptors@1.0.x does not support Tapscript yet. + // These are the supported descriptors: + // - pkh(KEY) + // - wpkh(KEY) + // - sh(wpkh(KEY)) + // - sh(SCRIPT) + // - wsh(SCRIPT) + // - sh(wsh(SCRIPT)), where + // SCRIPT is any of the (non-tapscript) fragments in: https://bitcoin.sipa.be/miniscript/ + // + // Other expressions are not supported and third party validation would not be applicable: + thirdPartyValidationApplicable = false; + } + if ( + thirdPartyValidationApplicable && + address !== thirdPartyGeneratedAddress + ) + throw new Error( + `Third party address validation mismatch: ${address} != ${thirdPartyGeneratedAddress}` + ); + } } export default AppClient; diff --git a/bitcoin_client_js/src/lib/bip32.ts b/bitcoin_client_js/src/lib/bip32.ts index 0275bb172..091cf66f1 100644 --- a/bitcoin_client_js/src/lib/bip32.ts +++ b/bitcoin_client_js/src/lib/bip32.ts @@ -32,7 +32,7 @@ export function pathStringToArray(path: string): readonly number[] { } export function pubkeyFromXpub(xpub: string): Buffer { - const xpubBuf = bs58check.decode(xpub); + const xpubBuf = Buffer.from(bs58check.decode(xpub)); return xpubBuf.slice(xpubBuf.length - 33); } @@ -41,7 +41,7 @@ export function getXpubComponents(xpub: string): { readonly pubkey: Buffer; readonly version: number; } { - const xpubBuf: Buffer = bs58check.decode(xpub); + const xpubBuf = Buffer.from(bs58check.decode(xpub)); return { chaincode: xpubBuf.slice(13, 13 + 32), pubkey: xpubBuf.slice(xpubBuf.length - 33), diff --git a/bitcoin_client_js/src/lib/clientCommands.ts b/bitcoin_client_js/src/lib/clientCommands.ts index bf0dfce03..0267754d0 100644 --- a/bitcoin_client_js/src/lib/clientCommands.ts +++ b/bitcoin_client_js/src/lib/clientCommands.ts @@ -3,6 +3,7 @@ import { crypto } from 'bitcoinjs-lib'; import { BufferReader } from './buffertools'; import { hashLeaf, Merkle } from './merkle'; import { MerkleMap } from './merkleMap'; +import { WalletPolicy } from './policy'; import { createVarint, sanitizeBigintToNumber } from './varint'; enum ClientCommandCode { @@ -324,6 +325,14 @@ export class ClientCommandInterpreter { this.addKnownList(mm.values); } + addKnownWalletPolicy(wp: WalletPolicy): void { + this.addKnownPreimage(wp.serialize()); + this.addKnownList( + wp.keys.map((k) => Buffer.from(k, 'ascii')) + ); + this.addKnownPreimage(Buffer.from(wp.descriptorTemplate)); + } + execute(request: Buffer): Buffer { if (request.length == 0) { throw new Error('Unexpected empty command'); diff --git a/bitcoin_client_js/src/lib/policy.ts b/bitcoin_client_js/src/lib/policy.ts index 09c53cd3f..9e2c67ccd 100644 --- a/bitcoin_client_js/src/lib/policy.ts +++ b/bitcoin_client_js/src/lib/policy.ts @@ -3,6 +3,8 @@ import { crypto } from 'bitcoinjs-lib'; import { BufferWriter } from './buffertools'; import { hashLeaf, Merkle } from './merkle'; +const WALLET_POLICY_V2 = 2; + /** * The Bitcon hardware app uses a descriptors-like thing to describe * how to construct output scripts from keys. A "Wallet Policy" consists @@ -49,20 +51,29 @@ export class WalletPolicy { const m = new Merkle(keyBuffers.map((k) => hashLeaf(k))); const buf = new BufferWriter(); - buf.writeUInt8(0x01); // wallet type (policy map) + buf.writeUInt8(WALLET_POLICY_V2); // wallet version + + // length of wallet name, and wallet name buf.writeVarSlice(Buffer.from(this.name, 'ascii')); - buf.writeVarSlice(Buffer.from(this.descriptorTemplate, 'ascii')); + + // length of descriptor template + buf.writeVarInt(this.descriptorTemplate.length); + // sha256 hash of descriptor template + buf.writeSlice(crypto.sha256(Buffer.from(this.descriptorTemplate))); + + // number of keys buf.writeVarInt(this.keys.length); + // root of Merkle tree of keys buf.writeSlice(m.getRoot()); return buf.buffer(); } } export type DefaultDescriptorTemplate = - | 'pkh(@0)' - | 'sh(wpkh(@0))' - | 'wpkh(@0)' - | 'tr(@0)'; + | 'pkh(@0/**)' + | 'sh(wpkh(@0/**))' + | 'wpkh(@0/**)' + | 'tr(@0/**)'; /** * Simplified class to handle default wallet policies that can be used without policy registration. diff --git a/bitcoin_client_js/src/lib/psbtv2.ts b/bitcoin_client_js/src/lib/psbtv2.ts index 25cddc909..c4bf84be9 100644 --- a/bitcoin_client_js/src/lib/psbtv2.ts +++ b/bitcoin_client_js/src/lib/psbtv2.ts @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import * as bjs from 'bitcoinjs-lib'; + import { BufferReader, BufferWriter, @@ -9,6 +11,8 @@ import { import { sanitizeBigintToNumber } from './varint'; export enum psbtGlobal { + UNSIGNED_TX = 0x00, + XPUB = 0x01, TX_VERSION = 0x02, FALLBACK_LOCKTIME = 0x03, INPUT_COUNT = 0x04, @@ -22,6 +26,7 @@ export enum psbtIn { PARTIAL_SIG = 0x02, SIGHASH_TYPE = 0x03, REDEEM_SCRIPT = 0x04, + WITNESS_SCRIPT = 0x05, BIP32_DERIVATION = 0x06, FINAL_SCRIPTSIG = 0x07, FINAL_SCRIPTWITNESS = 0x08, @@ -33,6 +38,7 @@ export enum psbtIn { } export enum psbtOut { REDEEM_SCRIPT = 0x00, + WITNESS_SCRIPT = 0x01, BIP_32_DERIVATION = 0x02, AMOUNT = 0x03, SCRIPT = 0x04, @@ -113,21 +119,24 @@ export class PsbtV2 { } setInputWitnessUtxo( inputIndex: number, - amount: Buffer, + amount: number, scriptPubKey: Buffer ) { const buf = new BufferWriter(); - buf.writeSlice(amount); + buf.writeSlice(uint64LE(amount)); buf.writeVarSlice(scriptPubKey); this.setInput(inputIndex, psbtIn.WITNESS_UTXO, b(), buf.buffer()); } getInputWitnessUtxo( inputIndex: number - ): { readonly amount: Buffer; readonly scriptPubKey: Buffer } | undefined { + ): { readonly amount: number; readonly scriptPubKey: Buffer } | undefined { const utxo = this.getInputOptional(inputIndex, psbtIn.WITNESS_UTXO, b()); if (!utxo) return undefined; const buf = new BufferReader(utxo); - return { amount: buf.readSlice(8), scriptPubKey: buf.readVarSlice() }; + return { + amount: unsafeFrom64bitLE(buf.readSlice(8)), + scriptPubKey: buf.readVarSlice() + }; } setInputPartialSig(inputIndex: number, pubkey: Buffer, signature: Buffer) { this.setInput(inputIndex, psbtIn.PARTIAL_SIG, pubkey, signature); @@ -149,6 +158,12 @@ export class PsbtV2 { getInputRedeemScript(inputIndex: number): Buffer | undefined { return this.getInputOptional(inputIndex, psbtIn.REDEEM_SCRIPT, b()); } + setInputWitnessScript(inputIndex: number, witnessScript: Buffer) { + this.setInput(inputIndex, psbtIn.WITNESS_SCRIPT, b(), witnessScript); + } + getInputWitnessScript(inputIndex: number): Buffer | undefined { + return this.getInputOptional(inputIndex, psbtIn.WITNESS_SCRIPT, b()); + } setInputBip32Derivation( inputIndex: number, pubkey: Buffer, @@ -355,14 +370,156 @@ export class PsbtV2 { throw new Error('Invalid magic bytes'); } while (this.readKeyPair(this.globalMap, buf)); - for (let i = 0; i < this.getGlobalInputCount(); i++) { + + let psbtVersion: number; + try { + psbtVersion = this.getGlobalPsbtVersion(); + } catch { + psbtVersion = 0; + } + + if (psbtVersion !== 0 && psbtVersion !== 2) throw new Error("Only PSBTs of version 0 or 2 are supported"); + + let nInputs: number; + let nOutputs: number; + if (psbtVersion == 0) { + // if PSBTv0, we parse the PSBT_GLOBAL_UNSIGNED_TX field + const txRaw = this.getGlobal(psbtGlobal.UNSIGNED_TX); + const tx = bjs.Transaction.fromBuffer(txRaw); + nInputs = tx.ins.length; + nOutputs = tx.outs.length + } else { + // if PSBTv2, we already have the counts + nInputs = this.getGlobalInputCount(); + nOutputs = this.getGlobalOutputCount(); + } + + for (let i = 0; i < nInputs; i++) { this.inputMaps[i] = new Map(); while (this.readKeyPair(this.inputMaps[i], buf)); } - for (let i = 0; i < this.getGlobalOutputCount(); i++) { + for (let i = 0; i < nOutputs; i++) { this.outputMaps[i] = new Map(); while (this.readKeyPair(this.outputMaps[i], buf)); } + + this.normalizeToV2(); + } + normalizeToV2() { + // if the psbt is a PsbtV0, convert it to PsbtV2 instead. + // throw an error for any version other than 0 or 2, + const psbtVersion = this.getGlobalOptional(psbtGlobal.VERSION)?.readInt32LE(0); + if (psbtVersion === 2) return; + else if (psbtVersion !== undefined) { + throw new Error('Invalid or unsupported value for PSBT_GLOBAL_VERSION'); + } + + // Convert PsbtV0 to PsbtV2 by parsing the PSBT_GLOBAL_UNSIGNED_TX field + // and filling in the corresponding fields. + const txRaw = this.getGlobal(psbtGlobal.UNSIGNED_TX); + const tx = bjs.Transaction.fromBuffer(txRaw); + + this.setGlobalPsbtVersion(2); + this.setGlobalTxVersion(tx.version); + this.setGlobalFallbackLocktime(tx.locktime); + this.setGlobalInputCount(tx.ins.length); + this.setGlobalOutputCount(tx.outs.length); + + for (let i = 0; i < tx.ins.length; i++) { + this.setInputPreviousTxId(i, tx.ins[i].hash); + this.setInputOutputIndex(i, tx.ins[i].index); + this.setInputSequence(i, tx.ins[i].sequence); + } + + for (let i = 0; i < tx.outs.length; i++) { + this.setOutputAmount(i, tx.outs[i].value); + this.setOutputScript(i, tx.outs[i].script); + } + + // PSBT_GLOBAL_UNSIGNED_TX must be removed in a valid PSBTv2 + this.globalMap.delete(psbtGlobal.UNSIGNED_TX.toString(16).padStart(2, '0')); + } + /** + * Imports a BitcoinJS (bitcoinjs-lib) Psbt object. + * https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/psbt.ts + * + * Prepares the fields required for signing a Psbt on a Ledger + * device. It should be used exclusively before calling + * `appClient.signPsbt()` and not as a general Psbt conversion method. + * + * Note: This method supports all the policies that the Ledger is able to + * sign, with the exception of taproot: tr(@0). + */ + fromBitcoinJS(psbtBJS: bjs.Psbt) : PsbtV2 { + function isTaprootInput(input): boolean { + let isP2TR; + try { + bjs.payments.p2tr({ output: input.witnessUtxo.script }); + isP2TR = true; + } catch (err) { + isP2TR = false; + } + return ( + input && + !!( + input.tapInternalKey || + input.tapMerkleRoot || + (input.tapLeafScript && input.tapLeafScript.length) || + (input.tapBip32Derivation && input.tapBip32Derivation.length) || + isP2TR + ) + ); + } + this.setGlobalPsbtVersion(2); + this.setGlobalTxVersion(psbtBJS.version); + this.setGlobalInputCount(psbtBJS.data.inputs.length); + this.setGlobalOutputCount(psbtBJS.txOutputs.length); + if (psbtBJS.locktime !== undefined) + this.setGlobalFallbackLocktime(psbtBJS.locktime); + psbtBJS.data.inputs.forEach((input, index) => { + if (isTaprootInput(input)) + throw new Error(`Taproot inputs not supported`); + this.setInputPreviousTxId(index, psbtBJS.txInputs[index].hash); + if (psbtBJS.txInputs[index].sequence !== undefined) + this.setInputSequence(index, psbtBJS.txInputs[index].sequence); + this.setInputOutputIndex(index, psbtBJS.txInputs[index].index); + if (input.sighashType !== undefined) + this.setInputSighashType(index, input.sighashType); + if (input.nonWitnessUtxo) + this.setInputNonWitnessUtxo(index, input.nonWitnessUtxo); + if (input.witnessUtxo) { + this.setInputWitnessUtxo( + index, + input.witnessUtxo.value, + input.witnessUtxo.script + ); + } + if (input.witnessScript) + this.setInputWitnessScript(index, input.witnessScript); + if (input.redeemScript) + this.setInputRedeemScript(index, input.redeemScript); + psbtBJS.data.inputs[index].bip32Derivation.forEach(derivation => { + if (!/^m\//i.test(derivation.path)) + throw new Error(`Invalid input bip32 derivation`); + const pathArray = derivation.path + .replace(/m\//i, '') + .split('/') + .map(level => + level.match(/['h]/i) ? parseInt(level) + 0x80000000 : Number(level) + ); + this.setInputBip32Derivation( + index, + derivation.pubkey, + derivation.masterFingerprint, + pathArray + ); + }); + }); + psbtBJS.txOutputs.forEach((output, index) => { + this.setOutputAmount(index, output.value); + this.setOutputScript(index, output.script); + }); + return this; } private readKeyPair(map: Map, buf: BufferReader): boolean { const keyLen = sanitizeBigintToNumber(buf.readVarInt()); @@ -373,6 +530,7 @@ export class PsbtV2 { const keyData = buf.readSlice(keyLen - 1); const value = buf.readVarSlice(); set(map, keyType, keyData, value); + return true; } private getKeyDatas( @@ -560,9 +718,9 @@ function createKey(buf: Buffer): Key { return new Key(buf.readUInt8(0), buf.slice(1)); } function serializeMap(buf: BufferWriter, map: ReadonlyMap) { - for (const k in map.keys) { - const value = map.get(k)!; - const keyPair = new KeyPair(createKey(Buffer.from(k, 'hex')), value); + // serialize in lexicographical order of keys + for (let [key, value] of [...map].sort(([k1], [k2]) => k1.localeCompare(k2))) { + const keyPair = new KeyPair(createKey(Buffer.from(key, 'hex')), value); keyPair.serialize(buf); } buf.writeUInt8(0); diff --git a/bitcoin_client_rs/.gitignore b/bitcoin_client_rs/.gitignore new file mode 100644 index 000000000..be40ada9a --- /dev/null +++ b/bitcoin_client_rs/.gitignore @@ -0,0 +1,3 @@ +/target +/examples/ledger_hwi/target +/Cargo.lock diff --git a/bitcoin_client_rs/CONTRIBUTING.md b/bitcoin_client_rs/CONTRIBUTING.md new file mode 100644 index 000000000..82f92e46b --- /dev/null +++ b/bitcoin_client_rs/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing to `bitcoin_client_rs` + +## Workflow + +The codebase is maintained using the "contributor workflow" where everyone +without exception contributes patch proposals using "pull requests" (PRs). This +facilitates social contribution, easy testing and peer review. + +In general, [commits should be atomic](https://en.wikipedia.org/wiki/Atomic_commit#Atomic_commit_convention) +and diffs should be easy to read. For this reason, do not mix any formatting +fixes or code moves with actual code changes. + +When possible, make sure each individual commit is hygienic: that it builds successfully +on its own without warnings, errors, regressions, or test failures. + +Commit messages should be verbose by default consisting of a short subject line, +a blank line and detailed explanatory text as separate paragraph(s), unless the +title alone is self-explanatory. Commit messages should be helpful to people +reading your code in the future, so explain the reasoning for your decisions. + +If your pull request contains fixup commits (commits that change the same line of +code repeatedly) or too fine-grained commits, you may be asked to +[squash](https://git-scm.com/docs/git-rebase#_interactive_mode) your commits +before it will be merged. + +Patchsets should always be focused. For example, a pull request could add a +feature, fix a bug, or refactor code; but not a mixture. Please also avoid super +pull requests which attempt to do too much, are overly large, or overly complex +as this makes review difficult. Instead, prefer opening different focused pull requests. + + +## Minimum Supported Rust Version + +`bitcoin_client_rs` should always compile using **Rust 1.60** + +## Style + +Use [`rustfmt`](https://github.com/rust-lang/rustfmt) and +[Clippy](https://github.com/rust-lang/rust-clippy). diff --git a/bitcoin_client_rs/Cargo.toml b/bitcoin_client_rs/Cargo.toml new file mode 100644 index 000000000..cb3325f15 --- /dev/null +++ b/bitcoin_client_rs/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "ledger_bitcoin_client" +version = "0.4.1" +authors = ["Edouard Paris "] +edition = "2018" +description = "Ledger Bitcoin application client" +repository = "https://github.com/LedgerHQ/app-bitcoin-new" +license = "Apache-2.0" +documentation = "https://docs.rs/ledger_bitcoin_client/" + +[features] +default = ["async", "paranoid_client"] +async = ["async-trait"] + +# The paranoid_client feature makes sure that the client independently derives wallet +# policy addresses using rust-miniscript, returning an error if they do not match. +# It is strongly recommended to not disable this feature, unless the same check is +# performed elsewhere. +# Read more at https://donjon.ledger.com/lsb/019/ +paranoid_client = ["miniscript"] + +[dependencies] +async-trait = { version = "0.1", optional = true } +bitcoin = { version = "0.31", default-features = false, features = ["no-std"] } +miniscript = { version = "11.0", optional = true, default-features = false, features = ["no-std"] } + +[workspace] +members = ["examples/ledger_hwi"] + +# Dependencies used for tests and examples only. +[dev-dependencies] +hex = "0.4.3" +tokio = { version = "1.21", features = ["macros", "rt", "rt-multi-thread"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +base64 = "0.13.0" diff --git a/bitcoin_client_rs/README.md b/bitcoin_client_rs/README.md new file mode 100644 index 000000000..cf30dc7b2 --- /dev/null +++ b/bitcoin_client_rs/README.md @@ -0,0 +1,73 @@ +# Ledger Bitcoin application client + +Client library in [Rust](https://www.rust-lang.org/) for the Ledger +Bitcoin application with minimal dependencies. + +If you wish to contribute to this library, please read +[CONTRIBUTING.md](CONTRIBUTING.md). + +## Minimum Supported Rust Version + +`bitcoin_client_rs` should always compile using **Rust 1.60**. + +## Getting started + +The `client::BitcoinClient` struct implements the methods that call and +interpret the commands between the Ledger device and your software. + + +```rust +pub struct BitcoinClient {...} +impl BitcoinClient { + pub fn get_extended_pubkey( + &self, + path: &bitcoin::util::bip32::DerivationPath, + display: bool, + ) -> Result>; +} +``` + +It requires an internal connection implementing the `client::Transport` +Trait. + +```rust +pub trait Transport { + type Error: Debug; + fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error>; +} +``` + +In order to satisfy this Trait, it is possible to import the +`ledger-transport-hid` crate from https://github.com/Zondax/ledger-rs. +Please, read the `examples/ledger_hwi/src/transport.rs` file to find an example. + +## The `async` feature + +The optional feature `async` adds the `async_client` module to the crate +and imports the `async_trait` library. The `async_client::BitcoinClient` +struct is an asynchronous equivalent to the `BitcoinClient` struct. It +requires an internal connection implementing the `async_client::Transport` Trait. + +```rust +#[async_trait] +pub trait Transport { + type Error: Debug; + async fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error>; +} +``` + +## The `no-std` support + +Work in progress. + +## Example + +The code source for a simple tool to communicate with either a Ledger device or Speculos +emulator can be found in the `examples` directory. + +Example of a command to retrieve the extended pubkey with the given +derivation path and display it on the device screen: +``` +cargo run --package ledger_hwi -- \ +get-extended-pubkey --derivation-path "m/44'/0'/0'/0/0" --display +``` diff --git a/bitcoin_client_rs/examples/ledger_hwi/Cargo.toml b/bitcoin_client_rs/examples/ledger_hwi/Cargo.toml new file mode 100644 index 000000000..2931fed6f --- /dev/null +++ b/bitcoin_client_rs/examples/ledger_hwi/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ledger_hwi" +version = "0.1.0" +authors = ["edouard "] +edition = "2018" + +[dependencies] +clap = { version = "4.0.18", features = ["derive"] } +ledger_bitcoin_client = { path = "../.." } +async-trait = { version = "0.1"} +bitcoin = { version = "0.31", default-features = false, features = ["no-std"] } +hex = "0.4" +base64 = "0.13.0" +ledger-apdu = "0.10" +ledger-transport-hid = "0.10" +hidapi = { version = "1.4.1", features = ["linux-static-hidraw"], default-features = false } +tokio = { version = "1.21", features = ["macros", "net", "rt", "rt-multi-thread", "io-util", "sync"] } +regex = "1.6.0" + diff --git a/bitcoin_client_rs/examples/ledger_hwi/README.md b/bitcoin_client_rs/examples/ledger_hwi/README.md new file mode 100644 index 000000000..bef14f96c --- /dev/null +++ b/bitcoin_client_rs/examples/ledger_hwi/README.md @@ -0,0 +1,6 @@ +# `ledger_hwi` + +This tool first looks for a speculos simulator listening +at 127.0.0.1:9999, then looks for hid devices. + +`cargo run -- --help` diff --git a/bitcoin_client_rs/examples/ledger_hwi/src/main.rs b/bitcoin_client_rs/examples/ledger_hwi/src/main.rs new file mode 100644 index 000000000..0b53ed52d --- /dev/null +++ b/bitcoin_client_rs/examples/ledger_hwi/src/main.rs @@ -0,0 +1,231 @@ +use std::error::Error; +use std::str::FromStr; +use std::sync::Arc; + +use bitcoin::{bip32, hashes::hex::FromHex, psbt::Psbt}; + +use hidapi::HidApi; +use ledger_transport_hid::TransportNativeHID; +use regex::Regex; + +use ledger_bitcoin_client::{ + async_client::{BitcoinClient, Transport}, + psbt::PartialSignature, + wallet::{Version, WalletPolicy, WalletPubKey}, +}; + +mod transport; +use transport::{TransportHID, TransportTcp, TransportWrapper}; + +use clap::{Parser, Subcommand}; + +/// Ledger Hardware Wallet Interface +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[command(subcommand)] + command: Option, +} + +#[derive(Debug, Subcommand)] +enum Commands { + GetAppVersion, + GetFingerprint, + GetExtendedPubkey { + #[arg(long)] + derivation_path: String, + #[arg(short, long, default_value_t = false)] + display: bool, + }, + RegisterWallet { + #[arg(long)] + name: String, + #[arg(long)] + policy: String, + }, + Sign { + #[arg(long)] + psbt: String, + #[arg(long)] + name: String, + #[arg(long)] + policy: String, + #[arg(long)] + hmac: String, + }, + SignMessage { + #[arg(long)] + message: String, + #[arg(long)] + derivation_path: String, + }, +} + +#[tokio::main] +async fn main() { + let args = Args::parse(); + + let transport: Arc> + Send + Sync> = + if let Ok(transport) = TransportTcp::new().await { + Arc::new(transport) + } else { + Arc::new(TransportHID::new( + TransportNativeHID::new(&HidApi::new().expect("unable to get HIDAPI")).unwrap(), + )) + }; + + let client = BitcoinClient::new(TransportWrapper::new(transport)); + + match args.command { + Some(Commands::GetAppVersion) => { + let (name, version, flags) = client.get_version().await.unwrap(); + println!( + "name: {}\nversion: {}\nflags: {}", + name, + version, + hex::encode(flags) + ); + } + Some(Commands::GetFingerprint) => { + let fg = client.get_master_fingerprint().await.unwrap(); + println!("{}", fg); + } + Some(Commands::GetExtendedPubkey { + derivation_path, + display, + }) => { + get_extended_pubkey(&client, &derivation_path, display) + .await + .unwrap(); + } + Some(Commands::RegisterWallet { name, policy }) => { + register_wallet(&client, &name, &policy).await.unwrap(); + } + Some(Commands::Sign { + psbt, + name, + policy, + hmac, + }) => { + sign(&client, &psbt, &name, &policy, Some(&hmac)) + .await + .unwrap(); + } + Some(Commands::SignMessage { + message, + derivation_path, + }) => { + sign_message(&client, &message, &derivation_path) + .await + .unwrap(); + } + _ => {} + } +} + +async fn get_extended_pubkey( + client: &BitcoinClient, + derivation_path: &str, + display: bool, +) -> Result<(), Box> { + let path = bip32::DerivationPath::from_str(&derivation_path).map_err(|e| format!("{}", e))?; + let xpk = client + .get_extended_pubkey(&path, display) + .await + .map_err(|e| format!("{:#?}", e))?; + println!("{}", xpk); + Ok(()) +} + +async fn register_wallet( + client: &BitcoinClient, + name: &str, + policy: &str, +) -> Result<(), Box> { + let (descriptor_template, keys) = extract_keys_and_template(policy)?; + let wallet = WalletPolicy::new(name.to_string(), Version::V2, descriptor_template, keys); + let (_id, hmac) = client + .register_wallet(&wallet) + .await + .map_err(|e| format!("{:#?}", e))?; + println!("{}", hex::encode(hmac)); + Ok(()) +} + +async fn sign( + client: &BitcoinClient, + psbt: &str, + name: &str, + policy: &str, + hmac: Option<&str>, +) -> Result<(), Box> { + let psbt = Psbt::deserialize(&base64::decode(&psbt)?).map_err(|e| format!("{:#?}", e))?; + let (descriptor_template, keys) = extract_keys_and_template(policy)?; + let wallet = WalletPolicy::new(name.to_string(), Version::V2, descriptor_template, keys); + let hmac = if let Some(s) = hmac { + let mut h = [b'\0'; 32]; + h.copy_from_slice(&Vec::from_hex(&s).map_err(|e| format!("{:#?}", e))?); + Some(h) + } else { + None + }; + + let res = client + .sign_psbt(&psbt, &wallet, hmac.as_ref()) + .await + .map_err(|e| format!("{:#?}", e))?; + + for (index, sig) in res { + match sig { + PartialSignature::Sig(key, sig) => { + println!("index: {}, key: {}, sig: {}", index, key, sig); + } + PartialSignature::TapScriptSig(key, tapleaf_hash, sig) => { + println!( + "index: {}, key: {}, tapleaf_hash: {}, sig: {}", + index, + key, + tapleaf_hash + .map(|h| hex::encode(h)) + .unwrap_or("none".to_string()), + hex::encode(sig.to_vec()) + ); + } + } + } + Ok(()) +} + +fn extract_keys_and_template(policy: &str) -> Result<(String, Vec), Box> { + let re = Regex::new(r"((\[.+?\])?[xyYzZtuUvV]pub[1-9A-HJ-NP-Za-km-z]{79,108})").unwrap(); + let mut descriptor_template = policy.to_string(); + let mut pubkeys: Vec = Vec::new(); + for (index, capture) in re.find_iter(policy).enumerate() { + let pubkey = WalletPubKey::from_str(capture.as_str()).map_err(|e| format!("{}", e))?; + if !pubkeys.contains(&pubkey) { + pubkeys.push(pubkey); + } + descriptor_template = descriptor_template.replace(capture.as_str(), &format!("@{}", index)); + } + if let Some((descriptor_template, _hash)) = descriptor_template.rsplit_once("#") { + Ok((descriptor_template.to_string(), pubkeys)) + } else { + Ok((descriptor_template, pubkeys)) + } +} + +async fn sign_message( + client: &BitcoinClient, + message: &str, + derivation_path: &str, +) -> Result<(), Box> { + let path = bip32::DerivationPath::from_str(&derivation_path).map_err(|e| format!("{}", e))?; + let (header, ecdsa_sig) = client + .sign_message(message.as_bytes(), &path) + .await + .map_err(|e| format!("{:#?}", e))?; + let mut sig = vec![header]; + sig.extend(&ecdsa_sig.serialize_compact()); + println!("{}", base64::encode(sig)); + Ok(()) +} diff --git a/bitcoin_client_rs/examples/ledger_hwi/src/transport.rs b/bitcoin_client_rs/examples/ledger_hwi/src/transport.rs new file mode 100644 index 000000000..7a6b4ca45 --- /dev/null +++ b/bitcoin_client_rs/examples/ledger_hwi/src/transport.rs @@ -0,0 +1,109 @@ +use std::convert::TryFrom; +use std::error::Error; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::Arc; + +use async_trait::async_trait; +use ledger_apdu::APDUAnswer; +use ledger_transport_hid::TransportNativeHID; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, + sync::Mutex, +}; + +use ledger_bitcoin_client::{ + apdu::{APDUCommand, StatusWord}, + async_client::Transport, +}; + +/// Transport with the Ledger device. +pub struct TransportHID(TransportNativeHID); + +impl TransportHID { + pub fn new(t: TransportNativeHID) -> Self { + Self(t) + } +} + +#[async_trait] +impl Transport for TransportHID { + type Error = Box; + async fn exchange(&self, cmd: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error> { + self.0 + .exchange(&ledger_apdu::APDUCommand { + ins: cmd.ins, + cla: cmd.cla, + p1: cmd.p1, + p2: cmd.p2, + data: cmd.data.clone(), + }) + .map(|answer| { + ( + StatusWord::try_from(answer.retcode()).unwrap_or(StatusWord::Unknown), + answer.data().to_vec(), + ) + }) + .map_err(|e| e.into()) + } +} + +/// Transport to communicate with the Ledger Speculos simulator. +pub struct TransportTcp { + connection: Mutex, +} + +impl TransportTcp { + pub async fn new() -> Result> { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9999); + let stream = TcpStream::connect(addr).await?; + Ok(Self { + connection: Mutex::new(stream), + }) + } +} + +#[async_trait] +impl Transport for TransportTcp { + type Error = Box; + async fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error> { + let mut stream = self.connection.lock().await; + let command_bytes = command.encode(); + + let mut req = vec![0u8; command_bytes.len() + 4]; + req[..4].copy_from_slice(&(command_bytes.len() as u32).to_be_bytes()); + req[4..].copy_from_slice(&command_bytes); + stream.write(&req).await?; + + let mut buff = [0u8; 4]; + let len = match stream.read(&mut buff).await? { + 4 => u32::from_be_bytes(buff), + _ => return Err("Invalid Length".into()), + }; + + let mut resp = vec![0u8; len as usize + 2]; + stream.read_exact(&mut resp).await?; + let answer = APDUAnswer::from_answer(resp).map_err(|_| "Invalid Answer")?; + Ok(( + StatusWord::try_from(answer.retcode()).unwrap_or(StatusWord::Unknown), + answer.data().to_vec(), + )) + } +} + +/// Wrapper to handle both hid and tcp transport. +pub struct TransportWrapper(Arc> + Sync + Send>); + +impl TransportWrapper { + pub fn new(t: Arc> + Sync + Send>) -> Self { + Self(t) + } +} + +#[async_trait] +impl Transport for TransportWrapper { + type Error = Box; + async fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error> { + self.0.exchange(command).await + } +} diff --git a/bitcoin_client_rs/src/apdu.rs b/bitcoin_client_rs/src/apdu.rs new file mode 100644 index 000000000..d09de3808 --- /dev/null +++ b/bitcoin_client_rs/src/apdu.rs @@ -0,0 +1,135 @@ +use core::convert::TryFrom; +use core::fmt::Debug; + +// p2 encodes the protocol version implemented +pub const CURRENT_PROTOCOL_VERSION: u8 = 1; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum Cla { + Default = 0xB0, + Bitcoin = 0xE1, + Framework = 0xF8, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum BitcoinCommandCode { + GetExtendedPubkey = 0x00, + GetVersion = 0x01, + RegisterWallet = 0x02, + GetWalletAddress = 0x03, + SignPSBT = 0x04, + GetMasterFingerprint = 0x05, + SignMessage = 0x10, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum FrameworkCommandCode { + ContinueInterrupted = 0x01, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum ClientCommandCode { + Yield = 0x10, + GetPreimage = 0x40, + GetMerkleLeafProof = 0x41, + GetMerkleLeafIndex = 0x42, + GetMoreElements = 0xA0, +} + +impl TryFrom for ClientCommandCode { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0x10 => Ok(ClientCommandCode::Yield), + 0x40 => Ok(ClientCommandCode::GetPreimage), + 0x41 => Ok(ClientCommandCode::GetMerkleLeafProof), + 0x42 => Ok(ClientCommandCode::GetMerkleLeafIndex), + 0xA0 => Ok(ClientCommandCode::GetMoreElements), + _ => Err(()), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u16)] +pub enum StatusWord { + /// Rejected by user + Deny = 0x6985, + /// Incorrect Data + IncorrectData = 0x6A80, + /// Not Supported + NotSupported = 0x6A82, + /// Wrong P1P2 + WrongP1P2 = 0x6A86, + /// Wrong DataLength + WrongDataLength = 0x6A87, + /// Ins not supported + InsNotSupported = 0x6D00, + /// Cla not supported + ClaNotSupported = 0x6E00, + /// Bad state + BadState = 0xB007, + /// Signature fail + SignatureFail = 0xB008, + /// Success + OK = 0x9000, + /// The command is interrupted, and requires the client's response + InterruptedExecution = 0xE000, + /// Unknown + Unknown, +} + +impl TryFrom for StatusWord { + type Error = (); + + fn try_from(value: u16) -> Result { + match value { + 0x6985 => Ok(StatusWord::Deny), + 0x6A80 => Ok(StatusWord::IncorrectData), + 0x6A82 => Ok(StatusWord::NotSupported), + 0x6A86 => Ok(StatusWord::WrongP1P2), + 0x6A87 => Ok(StatusWord::WrongDataLength), + 0x6D00 => Ok(StatusWord::InsNotSupported), + 0x6E00 => Ok(StatusWord::ClaNotSupported), + 0xB007 => Ok(StatusWord::BadState), + 0xB008 => Ok(StatusWord::SignatureFail), + 0x9000 => Ok(StatusWord::OK), + 0xE000 => Ok(StatusWord::InterruptedExecution), + _ => Err(()), + } + } +} + +#[derive(Clone)] +pub struct APDUCommand { + pub cla: u8, + pub ins: u8, + pub p1: u8, + pub p2: u8, + pub data: Vec, +} + +impl APDUCommand { + pub fn encode(&self) -> Vec { + let mut vec = vec![self.cla, self.ins, self.p1, self.p2, self.data.len() as u8]; + vec.extend(self.data.iter()); + vec + } +} + +impl core::default::Default for APDUCommand { + fn default() -> Self { + Self { + cla: Cla::Default as u8, + ins: 0x00, + p1: 0x00, + p2: CURRENT_PROTOCOL_VERSION as u8, + data: Vec::new(), + } + } +} diff --git a/bitcoin_client_rs/src/async_client.rs b/bitcoin_client_rs/src/async_client.rs new file mode 100644 index 000000000..f2086f477 --- /dev/null +++ b/bitcoin_client_rs/src/async_client.rs @@ -0,0 +1,388 @@ +use core::fmt::Debug; +use core::str::FromStr; + +use async_trait::async_trait; + +use bitcoin::{ + address, + bip32::{DerivationPath, Fingerprint, Xpub}, + consensus::encode::{deserialize_partial, VarInt}, + secp256k1::ecdsa::Signature, + Psbt, +}; + +#[cfg(feature = "paranoid_client")] +use miniscript::{Descriptor, DescriptorPublicKey}; + +use crate::{ + apdu::{APDUCommand, StatusWord}, + command, + error::BitcoinClientError, + interpreter::{get_merkleized_map_commitment, ClientCommandInterpreter}, + psbt::*, + wallet::WalletPolicy, +}; + +/// BitcoinClient calls and interprets commands with the Ledger Device. +/// The methods can only be used by an asynchronous engine like tokio. +pub struct BitcoinClient { + transport: T, +} + +impl BitcoinClient { + pub fn new(transport: T) -> Self { + Self { transport } + } + + async fn make_request( + &self, + req: &APDUCommand, + interpreter: Option<&mut ClientCommandInterpreter>, + ) -> Result, BitcoinClientError> { + let (mut sw, mut data) = self + .transport + .exchange(req) + .await + .map_err(BitcoinClientError::Transport)?; + + if let Some(interpreter) = interpreter { + while sw == StatusWord::InterruptedExecution { + let response = interpreter.execute(data)?; + let res = self + .transport + .exchange(&command::continue_interrupted(response)) + .await + .map_err(BitcoinClientError::Transport)?; + sw = res.0; + data = res.1; + } + } + + if sw != StatusWord::OK { + Err(BitcoinClientError::Device { + status: sw, + command: req.ins, + }) + } else { + Ok(data) + } + } + + // Verifies that the address that the application returns matches the one independently + // computed on the client + #[cfg(feature = "paranoid_client")] + async fn check_address( + &self, + wallet: &WalletPolicy, + change: bool, + address_index: u32, + expected_address: &bitcoin::Address, + ) -> Result<(), BitcoinClientError> { + let desc_str = wallet + .get_descriptor(change) + .map_err(|_| BitcoinClientError::ClientError("Failed to get descriptor".to_string()))?; + let descriptor = Descriptor::::from_str(&desc_str).map_err(|_| { + BitcoinClientError::ClientError("Failed to parse descriptor".to_string()) + })?; + + if descriptor + .at_derivation_index(address_index) + .map_err(|_| { + BitcoinClientError::ClientError("Failed to derive descriptor".to_string()) + })? + .script_pubkey() + != expected_address.payload().script_pubkey() + { + return Err(BitcoinClientError::InvalidResponse("Invalid address. Please update your Bitcoin app. If the problem persists, report a bug at https://github.com/LedgerHQ/app-bitcoin-new".to_string())); + } + + Ok(()) + } + + /// Returns the currently running app's name, version and state flags + pub async fn get_version( + &self, + ) -> Result<(String, String, Vec), BitcoinClientError> { + let cmd = command::get_version(); + let data = self.make_request(&cmd, None).await?; + if data.is_empty() || data[0] != 0x01 { + return Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + }); + } + + let (name, i): (String, usize) = + deserialize_partial(&data[1..]).map_err(|_| BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.clone(), + })?; + + let (version, j): (String, usize) = deserialize_partial(&data[i + 1..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.clone(), + } + })?; + + let (flags, _): (Vec, usize) = + deserialize_partial(&data[i + j + 1..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.clone(), + } + })?; + + Ok((name, version, flags)) + } + + /// Retrieve the master fingerprint. + pub async fn get_master_fingerprint( + &self, + ) -> Result> { + let cmd = command::get_master_fingerprint(); + self.make_request(&cmd, None).await.and_then(|data| { + if data.len() < 4 { + Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + }) + } else { + let mut fg = [0x00; 4]; + fg.copy_from_slice(&data[0..4]); + Ok(Fingerprint::from(fg)) + } + }) + } + + /// Retrieve the bip32 extended pubkey derived with the given path + /// and optionally display it on screen + pub async fn get_extended_pubkey( + &self, + path: &DerivationPath, + display: bool, + ) -> Result> { + let cmd = command::get_extended_pubkey(path, display); + self.make_request(&cmd, None).await.and_then(|data| { + Xpub::from_str(&String::from_utf8_lossy(&data)).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + } + }) + }) + } + + /// Registers the given wallet policy, returns the wallet ID and HMAC. + pub async fn register_wallet( + &self, + wallet: &WalletPolicy, + ) -> Result<([u8; 32], [u8; 32]), BitcoinClientError> { + let cmd = command::register_wallet(wallet); + let mut intpr = ClientCommandInterpreter::new(); + intpr.add_known_preimage(wallet.serialize()); + let keys: Vec = wallet.keys.iter().map(|k| k.to_string()).collect(); + intpr.add_known_list(&keys); + //necessary for version 1 of the protocol (introduced in version 2.1.0) + intpr.add_known_preimage(wallet.descriptor_template.as_bytes().to_vec()); + let (id, hmac) = self + .make_request(&cmd, Some(&mut intpr)) + .await + .and_then(|data| { + if data.len() < 64 { + Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + }) + } else { + let mut id = [0x00; 32]; + id.copy_from_slice(&data[0..32]); + let mut hmac = [0x00; 32]; + hmac.copy_from_slice(&data[32..64]); + Ok((id, hmac)) + } + })?; + + #[cfg(feature = "paranoid_client")] + { + let device_addr = self + .get_wallet_address(wallet, Some(&hmac), false, 0, false) + .await?; + + self.check_address(wallet, false, 0, &device_addr).await?; + } + + Ok((id, hmac)) + } + + /// For a given wallet that was already registered on the device (or a standard wallet that does not need registration), + /// returns the address for a certain `change`/`address_index` combination. + pub async fn get_wallet_address( + &self, + wallet: &WalletPolicy, + wallet_hmac: Option<&[u8; 32]>, + change: bool, + address_index: u32, + display: bool, + ) -> Result, BitcoinClientError> { + let mut intpr = ClientCommandInterpreter::new(); + intpr.add_known_preimage(wallet.serialize()); + let keys: Vec = wallet.keys.iter().map(|k| k.to_string()).collect(); + intpr.add_known_list(&keys); + // necessary for version 1 of the protocol (introduced in version 2.1.0) + intpr.add_known_preimage(wallet.descriptor_template.as_bytes().to_vec()); + let cmd = command::get_wallet_address(wallet, wallet_hmac, change, address_index, display); + let address = self + .make_request(&cmd, Some(&mut intpr)) + .await + .and_then(|data| { + bitcoin::Address::from_str(&String::from_utf8_lossy(&data)).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + } + }) + })?; + + #[cfg(feature = "paranoid_client")] + { + self.check_address(wallet, change, address_index, &address) + .await?; + } + + Ok(address) + } + + /// Signs a PSBT using a registered wallet (or a standard wallet that does not need registration). + /// Signature requires explicit approval from the user. + #[allow(clippy::type_complexity)] + pub async fn sign_psbt( + &self, + psbt: &Psbt, + wallet: &WalletPolicy, + wallet_hmac: Option<&[u8; 32]>, + ) -> Result, BitcoinClientError> { + let mut intpr = ClientCommandInterpreter::new(); + intpr.add_known_preimage(wallet.serialize()); + let keys: Vec = wallet.keys.iter().map(|k| k.to_string()).collect(); + intpr.add_known_list(&keys); + // necessary for version 1 of the protocol (introduced in version 2.1.0) + intpr.add_known_preimage(wallet.descriptor_template.as_bytes().to_vec()); + + let global_map: Vec<(Vec, Vec)> = get_v2_global_pairs(psbt) + .into_iter() + .map(deserialize_pair) + .collect(); + intpr.add_known_mapping(&global_map); + let global_mapping_commitment = get_merkleized_map_commitment(&global_map); + + let mut input_commitments: Vec> = Vec::with_capacity(psbt.inputs.len()); + for (index, input) in psbt.inputs.iter().enumerate() { + let txin = psbt + .unsigned_tx + .input + .get(index) + .ok_or(BitcoinClientError::InvalidPsbt)?; + let input_map: Vec<(Vec, Vec)> = get_v2_input_pairs(input, txin) + .into_iter() + .map(deserialize_pair) + .collect(); + intpr.add_known_mapping(&input_map); + input_commitments.push(get_merkleized_map_commitment(&input_map)); + } + let input_commitments_root = intpr.add_known_list(&input_commitments); + + let mut output_commitments: Vec> = Vec::with_capacity(psbt.outputs.len()); + for (index, output) in psbt.outputs.iter().enumerate() { + let txout = psbt + .unsigned_tx + .output + .get(index) + .ok_or(BitcoinClientError::InvalidPsbt)?; + let output_map: Vec<(Vec, Vec)> = get_v2_output_pairs(output, txout) + .into_iter() + .map(deserialize_pair) + .collect(); + intpr.add_known_mapping(&output_map); + output_commitments.push(get_merkleized_map_commitment(&output_map)); + } + let output_commitments_root = intpr.add_known_list(&output_commitments); + + let cmd = command::sign_psbt( + &global_mapping_commitment, + psbt.inputs.len(), + &input_commitments_root, + psbt.outputs.len(), + &output_commitments_root, + wallet, + wallet_hmac, + ); + + self.make_request(&cmd, Some(&mut intpr)).await?; + + let results = intpr.yielded(); + if results.iter().any(|res| res.len() <= 1) { + return Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: results.into_iter().fold(Vec::new(), |mut acc, res| { + acc.extend(res); + acc + }), + }); + } + + let mut signatures = Vec::new(); + for result in results { + let (input_index, i): (VarInt, usize) = + deserialize_partial(&result).map_err(|_| BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: result.clone(), + })?; + + signatures.push(( + input_index.0 as usize, + PartialSignature::from_slice(&result[i..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: result.clone(), + } + })?, + )); + } + + Ok(signatures) + } + + /// Sign a message with the key derived with the given derivation path. + /// Result is the header byte (31-34: P2PKH compressed) and the ecdsa signature. + pub async fn sign_message( + &self, + message: &[u8], + path: &DerivationPath, + ) -> Result<(u8, Signature), BitcoinClientError> { + let chunks: Vec<&[u8]> = message.chunks(64).collect(); + let mut intpr = ClientCommandInterpreter::new(); + let message_commitment_root = intpr.add_known_list(&chunks); + let cmd = command::sign_message(message.len(), &message_commitment_root, path); + self.make_request(&cmd, Some(&mut intpr)) + .await + .and_then(|data| { + Ok(( + data[0], + Signature::from_compact(&data[1..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.to_vec(), + } + })?, + )) + }) + } +} + +/// Asynchronous communication layer between the bitcoin client and the Ledger device. +#[async_trait] +pub trait Transport { + type Error: Debug; + async fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error>; +} diff --git a/bitcoin_client_rs/src/client.rs b/bitcoin_client_rs/src/client.rs new file mode 100644 index 000000000..a721e4bc1 --- /dev/null +++ b/bitcoin_client_rs/src/client.rs @@ -0,0 +1,366 @@ +use core::fmt::Debug; +use core::str::FromStr; + +use bitcoin::{ + address, + bip32::{DerivationPath, Fingerprint, Xpub}, + consensus::encode::{deserialize_partial, VarInt}, + psbt::Psbt, + secp256k1::ecdsa, +}; + +#[cfg(feature = "paranoid_client")] +use miniscript::{Descriptor, DescriptorPublicKey}; + +use crate::{ + apdu::{APDUCommand, StatusWord}, + command, + error::BitcoinClientError, + interpreter::{get_merkleized_map_commitment, ClientCommandInterpreter}, + psbt::*, + wallet::WalletPolicy, +}; + +/// BitcoinClient calls and interprets commands with the Ledger Device. +pub struct BitcoinClient { + transport: T, +} + +impl BitcoinClient { + pub fn new(transport: T) -> Self { + Self { transport } + } + + fn make_request( + &self, + req: &APDUCommand, + interpreter: Option<&mut ClientCommandInterpreter>, + ) -> Result, BitcoinClientError> { + let (mut sw, mut data) = self + .transport + .exchange(req) + .map_err(BitcoinClientError::Transport)?; + + if let Some(interpreter) = interpreter { + while sw == StatusWord::InterruptedExecution { + let response = interpreter.execute(data)?; + let res = self + .transport + .exchange(&command::continue_interrupted(response)) + .map_err(BitcoinClientError::Transport)?; + sw = res.0; + data = res.1; + } + } + + if sw != StatusWord::OK { + Err(BitcoinClientError::Device { + status: sw, + command: req.ins, + }) + } else { + Ok(data) + } + } + + // Verifies that the address that the application returns matches the one independently + // computed on the client + #[cfg(feature = "paranoid_client")] + fn check_address( + &self, + wallet: &WalletPolicy, + change: bool, + address_index: u32, + expected_address: &bitcoin::Address, + ) -> Result<(), BitcoinClientError> { + let desc_str = wallet + .get_descriptor(change) + .map_err(|_| BitcoinClientError::ClientError("Failed to get descriptor".to_string()))?; + let descriptor = Descriptor::::from_str(&desc_str).map_err(|_| { + BitcoinClientError::ClientError("Failed to parse descriptor".to_string()) + })?; + + if descriptor + .at_derivation_index(address_index) + .map_err(|_| { + BitcoinClientError::ClientError("Failed to derive descriptor".to_string()) + })? + .script_pubkey() + != expected_address.payload().script_pubkey() + { + return Err(BitcoinClientError::InvalidResponse("Invalid address. Please update your Bitcoin app. If the problem persists, report a bug at https://github.com/LedgerHQ/app-bitcoin-new".to_string())); + } + + Ok(()) + } + + /// Returns the currently running app's name, version and state flags + pub fn get_version(&self) -> Result<(String, String, Vec), BitcoinClientError> { + let cmd = command::get_version(); + let data = self.make_request(&cmd, None)?; + if data.is_empty() || data[0] != 0x01 { + return Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + }); + } + + let (name, i): (String, usize) = + deserialize_partial(&data[1..]).map_err(|_| BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.clone(), + })?; + + let (version, j): (String, usize) = deserialize_partial(&data[i + 1..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.clone(), + } + })?; + + let (flags, _): (Vec, usize) = + deserialize_partial(&data[i + j + 1..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.clone(), + } + })?; + + Ok((name, version, flags)) + } + + /// Retrieve the master fingerprint. + pub fn get_master_fingerprint(&self) -> Result> { + let cmd = command::get_master_fingerprint(); + self.make_request(&cmd, None).and_then(|data| { + if data.len() < 4 { + Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + }) + } else { + let mut fg = [0x00; 4]; + fg.copy_from_slice(&data[0..4]); + Ok(Fingerprint::from(fg)) + } + }) + } + + /// Retrieve the bip32 extended pubkey derived with the given path + /// and optionally display it on screen + pub fn get_extended_pubkey( + &self, + path: &DerivationPath, + display: bool, + ) -> Result> { + let cmd = command::get_extended_pubkey(path, display); + self.make_request(&cmd, None).and_then(|data| { + Xpub::from_str(&String::from_utf8_lossy(&data)).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + } + }) + }) + } + + /// Registers the given wallet policy, returns the wallet ID and HMAC. + #[allow(clippy::type_complexity)] + pub fn register_wallet( + &self, + wallet: &WalletPolicy, + ) -> Result<([u8; 32], [u8; 32]), BitcoinClientError> { + let cmd = command::register_wallet(wallet); + let mut intpr = ClientCommandInterpreter::new(); + intpr.add_known_preimage(wallet.serialize()); + let keys: Vec = wallet.keys.iter().map(|k| k.to_string()).collect(); + intpr.add_known_list(&keys); + // necessary for version 1 of the protocol (introduced in version 2.1.0) + intpr.add_known_preimage(wallet.descriptor_template.as_bytes().to_vec()); + let (id, hmac) = self.make_request(&cmd, Some(&mut intpr)).and_then(|data| { + if data.len() < 64 { + Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + }) + } else { + let mut id = [0x00; 32]; + id.copy_from_slice(&data[0..32]); + let mut hmac = [0x00; 32]; + hmac.copy_from_slice(&data[32..64]); + Ok((id, hmac)) + } + })?; + + #[cfg(feature = "paranoid_client")] + { + let device_addr = self.get_wallet_address(wallet, Some(&hmac), false, 0, false)?; + self.check_address(wallet, false, 0, &device_addr)?; + } + + Ok((id, hmac)) + } + + /// For a given wallet that was already registered on the device (or a standard wallet that does not need registration), + /// returns the address for a certain `change`/`address_index` combination. + pub fn get_wallet_address( + &self, + wallet: &WalletPolicy, + wallet_hmac: Option<&[u8; 32]>, + change: bool, + address_index: u32, + display: bool, + ) -> Result, BitcoinClientError> { + let mut intpr = ClientCommandInterpreter::new(); + intpr.add_known_preimage(wallet.serialize()); + let keys: Vec = wallet.keys.iter().map(|k| k.to_string()).collect(); + intpr.add_known_list(&keys); + // necessary for version 1 of the protocol (introduced in version 2.1.0) + intpr.add_known_preimage(wallet.descriptor_template.as_bytes().to_vec()); + let cmd = command::get_wallet_address(wallet, wallet_hmac, change, address_index, display); + let address = self.make_request(&cmd, Some(&mut intpr)).and_then(|data| { + bitcoin::Address::::from_str(&String::from_utf8_lossy(&data)) + .map_err(|_| BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data, + }) + })?; + + #[cfg(feature = "paranoid_client")] + { + self.check_address(wallet, change, address_index, &address)?; + } + + Ok(address) + } + + /// Signs a PSBT using a registered wallet (or a standard wallet that does not need registration). + /// Signature requires explicit approval from the user. + #[allow(clippy::type_complexity)] + pub fn sign_psbt( + &self, + psbt: &Psbt, + wallet: &WalletPolicy, + wallet_hmac: Option<&[u8; 32]>, + ) -> Result, BitcoinClientError> { + let mut intpr = ClientCommandInterpreter::new(); + intpr.add_known_preimage(wallet.serialize()); + let keys: Vec = wallet.keys.iter().map(|k| k.to_string()).collect(); + intpr.add_known_list(&keys); + // necessary for version 1 of the protocol (introduced in version 2.1.0) + intpr.add_known_preimage(wallet.descriptor_template.as_bytes().to_vec()); + + let global_map: Vec<(Vec, Vec)> = get_v2_global_pairs(psbt) + .into_iter() + .map(deserialize_pair) + .collect(); + intpr.add_known_mapping(&global_map); + let global_mapping_commitment = get_merkleized_map_commitment(&global_map); + + let mut input_commitments: Vec> = Vec::with_capacity(psbt.inputs.len()); + for (index, input) in psbt.inputs.iter().enumerate() { + let txin = psbt + .unsigned_tx + .input + .get(index) + .ok_or(BitcoinClientError::InvalidPsbt)?; + let input_map: Vec<(Vec, Vec)> = get_v2_input_pairs(input, txin) + .into_iter() + .map(deserialize_pair) + .collect(); + intpr.add_known_mapping(&input_map); + input_commitments.push(get_merkleized_map_commitment(&input_map)); + } + let input_commitments_root = intpr.add_known_list(&input_commitments); + + let mut output_commitments: Vec> = Vec::with_capacity(psbt.outputs.len()); + for (index, output) in psbt.outputs.iter().enumerate() { + let txout = psbt + .unsigned_tx + .output + .get(index) + .ok_or(BitcoinClientError::InvalidPsbt)?; + let output_map: Vec<(Vec, Vec)> = get_v2_output_pairs(output, txout) + .into_iter() + .map(deserialize_pair) + .collect(); + intpr.add_known_mapping(&output_map); + output_commitments.push(get_merkleized_map_commitment(&output_map)); + } + let output_commitments_root = intpr.add_known_list(&output_commitments); + + let cmd = command::sign_psbt( + &global_mapping_commitment, + psbt.inputs.len(), + &input_commitments_root, + psbt.outputs.len(), + &output_commitments_root, + wallet, + wallet_hmac, + ); + + self.make_request(&cmd, Some(&mut intpr))?; + + let results = intpr.yielded(); + if results.iter().any(|res| res.len() <= 1) { + return Err(BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: results.into_iter().fold(Vec::new(), |mut acc, res| { + acc.extend(res); + acc + }), + }); + } + + let mut signatures = Vec::new(); + for result in results { + let (input_index, i): (VarInt, usize) = + deserialize_partial(&result).map_err(|_| BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: result.clone(), + })?; + + signatures.push(( + input_index.0 as usize, + PartialSignature::from_slice(&result[i..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: result.clone(), + } + })?, + )); + } + + Ok(signatures) + } + + /// Sign a message with the key derived with the given derivation path. + /// Result is the header byte (31-34: P2PKH compressed) and the ecdsa signature. + pub fn sign_message( + &self, + message: &[u8], + path: &DerivationPath, + ) -> Result<(u8, ecdsa::Signature), BitcoinClientError> { + let chunks: Vec<&[u8]> = message.chunks(64).collect(); + let mut intpr = ClientCommandInterpreter::new(); + let message_commitment_root = intpr.add_known_list(&chunks); + let cmd = command::sign_message(message.len(), &message_commitment_root, path); + self.make_request(&cmd, Some(&mut intpr)).and_then(|data| { + Ok(( + data[0], + ecdsa::Signature::from_compact(&data[1..]).map_err(|_| { + BitcoinClientError::UnexpectedResult { + command: cmd.ins, + data: data.to_vec(), + } + })?, + )) + }) + } +} + +/// Communication layer between the bitcoin client and the Ledger device. +pub trait Transport { + type Error: Debug; + fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error>; +} diff --git a/bitcoin_client_rs/src/command.rs b/bitcoin_client_rs/src/command.rs new file mode 100644 index 000000000..6d54c7940 --- /dev/null +++ b/bitcoin_client_rs/src/command.rs @@ -0,0 +1,148 @@ +/// APDU commands for the Bitcoin application. +/// +use bitcoin::{ + bip32::{ChildNumber, DerivationPath}, + consensus::encode::{self, VarInt}, +}; +use core::default::Default; + +use super::{ + apdu::{self, APDUCommand}, + wallet::WalletPolicy, +}; + +/// Creates the APDU Command to retrieve the app's name, version and state flags. +pub fn get_version() -> APDUCommand { + APDUCommand { + ins: apdu::BitcoinCommandCode::GetVersion as u8, + p2: 0x00, + ..Default::default() + } +} + +/// Creates the APDU Command to retrieve the master fingerprint. +pub fn get_master_fingerprint() -> APDUCommand { + APDUCommand { + cla: apdu::Cla::Bitcoin as u8, + ins: apdu::BitcoinCommandCode::GetMasterFingerprint as u8, + ..Default::default() + } +} + +/// Creates the APDU command required to get the extended pubkey with the given derivation path. +pub fn get_extended_pubkey(path: &DerivationPath, display: bool) -> APDUCommand { + let child_numbers: &[ChildNumber] = path.as_ref(); + let data: Vec = child_numbers.iter().fold( + vec![ + if display { 1_u8 } else { b'\0' }, + child_numbers.len() as u8, + ], + |mut acc, &x| { + acc.extend_from_slice(&u32::from(x).to_be_bytes()); + acc + }, + ); + + APDUCommand { + cla: apdu::Cla::Bitcoin as u8, + ins: apdu::BitcoinCommandCode::GetExtendedPubkey as u8, + data, + ..Default::default() + } +} + +/// Creates the APDU command required to register the given wallet policy. +pub fn register_wallet(policy: &WalletPolicy) -> APDUCommand { + let bytes = policy.serialize(); + let mut data = encode::serialize(&VarInt(bytes.len() as u64)); + data.extend(bytes); + APDUCommand { + cla: apdu::Cla::Bitcoin as u8, + ins: apdu::BitcoinCommandCode::RegisterWallet as u8, + data, + ..Default::default() + } +} + +/// Creates the APDU command required to retrieve an address for the given wallet. +pub fn get_wallet_address( + policy: &WalletPolicy, + hmac: Option<&[u8; 32]>, + change: bool, + address_index: u32, + display: bool, +) -> APDUCommand { + let mut data: Vec = Vec::with_capacity(70); + data.push(if display { 1_u8 } else { b'\0' }); + data.extend_from_slice(&policy.id()); + data.extend_from_slice(hmac.unwrap_or(&[b'\0'; 32])); + data.push(if change { 1_u8 } else { b'\0' }); + data.extend_from_slice(&address_index.to_be_bytes()); + APDUCommand { + cla: apdu::Cla::Bitcoin as u8, + ins: apdu::BitcoinCommandCode::GetWalletAddress as u8, + data, + ..Default::default() + } +} + +/// Creates the APDU command required to sign a psbt. +pub fn sign_psbt( + global_mapping_commitment: &[u8], + inputs_number: usize, + input_commitments_root: &[u8; 32], + outputs_number: usize, + output_commitments_root: &[u8; 32], + policy: &WalletPolicy, + hmac: Option<&[u8; 32]>, +) -> APDUCommand { + let mut data: Vec = Vec::new(); + data.extend_from_slice(global_mapping_commitment); + data.extend(encode::serialize(&VarInt(inputs_number as u64))); + data.extend_from_slice(input_commitments_root); + data.extend(encode::serialize(&VarInt(outputs_number as u64))); + data.extend_from_slice(output_commitments_root); + data.extend_from_slice(&policy.id()); + data.extend_from_slice(hmac.unwrap_or(&[b'\0'; 32])); + APDUCommand { + cla: apdu::Cla::Bitcoin as u8, + ins: apdu::BitcoinCommandCode::SignPSBT as u8, + data, + ..Default::default() + } +} + +/// Creates the APDU Command to sign a message. +pub fn sign_message( + message_length: usize, + message_commitment_root: &[u8; 32], + path: &DerivationPath, +) -> APDUCommand { + let child_numbers: &[ChildNumber] = path.as_ref(); + let mut data: Vec = + child_numbers + .iter() + .fold(vec![child_numbers.len() as u8], |mut acc, &x| { + acc.extend_from_slice(&u32::from(x).to_be_bytes()); + acc + }); + data.extend(encode::serialize(&VarInt(message_length as u64))); + data.extend_from_slice(message_commitment_root); + + APDUCommand { + cla: apdu::Cla::Bitcoin as u8, + ins: apdu::BitcoinCommandCode::SignMessage as u8, + data, + ..Default::default() + } +} + +/// Creates the APDU command to CONTINUE. +pub fn continue_interrupted(data: Vec) -> APDUCommand { + APDUCommand { + cla: apdu::Cla::Framework as u8, + ins: apdu::FrameworkCommandCode::ContinueInterrupted as u8, + data, + ..Default::default() + } +} diff --git a/bitcoin_client_rs/src/error.rs b/bitcoin_client_rs/src/error.rs new file mode 100644 index 000000000..fce642c62 --- /dev/null +++ b/bitcoin_client_rs/src/error.rs @@ -0,0 +1,21 @@ +use core::fmt::Debug; + +use crate::{apdu::StatusWord, interpreter::InterpreterError}; + +#[derive(Debug)] +pub enum BitcoinClientError { + ClientError(String), + InvalidPsbt, + Transport(T), + Interpreter(InterpreterError), + Device { command: u8, status: StatusWord }, + UnexpectedResult { command: u8, data: Vec }, + InvalidResponse(String), + UnsupportedAppVersion, +} + +impl From for BitcoinClientError { + fn from(e: InterpreterError) -> BitcoinClientError { + BitcoinClientError::Interpreter(e) + } +} diff --git a/bitcoin_client_rs/src/interpreter.rs b/bitcoin_client_rs/src/interpreter.rs new file mode 100644 index 000000000..3bf01456a --- /dev/null +++ b/bitcoin_client_rs/src/interpreter.rs @@ -0,0 +1,315 @@ +use core::convert::TryFrom; +use core::fmt::Debug; + +use bitcoin::{ + consensus::encode::{self, VarInt}, + hashes::{sha256, Hash, HashEngine}, +}; + +use crate::{apdu::ClientCommandCode, merkle::MerkleTree}; + +/// Interpreter for the client-side commands. +/// This struct keeps has methods to keep track of: +/// - known preimages +/// - known Merkle trees from lists of elements +/// Moreover, it containes the state that is relevant for the interpreted client side commands: +/// - a queue of bytes that contains any bytes that could not fit in a response from the +/// GET_PREIMAGE client command (when a preimage is too long to fit in a single message) or the +/// GET_MERKLE_LEAF_PROOF command (which returns a Merkle proof, which might be too long to fit +/// in a single message). The data in the queue is returned in one (or more) successive +/// GET_MORE_ELEMENTS commands from the hardware wallet. +/// Finally, it keeps track of the yielded values (that is, the values sent from the hardware +/// wallet with a YIELD client command). +pub struct ClientCommandInterpreter { + yielded: Vec>, + queue: Vec>, + known_preimages: Vec<([u8; 32], Vec)>, + trees: Vec, +} + +impl ClientCommandInterpreter { + pub fn new() -> Self { + Self { + yielded: Vec::new(), + queue: Vec::new(), + known_preimages: Vec::new(), + trees: Vec::new(), + } + } + + /// Adds a preimage to the list of known preimages. + /// The client must respond with `element` when a GET_PREIMAGE command is sent with + /// `sha256(element)` in its request. + pub fn add_known_preimage(&mut self, element: Vec) { + let mut engine = sha256::Hash::engine(); + engine.input(&element); + let hash = sha256::Hash::from_engine(engine).to_byte_array(); + self.known_preimages.push((hash, element)); + } + + /// Adds a known Merkleized list. + /// Builds the Merkle tree of `elements`, and adds it to the Merkle trees known to the client + /// (mapped by Merkle root `mt_root`). + /// Moreover, adds all the leafs (after adding the b'\0' prefix) to the list of known preimages. + /// If `el` is one of `elements`, the client must respond with b'\0' + `el` when a GET_PREIMAGE + /// client command is sent with `sha256(b'\0' + el)`. + /// Moreover, the commands GET_MERKLE_LEAF_INDEX and GET_MERKLE_LEAF_PROOF must correctly answer + /// queries relative to the Merkle whose root is `mt_root`. + pub fn add_known_list(&mut self, elements: &[impl AsRef<[u8]>]) -> [u8; 32] { + let mut leaves = Vec::with_capacity(elements.len()); + for element in elements { + let mut preimage = vec![0x00]; + preimage.extend_from_slice(element.as_ref()); + let mut engine = sha256::Hash::engine(); + engine.input(&preimage); + let hash = sha256::Hash::from_engine(engine).to_byte_array(); + self.known_preimages.push((hash, preimage)); + leaves.push(hash); + } + let tree = MerkleTree::new(leaves); + let root_hash = *tree.root_hash(); + self.trees.push(tree); + root_hash + } + + /// Adds the Merkle trees of keys, and the Merkle tree of values (ordered by key) + /// of a mapping of bytes to bytes. + /// Adds the Merkle tree of the list of keys, and the Merkle tree of the list of corresponding + /// values, with the same semantics as the `add_known_list` applied separately to the two lists. + pub fn add_known_mapping(&mut self, mapping: &[(Vec, Vec)]) { + let mut sorted: Vec<&(Vec, Vec)> = mapping.iter().collect(); + sorted.sort_by(|(k1, _), (k2, _)| k1.as_slice().cmp(k2)); + + let mut keys = Vec::with_capacity(sorted.len()); + let mut values = Vec::with_capacity(sorted.len()); + for (key, value) in sorted { + keys.push(key.as_slice()); + values.push(value.as_slice()); + } + self.add_known_list(&keys); + self.add_known_list(&values); + } + + // Interprets the client command requested by the hardware wallet, returns the appropriate + // response to transmit back and updates interpreter internal states. + pub fn execute(&mut self, command: Vec) -> Result, InterpreterError> { + if command.is_empty() { + return Err(InterpreterError::EmptyInput); + } + match ClientCommandCode::try_from(command[0]) { + Ok(ClientCommandCode::Yield) => { + self.yielded.push(command[1..].to_vec()); + Ok(Vec::new()) + } + Ok(ClientCommandCode::GetPreimage) => { + get_preimage_command(&mut self.queue, &self.known_preimages, &command[1..]) + } + Ok(ClientCommandCode::GetMerkleLeafProof) => { + get_merkle_leaf_proof(&mut self.queue, &self.trees, &command[1..]) + } + Ok(ClientCommandCode::GetMerkleLeafIndex) => { + get_merkle_leaf_index(&self.trees, &command[1..]) + } + Ok(ClientCommandCode::GetMoreElements) => get_more_elements(&mut self.queue), + Err(()) => Err(InterpreterError::UnknownCommand(command[0])), + } + } + + /// Consumes the interpreter and returns the yielded results. + pub fn yielded(self) -> Vec> { + self.yielded + } +} + +fn get_preimage_command( + queue: &mut Vec>, + known_preimages: &[([u8; 32], Vec)], + request: &[u8], +) -> Result, InterpreterError> { + if request.len() != 33 || request[0] != b'\0' { + return Err(InterpreterError::UnsupportedRequest( + ClientCommandCode::GetPreimage as u8, + )); + }; + + let (_, preimage) = known_preimages + .iter() + .find(|(hash, _)| hash == &request[1..]) + .ok_or(InterpreterError::UnknownHash)?; + + let preimage_len_out = encode::serialize(&VarInt(preimage.len() as u64)); + + // We can send at most 255 - len(preimage_len_out) - 1 bytes in a single message; + //the rest will be stored for GET_MORE_ELEMENTS + let max_payload_size = 255 - preimage_len_out.len() - 1; + + let payload_size = if preimage.len() > max_payload_size { + max_payload_size + } else { + preimage.len() + }; + + if payload_size < preimage.len() { + for byte in &preimage[payload_size..] { + queue.push(vec![*byte]); + } + } + + let mut response = preimage_len_out; + response.extend_from_slice(&(payload_size as u8).to_be_bytes()); + response.extend_from_slice(&preimage[..payload_size]); + Ok(response) +} + +fn get_merkle_leaf_proof( + queue: &mut Vec>, + trees: &[MerkleTree], + request: &[u8], +) -> Result, InterpreterError> { + if !queue.is_empty() { + return Err(InterpreterError::UnexpectedQueue); + } else if request.len() < 34 { + return Err(InterpreterError::UnsupportedRequest( + ClientCommandCode::GetMerkleLeafProof as u8, + )); + }; + + let root = &request[0..32]; + let (tree_size, read): (VarInt, usize) = + encode::deserialize_partial(&request[32..]).map_err(|_| { + InterpreterError::UnsupportedRequest(ClientCommandCode::GetMerkleLeafProof as u8) + })?; + + // deserialize consumes the entire vector. + let leaf_index: VarInt = encode::deserialize(&request[32 + read..]).map_err(|_| { + InterpreterError::UnsupportedRequest(ClientCommandCode::GetMerkleLeafProof as u8) + })?; + + let tree = trees + .iter() + .find(|tree| tree.root_hash() == root) + .ok_or(InterpreterError::UnknownHash)?; + + if leaf_index >= tree_size || tree_size.0 != tree.size() as u64 { + return Err(InterpreterError::InvalidIndexOrSize); + } + + let proof = tree + .get_leaf_proof(leaf_index.0 as usize) + .ok_or(InterpreterError::InvalidIndexOrSize)?; + + let len_proof = proof.len(); + let mut first_part_proof = Vec::new(); + let mut n_response_elements = 0; + for (i, p) in proof.into_iter().enumerate() { + // how many elements we can fit in 255 - 32 - 1 - 1 = 221 bytes ? + // response: 6 array of 32 bytes. + if i < 6 { + first_part_proof.extend(p); + n_response_elements += 1; + } else { + // Add to the queue any proof elements that do not fit the response + queue.push(p); + } + } + + let mut response = tree.get_leaf(leaf_index.0 as usize).unwrap().to_vec(); + response.extend_from_slice(&(len_proof as u8).to_be_bytes()); + response.extend_from_slice(&(n_response_elements as u8).to_be_bytes()); + response.extend_from_slice(&first_part_proof); + Ok(response) +} + +fn get_merkle_leaf_index( + trees: &[MerkleTree], + request: &[u8], +) -> Result, InterpreterError> { + if request.len() < 64 { + return Err(InterpreterError::UnsupportedRequest( + ClientCommandCode::GetMerkleLeafIndex as u8, + )); + } + let root = &request[0..32]; + let hash = &request[32..64]; + + let tree = trees + .iter() + .find(|tree| tree.root_hash() == root) + .ok_or(InterpreterError::UnknownHash)?; + + let leaf_index = tree + .get_leaf_index(hash) + .ok_or(InterpreterError::UnknownHash)?; + + let mut response = 1_u8.to_be_bytes().to_vec(); + response.extend(encode::serialize(&VarInt(leaf_index as u64))); + Ok(response) +} + +fn get_more_elements(queue: &mut Vec>) -> Result, InterpreterError> { + if queue.is_empty() { + return Err(InterpreterError::UnexpectedQueue); + } + + // The queue must contain only element of the same length. + let element_length = queue[0].len(); + if queue.iter().any(|e| e.len() != element_length) { + return Err(InterpreterError::UnexpectedQueue); + } + + let mut response_elements = Vec::new(); + let mut n_added_elements = 0; + for element in queue.iter() { + if response_elements.len() + element_length <= 253 { + response_elements.extend_from_slice(element); + n_added_elements += 1; + } + } + *queue = queue[n_added_elements..].to_vec(); + + let mut response = (n_added_elements as u8).to_be_bytes().to_vec(); + response.extend((element_length as u8).to_be_bytes()); + response.extend(response_elements); + Ok(response) +} + +/// Returns a serialized Merkleized map commitment, encoded as the concatenation of: +/// - the number of key/value pairs, as a Bitcoin-style varint; +/// - the root of the Merkle tree of the keys +/// - the root of the Merkle tree of the values. +pub fn get_merkleized_map_commitment(mapping: &[(Vec, Vec)]) -> Vec { + let mut sorted: Vec<&(Vec, Vec)> = mapping.iter().collect(); + sorted.sort_by(|(k1, _), (k2, _)| k1.as_slice().cmp(k2)); + + let mut keys_hashes: Vec<[u8; 32]> = Vec::with_capacity(sorted.len()); + let mut values_hashes: Vec<[u8; 32]> = Vec::with_capacity(sorted.len()); + for (key, value) in &sorted { + let mut preimage = vec![0x00]; + preimage.extend_from_slice(key); + let mut engine = sha256::Hash::engine(); + engine.input(&preimage); + keys_hashes.push(sha256::Hash::from_engine(engine).to_byte_array()); + + let mut preimage = vec![0x00]; + preimage.extend_from_slice(value); + let mut engine = sha256::Hash::engine(); + engine.input(&preimage); + values_hashes.push(sha256::Hash::from_engine(engine).to_byte_array()); + } + + let mut commitment = encode::serialize(&VarInt(sorted.len() as u64)); + commitment.extend(MerkleTree::new(keys_hashes).root_hash()); + commitment.extend(MerkleTree::new(values_hashes).root_hash()); + commitment +} + +#[derive(Debug)] +pub enum InterpreterError { + EmptyInput, + UnknownCommand(u8), + UnsupportedRequest(u8), + InvalidIndexOrSize, + UnknownHash, + UnknownMerkleRoot, + UnexpectedQueue, +} diff --git a/bitcoin_client_rs/src/lib.rs b/bitcoin_client_rs/src/lib.rs new file mode 100644 index 000000000..dc751c4cd --- /dev/null +++ b/bitcoin_client_rs/src/lib.rs @@ -0,0 +1,15 @@ +mod command; +mod interpreter; +mod merkle; + +pub mod apdu; +pub mod client; +pub mod error; +pub mod psbt; +pub mod wallet; + +#[cfg(feature = "async")] +pub mod async_client; + +pub use client::{BitcoinClient, Transport}; +pub use wallet::{WalletPolicy, WalletPubKey}; diff --git a/bitcoin_client_rs/src/merkle.rs b/bitcoin_client_rs/src/merkle.rs new file mode 100644 index 000000000..a82766d2b --- /dev/null +++ b/bitcoin_client_rs/src/merkle.rs @@ -0,0 +1,223 @@ +use bitcoin::hashes::{sha256, Hash, HashEngine}; + +///! This implementation of Merkle Trees makes usage of a +///! simple and opinionated data structure. +///! The Tree is only created once and does not require +///! multiple manipulation like new leaf insertion at a choosen index. +///! In fact the client uses merkle trees for read only commands: +///! - get_merkle_leaf_proof: provide the proof the hash of the leaf +///! with index i +///! - get_merkle_leaf_index: provide the index of the leaf with hash. + +/// MerkleTree is containing a merkle tree generated from a list of items. +pub struct MerkleTree { + root: Tree, + leaves: Vec<[u8; 32]>, +} + +impl MerkleTree { + pub fn new(leaves: Vec<[u8; 32]>) -> Self { + Self { + root: Tree::new(&leaves, 0, leaves.len()), + leaves, + } + } + + pub fn size(&self) -> usize { + self.leaves.len() + } + + /// Returns the root hash of the Merkle tree. + pub fn root_hash(&self) -> &[u8; 32] { + match &self.root { + Tree::Node { value, .. } => value, + Tree::Leaf(idx) => &self.leaves[*idx], + } + } + + /// Returns the leaf value at index i. + pub fn get_leaf(&self, i: usize) -> Option<&[u8; 32]> { + self.leaves.get(i) + } + + /// Get position of the leaf in the tree. + pub fn get_leaf_index(&self, val: &[u8]) -> Option { + self.leaves.iter().position(|v| v == val) + } + + // Get Merkle proof of a leaf with the given index. + pub fn get_leaf_proof(&self, index: usize) -> Option>> { + if index >= self.leaves.len() { + // Out of bound + None + } else { + Some(self.root.get_proof(&self.leaves, index)) + } + } +} + +/// Tree is either a Node with children trees or a Leaf with only a given value. +enum Tree { + Node { + value: [u8; 32], + left: Box, + right: Box, + height: usize, + }, + // index of the leaf in the leaves array + Leaf(usize), +} + +impl Tree { + fn new(leaves: &[[u8; 32]], start: usize, size: usize) -> Self { + if size == 1 { + return Tree::Leaf(start); + } + + let lchild_size = largest_power_of_2_less_than(size); + let lchild = Tree::new(leaves, start, lchild_size); + let rchild = Tree::new(leaves, start + lchild_size, size - lchild_size); + + let mut input = vec![0x01]; + input.extend_from_slice(lchild.value(leaves)); + input.extend_from_slice(rchild.value(leaves)); + + let mut engine = sha256::Hash::engine(); + engine.input(input.as_slice()); + let value = sha256::Hash::from_engine(engine).to_byte_array(); + Tree::Node { + height: lchild.height() + 1, + left: Box::new(lchild), + right: Box::new(rchild), + value, + } + } + + fn value<'a>(&'a self, leaves: &'a [[u8; 32]]) -> &'a [u8; 32] { + match self { + Self::Node { value, .. } => value, + Self::Leaf(idx) => &leaves[*idx], + } + } + + fn height(&self) -> usize { + match self { + Self::Node { height, .. } => *height, + Self::Leaf(_) => 0, + } + } + + /// get the merkle proof of a leaf with the given index in the leaves array. + fn get_proof(&self, leaves: &[[u8; 32]], index: usize) -> Vec> { + match self { + Self::Leaf(_) => Vec::new(), + Self::Node { left, right, .. } => { + let (mut proof, sibling) = if index < pow2(left.height()) { + (left.get_proof(leaves, index), right) + } else { + (right.get_proof(leaves, index - pow2(left.height())), left) + }; + match **sibling { + Self::Node { value, .. } => proof.push(value.to_vec()), + Self::Leaf(idx) => proof.push(leaves[idx].to_vec()), + } + proof + } + } + } +} + +/// Return floor(log_2(n)) for a positive integer `n`. +fn floor_lg(n: usize) -> usize { + assert!(n > 0); + + let mut r = 0; + let mut t = 1; + while 2 * t <= n { + t *= 2; + r += 1; + } + r +} + +fn pow2(n: usize) -> usize { + let mut p = 1; + for _i in 0..n { + p *= 2 + } + p +} + +/// For a positive integer `n`, returns `True` is `n` is a perfect power of 2, `False` otherwise. +fn is_power_of_2(n: usize) -> bool { + assert!(n >= 1); + n & (n - 1) == 0 +} + +/// For an integer `n` which is at least 2, returns the largest exact power of 2 that is strictly less than `n`. +fn largest_power_of_2_less_than(n: usize) -> usize { + if n == 2 { + return 1; + } + assert!(n > 1); + if is_power_of_2(n) { + n / 2 + } else { + 1 << floor_lg(n) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitcoin::hashes::{sha256, Hash, HashEngine}; + + #[test] + fn test_merkle_tree() { + let leaves = [ + [ + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, + 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, + 0x78, 0x52, 0xb8, 0x55, + ], + [ + 0xd7, 0xa8, 0xfb, 0xb3, 0x07, 0xd7, 0x80, 0x94, 0x69, 0xca, 0x9a, 0xbc, 0xb0, 0x08, + 0x2e, 0x4f, 0x8d, 0x56, 0x51, 0xe4, 0x6d, 0x3c, 0xdb, 0x76, 0x2d, 0x02, 0xd0, 0xbf, + 0x37, 0xc9, 0xe5, 0x92, + ], + [ + 0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, + 0x3d, 0x97, 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2, 0xb7, 0x65, 0x44, 0x8c, + 0x86, 0x35, 0xfb, 0x6c, + ], + [ + 0x00, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7, 0x82, 0x52, 0x65, 0x29, 0xa9, 0xb6, + 0x3d, 0x00, 0xaa, 0x63, 0x15, 0x64, 0xd5, 0xd7, 0x89, 0xc2, 0xb7, 0x65, 0x44, 0x8c, + 0x86, 0x35, 0xfb, 0x6c, + ], + ]; + + let tree = MerkleTree::new(leaves[0..3].to_vec()); + + assert_eq!( + tree.get_leaf_proof(0), + Some(vec![leaves[1].to_vec(), leaves[2].to_vec()]) + ); + + assert_eq!( + tree.get_leaf_proof(1), + Some(vec![leaves[0].to_vec(), leaves[2].to_vec()]) + ); + + let mut input = vec![0x01]; + input.extend_from_slice(&leaves[0]); + input.extend_from_slice(&leaves[1]); + + let mut engine = sha256::Hash::engine(); + engine.input(input.as_slice()); + let value = sha256::Hash::from_engine(engine).to_byte_array(); + assert_eq!(tree.get_leaf_proof(2), Some(vec![value.to_vec()])); + + let _tree = MerkleTree::new(leaves.to_vec()); + } +} diff --git a/bitcoin_client_rs/src/psbt.rs b/bitcoin_client_rs/src/psbt.rs new file mode 100644 index 000000000..8708a64e9 --- /dev/null +++ b/bitcoin_client_rs/src/psbt.rs @@ -0,0 +1,863 @@ +/// code is from github.com/rust-bitcoin/rust-bitcoin +/// SPDX-License-Identifier: CC0-1.0 +/// +/// Note: Only psbt V2 is supported by the ledger bitcoin app. +/// rust-bitcoin currently support V0. +use bitcoin::{ + blockdata::transaction::{TxIn, TxOut}, + consensus::encode::{deserialize, serialize, VarInt}, + ecdsa, + hashes::Hash, + key::Error as KeyError, + psbt::{raw, Input, Output, Psbt}, + secp256k1::{self, XOnlyPublicKey}, + taproot, + taproot::TapLeafHash, + PublicKey, +}; + +use serialize::Serialize; + +#[rustfmt::skip] +macro_rules! impl_psbt_get_pair { + ($rv:ident.push($slf:ident.$unkeyed_name:ident, $unkeyed_typeval:ident)) => { + if let Some(ref $unkeyed_name) = $slf.$unkeyed_name { + $rv.push(bitcoin::psbt::raw::Pair { + key: bitcoin::psbt::raw::Key { + type_value: $unkeyed_typeval, + key: vec![], + }, + value: Serialize::serialize($unkeyed_name), + }); + } + }; + ($rv:ident.push_map($slf:ident.$keyed_name:ident, $keyed_typeval:ident)) => { + for (key, val) in &$slf.$keyed_name { + $rv.push(bitcoin::psbt::raw::Pair { + key: bitcoin::psbt::raw::Key { + type_value: $keyed_typeval, + key: Serialize::serialize(key), + }, + value: Serialize::serialize(val), + }); + } + }; +} + +/// V0, Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00 +/// const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00; +/// Type: Extended Public Key PSBT_GLOBAL_XPUB = 0x01 +const PSBT_GLOBAL_XPUB: u8 = 0x01; +/// V2 field +const PSBT_GLOBAL_TX_VERSION: u8 = 0x02; +/// V2 field +const PSBT_GLOBAL_FALLBACK_LOCKTIME: u8 = 0x03; +/// V2 field +const PSBT_GLOBAL_INPUT_COUNT: u8 = 0x04; +/// V2 field +const PSBT_GLOBAL_OUTPUT_COUNT: u8 = 0x05; +/// V2 field +/// const PSBT_GLOBAL_TX_MODIFIABLE: u8 = 0x06; +/// Type: Version Number PSBT_GLOBAL_VERSION = 0xFB +const PSBT_GLOBAL_VERSION: u8 = 0xFB; + +pub fn get_v2_global_pairs(psbt: &Psbt) -> Vec { + let mut rv: Vec = Default::default(); + + for (xpub, (fingerprint, derivation)) in &psbt.xpub { + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_XPUB, + key: xpub.encode().to_vec(), + }, + value: { + let mut ret = Vec::with_capacity(4 + derivation.len() * 4); + ret.extend(fingerprint.as_bytes()); + derivation + .into_iter() + .for_each(|n| ret.extend(u32::from(*n).to_le_bytes())); + ret + }, + }); + } + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_FALLBACK_LOCKTIME, + key: vec![], + }, + value: serialize(&psbt.unsigned_tx.lock_time), + }); + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_INPUT_COUNT, + key: vec![], + }, + value: serialize(&VarInt(psbt.inputs.len() as u64)), + }); + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_OUTPUT_COUNT, + key: vec![], + }, + value: serialize(&VarInt(psbt.outputs.len() as u64)), + }); + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_TX_VERSION, + key: vec![], + }, + value: psbt.unsigned_tx.version.0.to_le_bytes().to_vec(), + }); + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_VERSION, + key: vec![], + }, + value: 2_u32.to_le_bytes().to_vec(), + }); + + for (key, value) in psbt.proprietary.iter() { + rv.push(raw::Pair { + key: key.to_key(), + value: value.clone(), + }); + } + + for (key, value) in psbt.unknown.iter() { + rv.push(raw::Pair { + key: key.clone(), + value: value.clone(), + }); + } + + rv +} + +/// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00 +const PSBT_IN_NON_WITNESS_UTXO: u8 = 0x00; +/// Type: Witness UTXO PSBT_IN_WITNESS_UTXO = 0x01 +const PSBT_IN_WITNESS_UTXO: u8 = 0x01; +/// Type: Partial Signature PSBT_IN_PARTIAL_SIG = 0x02 +const PSBT_IN_PARTIAL_SIG: u8 = 0x02; +/// Type: Sighash Type PSBT_IN_SIGHASH_TYPE = 0x03 +const PSBT_IN_SIGHASH_TYPE: u8 = 0x03; +/// Type: Redeem Script PSBT_IN_REDEEM_SCRIPT = 0x04 +const PSBT_IN_REDEEM_SCRIPT: u8 = 0x04; +/// Type: Witness Script PSBT_IN_WITNESS_SCRIPT = 0x05 +const PSBT_IN_WITNESS_SCRIPT: u8 = 0x05; +/// Type: BIP 32 Derivation Path PSBT_IN_BIP32_DERIVATION = 0x06 +const PSBT_IN_BIP32_DERIVATION: u8 = 0x06; +/// Type: Finalized scriptSig PSBT_IN_FINAL_SCRIPTSIG = 0x07 +const PSBT_IN_FINAL_SCRIPTSIG: u8 = 0x07; +/// Type: Finalized scriptWitness PSBT_IN_FINAL_SCRIPTWITNESS = 0x08 +const PSBT_IN_FINAL_SCRIPTWITNESS: u8 = 0x08; +/// V2 +const PSBT_IN_PREVIOUS_TXID: u8 = 0x0e; +/// V2 +const PSBT_IN_SEQUENCE: u8 = 0x10; +/// V2 +/// const PSBT_IN_REQUIRED_TIME_LOCKTIME: u8 = 0x11; +/// V2 +///const PSBT_IN_REQUIRED_HEIGHT_LOCKTIME: u8 = 0x12; +const PSBT_IN_OUTPUT_INDEX: u8 = 0x0f; +/// Type: RIPEMD160 preimage PSBT_IN_RIPEMD160 = 0x0a +const PSBT_IN_RIPEMD160: u8 = 0x0a; +/// Type: SHA256 preimage PSBT_IN_SHA256 = 0x0b +const PSBT_IN_SHA256: u8 = 0x0b; +/// Type: HASH160 preimage PSBT_IN_HASH160 = 0x0c +const PSBT_IN_HASH160: u8 = 0x0c; +/// Type: HASH256 preimage PSBT_IN_HASH256 = 0x0d +const PSBT_IN_HASH256: u8 = 0x0d; +/// Type: Schnorr Signature in Key Spend PSBT_IN_TAP_KEY_SIG = 0x13 +const PSBT_IN_TAP_KEY_SIG: u8 = 0x13; +/// Type: Schnorr Signature in Script Spend PSBT_IN_TAP_SCRIPT_SIG = 0x14 +const PSBT_IN_TAP_SCRIPT_SIG: u8 = 0x14; +/// Type: Taproot Leaf Script PSBT_IN_TAP_LEAF_SCRIPT = 0x14 +const PSBT_IN_TAP_LEAF_SCRIPT: u8 = 0x15; +/// Type: Taproot Key BIP 32 Derivation Path PSBT_IN_TAP_BIP32_DERIVATION = 0x16 +const PSBT_IN_TAP_BIP32_DERIVATION: u8 = 0x16; +/// Type: Taproot Internal Key PSBT_IN_TAP_INTERNAL_KEY = 0x17 +const PSBT_IN_TAP_INTERNAL_KEY: u8 = 0x17; +/// Type: Taproot Merkle Root PSBT_IN_TAP_MERKLE_ROOT = 0x18 +const PSBT_IN_TAP_MERKLE_ROOT: u8 = 0x18; + +pub fn get_v2_input_pairs(input: &Input, txin: &TxIn) -> Vec { + let mut rv: Vec = Default::default(); + + impl_psbt_get_pair! { + rv.push(input.non_witness_utxo, PSBT_IN_NON_WITNESS_UTXO) + } + + impl_psbt_get_pair! { + rv.push(input.witness_utxo, PSBT_IN_WITNESS_UTXO) + } + + impl_psbt_get_pair! { + rv.push_map(input.partial_sigs, PSBT_IN_PARTIAL_SIG) + } + + impl_psbt_get_pair! { + rv.push(input.sighash_type, PSBT_IN_SIGHASH_TYPE) + } + + impl_psbt_get_pair! { + rv.push(input.redeem_script, PSBT_IN_REDEEM_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push(input.witness_script, PSBT_IN_WITNESS_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push_map(input.bip32_derivation, PSBT_IN_BIP32_DERIVATION) + } + + impl_psbt_get_pair! { + rv.push(input.final_script_sig, PSBT_IN_FINAL_SCRIPTSIG) + } + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_IN_PREVIOUS_TXID, + key: vec![], + }, + value: serialize(&txin.previous_output.txid), + }); + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_IN_OUTPUT_INDEX, + key: vec![], + }, + value: serialize(&txin.previous_output.vout), + }); + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_IN_SEQUENCE, + key: vec![], + }, + value: serialize(&txin.sequence), + }); + + impl_psbt_get_pair! { + rv.push(input.final_script_witness, PSBT_IN_FINAL_SCRIPTWITNESS) + } + + impl_psbt_get_pair! { + rv.push_map(input.ripemd160_preimages, PSBT_IN_RIPEMD160) + } + + impl_psbt_get_pair! { + rv.push_map(input.sha256_preimages, PSBT_IN_SHA256) + } + + impl_psbt_get_pair! { + rv.push_map(input.hash160_preimages, PSBT_IN_HASH160) + } + + impl_psbt_get_pair! { + rv.push_map(input.hash256_preimages, PSBT_IN_HASH256) + } + + impl_psbt_get_pair! { + rv.push(input.tap_key_sig, PSBT_IN_TAP_KEY_SIG) + } + + impl_psbt_get_pair! { + rv.push_map(input.tap_script_sigs, PSBT_IN_TAP_SCRIPT_SIG) + } + + impl_psbt_get_pair! { + rv.push_map(input.tap_scripts, PSBT_IN_TAP_LEAF_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push_map(input.tap_key_origins, PSBT_IN_TAP_BIP32_DERIVATION) + } + + impl_psbt_get_pair! { + rv.push(input.tap_internal_key, PSBT_IN_TAP_INTERNAL_KEY) + } + + impl_psbt_get_pair! { + rv.push(input.tap_merkle_root, PSBT_IN_TAP_MERKLE_ROOT) + } + + for (key, value) in input.proprietary.iter() { + rv.push(raw::Pair { + key: key.to_key(), + value: value.clone(), + }); + } + + for (key, value) in input.unknown.iter() { + rv.push(raw::Pair { + key: key.clone(), + value: value.clone(), + }); + } + + rv +} + +/// Type: Redeem Script PSBT_OUT_REDEEM_SCRIPT = 0x00 +const PSBT_OUT_REDEEM_SCRIPT: u8 = 0x00; +/// Type: Witness Script PSBT_OUT_WITNESS_SCRIPT = 0x01 +const PSBT_OUT_WITNESS_SCRIPT: u8 = 0x01; +/// Type: BIP 32 Derivation Path PSBT_OUT_BIP32_DERIVATION = 0x02 +const PSBT_OUT_BIP32_DERIVATION: u8 = 0x02; +/// V2 +const PSBT_OUT_AMOUNT: u8 = 0x03; +/// V2 +const PSBT_OUT_SCRIPT: u8 = 0x04; +/// Type: Taproot Internal Key PSBT_OUT_TAP_INTERNAL_KEY = 0x05 +const PSBT_OUT_TAP_INTERNAL_KEY: u8 = 0x05; +/// Type: Taproot Tree PSBT_OUT_TAP_TREE = 0x06 +const PSBT_OUT_TAP_TREE: u8 = 0x06; +/// Type: Taproot Key BIP 32 Derivation Path PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 +const PSBT_OUT_TAP_BIP32_DERIVATION: u8 = 0x07; + +pub fn get_v2_output_pairs(output: &Output, txout: &TxOut) -> Vec { + let mut rv: Vec = Default::default(); + + impl_psbt_get_pair! { + rv.push(output.redeem_script, PSBT_OUT_REDEEM_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push(output.witness_script, PSBT_OUT_WITNESS_SCRIPT) + } + + impl_psbt_get_pair! { + rv.push_map(output.bip32_derivation, PSBT_OUT_BIP32_DERIVATION) + } + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_OUT_AMOUNT, + key: vec![], + }, + value: txout.value.to_sat().to_le_bytes().to_vec(), + }); + + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_OUT_SCRIPT, + key: vec![], + }, + value: txout.script_pubkey.as_bytes().to_vec(), + }); + + impl_psbt_get_pair! { + rv.push(output.tap_internal_key, PSBT_OUT_TAP_INTERNAL_KEY) + } + + impl_psbt_get_pair! { + rv.push(output.tap_tree, PSBT_OUT_TAP_TREE) + } + + impl_psbt_get_pair! { + rv.push_map(output.tap_key_origins, PSBT_OUT_TAP_BIP32_DERIVATION) + } + + for (key, value) in output.proprietary.iter() { + rv.push(raw::Pair { + key: key.to_key(), + value: value.clone(), + }); + } + + for (key, value) in output.unknown.iter() { + rv.push(raw::Pair { + key: key.clone(), + value: value.clone(), + }); + } + + rv +} + +pub fn deserialize_pair(pair: raw::Pair) -> (Vec, Vec) { + ( + deserialize(&Serialize::serialize(&pair.key)).unwrap(), + pair.value, + ) +} + +pub enum PartialSignature { + /// signature stored in pbst.partial_sigs + Sig(PublicKey, ecdsa::Signature), + /// signature stored in pbst.tap_script_sigs + TapScriptSig(XOnlyPublicKey, Option, taproot::Signature), +} + +impl PartialSignature { + pub fn from_slice(slice: &[u8]) -> Result { + let key_augment_byte = slice + .first() + .ok_or(PartialSignatureError::BadKeyAugmentLength)?; + let key_augment_len = u8::from_le_bytes([*key_augment_byte]) as usize; + + if key_augment_len >= slice.len() { + Err(PartialSignatureError::BadKeyAugmentLength) + } else if key_augment_len == 64 { + let key = XOnlyPublicKey::from_slice(&slice[1..33]) + .map_err(PartialSignatureError::XOnlyPubKey)?; + let tap_leaf_hash = + TapLeafHash::from_slice(&slice[33..65]).map_err(PartialSignatureError::TapLeaf)?; + let sig = taproot::Signature::from_slice(&slice[key_augment_len + 1..]) + .map_err(PartialSignatureError::TaprootSig)?; + Ok(Self::TapScriptSig(key, Some(tap_leaf_hash), sig)) + } else if key_augment_len == 32 { + let key = XOnlyPublicKey::from_slice(&slice[1..33]) + .map_err(PartialSignatureError::XOnlyPubKey)?; + let sig = taproot::Signature::from_slice(&slice[key_augment_len + 1..]) + .map_err(PartialSignatureError::TaprootSig)?; + Ok(Self::TapScriptSig(key, None, sig)) + } else { + let key = PublicKey::from_slice(&slice[1..key_augment_len + 1]) + .map_err(PartialSignatureError::PubKey)?; + let sig = ecdsa::Signature::from_slice(&slice[key_augment_len + 1..]) + .map_err(PartialSignatureError::EcdsaSig)?; + Ok(Self::Sig(key, sig)) + } + } +} + +pub enum PartialSignatureError { + BadKeyAugmentLength, + XOnlyPubKey(secp256k1::Error), + PubKey(KeyError), + EcdsaSig(ecdsa::Error), + TaprootSig(taproot::SigFromSliceError), + TapLeaf(bitcoin::hashes::FromSliceError), +} + +mod serialize { + use core::convert::{TryFrom, TryInto}; + + use bitcoin::{ + bip32::{ChildNumber, Fingerprint, KeySource}, + blockdata::{ + script::ScriptBuf, + transaction::{Transaction, TxOut}, + witness::Witness, + }, + consensus::encode::{self, deserialize_partial, serialize, Decodable, Encodable}, + ecdsa, + hashes::{hash160, ripemd160, sha256, sha256d, Hash}, + key::PublicKey, + psbt::{Error, PsbtSighashType}, + secp256k1::{self, XOnlyPublicKey}, + taproot, + taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash, TapTree, TaprootBuilder}, + VarInt, + }; + + macro_rules! impl_psbt_de_serialize { + ($thing:ty) => { + impl_psbt_serialize!($thing); + impl_psbt_deserialize!($thing); + }; + } + + macro_rules! impl_psbt_deserialize { + ($thing:ty) => { + impl Deserialize for $thing { + fn deserialize(bytes: &[u8]) -> Result { + bitcoin::consensus::deserialize(&bytes[..]) + .map_err(|e| bitcoin::psbt::Error::from(e)) + } + } + }; + } + + macro_rules! impl_psbt_serialize { + ($thing:ty) => { + impl Serialize for $thing { + fn serialize(&self) -> Vec { + bitcoin::consensus::serialize(self) + } + } + }; + } + + // macros for serde of hashes + macro_rules! impl_psbt_hash_de_serialize { + ($hash_type:ty) => { + impl_psbt_hash_serialize!($hash_type); + impl_psbt_hash_deserialize!($hash_type); + }; + } + + macro_rules! impl_psbt_hash_deserialize { + ($hash_type:ty) => { + impl $crate::psbt::serialize::Deserialize for $hash_type { + fn deserialize(bytes: &[u8]) -> Result { + <$hash_type>::from_slice(&bytes[..]).map_err(|e| bitcoin::psbt::Error::from(e)) + } + } + }; + } + + macro_rules! impl_psbt_hash_serialize { + ($hash_type:ty) => { + impl $crate::psbt::serialize::Serialize for $hash_type { + fn serialize(&self) -> Vec { + self.as_byte_array().to_vec() + } + } + }; + } + + /// A trait for serializing a value as raw data for insertion into PSBT + /// key-value maps. + pub(crate) trait Serialize { + /// Serialize a value as raw data. + fn serialize(&self) -> Vec; + } + + /// A trait for deserializing a value from raw data in PSBT key-value maps. + pub(crate) trait Deserialize: Sized { + /// Deserialize a value from raw data. + fn deserialize(bytes: &[u8]) -> Result; + } + + impl_psbt_de_serialize!(Transaction); + impl_psbt_de_serialize!(TxOut); + impl_psbt_de_serialize!(Witness); + impl_psbt_hash_de_serialize!(ripemd160::Hash); + impl_psbt_hash_de_serialize!(sha256::Hash); + impl_psbt_hash_de_serialize!(TapLeafHash); + impl_psbt_hash_de_serialize!(TapNodeHash); + impl_psbt_hash_de_serialize!(hash160::Hash); + impl_psbt_hash_de_serialize!(sha256d::Hash); + + // taproot + impl_psbt_de_serialize!(Vec); + + impl Serialize for bitcoin::psbt::raw::Key { + fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + VarInt((self.key.len() + 1) as u64) + .consensus_encode(&mut buf) + .expect("in-memory writers don't error"); + + self.type_value + .consensus_encode(&mut buf) + .expect("in-memory writers don't error"); + + for key in &self.key { + key.consensus_encode(&mut buf) + .expect("in-memory writers don't error"); + } + + buf + } + } + + impl Serialize for ScriptBuf { + fn serialize(&self) -> Vec { + self.to_bytes() + } + } + + impl Deserialize for ScriptBuf { + fn deserialize(bytes: &[u8]) -> Result { + Ok(Self::from(bytes.to_vec())) + } + } + + impl Serialize for PublicKey { + fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.write_into(&mut buf).expect("vecs don't error"); + buf + } + } + + impl Deserialize for PublicKey { + fn deserialize(bytes: &[u8]) -> Result { + PublicKey::from_slice(bytes).map_err(Error::InvalidPublicKey) + } + } + + impl Serialize for secp256k1::PublicKey { + fn serialize(&self) -> Vec { + self.serialize().to_vec() + } + } + + impl Deserialize for secp256k1::PublicKey { + fn deserialize(bytes: &[u8]) -> Result { + secp256k1::PublicKey::from_slice(bytes).map_err(Error::InvalidSecp256k1PublicKey) + } + } + + impl Serialize for ecdsa::Signature { + fn serialize(&self) -> Vec { + self.to_vec() + } + } + + impl Deserialize for ecdsa::Signature { + fn deserialize(bytes: &[u8]) -> Result { + // NB: Since BIP-174 says "the signature as would be pushed to the stack from + // a scriptSig or witness" we should ideally use a consensus deserialization and do + // not error on a non-standard values. However, + // + // 1) the current implementation of from_u32_consensus(`flag`) does not preserve + // the sighash byte `flag` mapping all unknown values to EcdsaSighashType::All or + // EcdsaSighashType::AllPlusAnyOneCanPay. Therefore, break the invariant + // EcdsaSig::from_slice(&sl[..]).to_vec = sl. + // + // 2) This would cause to have invalid signatures because the sighash message + // also has a field sighash_u32 (See BIP141). For example, when signing with non-standard + // 0x05, the sighash message would have the last field as 0x05u32 while, the verification + // would use check the signature assuming sighash_u32 as `0x01`. + ecdsa::Signature::from_slice(bytes).map_err(|e| match e { + ecdsa::Error::EmptySignature => Error::InvalidEcdsaSignature(e), + ecdsa::Error::SighashType(flag) => Error::NonStandardSighashType(flag.0), + ecdsa::Error::Secp256k1(..) => Error::InvalidEcdsaSignature(e), + ecdsa::Error::Hex(..) => { + unreachable!("Decoding from slice, not hex") + } + _ => Error::InvalidEcdsaSignature(e), + }) + } + } + + impl Serialize for KeySource { + fn serialize(&self) -> Vec { + let mut rv: Vec = Vec::with_capacity(key_source_len(self)); + + rv.append(&mut self.0.to_bytes().to_vec()); + + for cnum in self.1.into_iter() { + rv.append(&mut serialize(&u32::from(*cnum))) + } + + rv + } + } + + impl Deserialize for KeySource { + fn deserialize(bytes: &[u8]) -> Result { + if bytes.len() < 4 { + return Err(Error::ConsensusEncoding( + bitcoin::consensus::encode::Error::ParseFailed( + "Not enough bytes for key source", + ), + )); + } + + let fprint: Fingerprint = bytes[0..4].try_into().expect("4 is the fingerprint length"); + let mut dpath: Vec = Default::default(); + + let mut d = &bytes[4..]; + while !d.is_empty() { + match u32::consensus_decode(&mut d) { + Ok(index) => dpath.push(index.into()), + Err(e) => return Err(e)?, + } + } + + Ok((fprint, dpath.into())) + } + } + + // partial sigs + impl Serialize for Vec { + fn serialize(&self) -> Vec { + self.clone() + } + } + + impl Deserialize for Vec { + fn deserialize(bytes: &[u8]) -> Result { + Ok(bytes.to_vec()) + } + } + + impl Serialize for PsbtSighashType { + fn serialize(&self) -> Vec { + serialize(&self.to_u32()) + } + } + + impl Deserialize for PsbtSighashType { + fn deserialize(bytes: &[u8]) -> Result { + let raw: u32 = encode::deserialize(bytes)?; + Ok(PsbtSighashType::from_u32(raw)) + } + } + + // Taproot related ser/deser + impl Serialize for XOnlyPublicKey { + fn serialize(&self) -> Vec { + XOnlyPublicKey::serialize(self).to_vec() + } + } + + impl Deserialize for XOnlyPublicKey { + fn deserialize(bytes: &[u8]) -> Result { + XOnlyPublicKey::from_slice(bytes).map_err(|_| Error::InvalidXOnlyPublicKey) + } + } + + impl Serialize for taproot::Signature { + fn serialize(&self) -> Vec { + self.to_vec() + } + } + + impl Deserialize for taproot::Signature { + fn deserialize(bytes: &[u8]) -> Result { + taproot::Signature::from_slice(bytes).map_err(Error::InvalidTaprootSignature) + } + } + + impl Serialize for (XOnlyPublicKey, TapLeafHash) { + fn serialize(&self) -> Vec { + let ser_pk = self.0.serialize(); + let mut buf = Vec::with_capacity(ser_pk.len() + self.1.as_byte_array().len()); + buf.extend(ser_pk); + buf.extend(self.1.as_byte_array()); + buf + } + } + + impl Deserialize for (XOnlyPublicKey, TapLeafHash) { + fn deserialize(bytes: &[u8]) -> Result { + if bytes.len() < 32 { + return Err(Error::ConsensusEncoding( + bitcoin::consensus::encode::Error::ParseFailed( + "Not enough bytes for public key and tapleaf hash", + ), + )); + } + let a: XOnlyPublicKey = Deserialize::deserialize(&bytes[..32])?; + let b: TapLeafHash = Deserialize::deserialize(&bytes[32..])?; + Ok((a, b)) + } + } + + impl Serialize for ControlBlock { + fn serialize(&self) -> Vec { + ControlBlock::serialize(self) + } + } + + impl Deserialize for ControlBlock { + fn deserialize(bytes: &[u8]) -> Result { + Self::decode(bytes).map_err(|_| Error::InvalidControlBlock) + } + } + + // Versioned ScriptBuf + impl Serialize for (ScriptBuf, LeafVersion) { + fn serialize(&self) -> Vec { + let mut buf = Vec::with_capacity(self.0.len() + 1); + buf.extend(self.0.as_bytes()); + buf.push(self.1.to_consensus()); + buf + } + } + + impl Deserialize for (ScriptBuf, LeafVersion) { + fn deserialize(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err(Error::ConsensusEncoding( + bitcoin::consensus::encode::Error::ParseFailed( + "Not enough bytes for script buf and leaf version", + ), + )); + } + // The last byte is LeafVersion. + let script = ScriptBuf::deserialize(&bytes[..bytes.len() - 1])?; + let leaf_ver = LeafVersion::from_consensus(bytes[bytes.len() - 1]) + .map_err(|_| Error::InvalidLeafVersion)?; + Ok((script, leaf_ver)) + } + } + + impl Serialize for (Vec, KeySource) { + fn serialize(&self) -> Vec { + let mut buf = Vec::with_capacity(32 * self.0.len() + key_source_len(&self.1)); + self.0 + .consensus_encode(&mut buf) + .expect("Vecs don't error allocation"); + // TODO: Add support for writing into a writer for key-source + buf.extend(self.1.serialize()); + buf + } + } + + impl Deserialize for (Vec, KeySource) { + fn deserialize(bytes: &[u8]) -> Result { + let (leafhash_vec, consumed) = deserialize_partial::>(bytes)?; + let key_source = KeySource::deserialize(&bytes[consumed..])?; + Ok((leafhash_vec, key_source)) + } + } + + impl Serialize for TapTree { + fn serialize(&self) -> Vec { + let capacity = self + .script_leaves() + .map(|l| { + l.script().len() + VarInt(l.script().len() as u64).size() // script version + + 1 // merkle branch + + 1 // leaf version + }) + .sum::(); + let mut buf = Vec::with_capacity(capacity); + for leaf_info in self.script_leaves() { + // # Cast Safety: + // + // TaprootMerkleBranch can only have len atmost 128(TAPROOT_CONTROL_MAX_NODE_COUNT). + // safe to cast from usize to u8 + buf.push(leaf_info.merkle_branch().len() as u8); + buf.push(leaf_info.version().to_consensus()); + leaf_info + .script() + .consensus_encode(&mut buf) + .expect("Vecs dont err"); + } + buf + } + } + + impl Deserialize for TapTree { + fn deserialize(bytes: &[u8]) -> Result { + let mut builder = TaprootBuilder::new(); + let mut bytes_iter = bytes.iter(); + while let Some(depth) = bytes_iter.next() { + let version = bytes_iter + .next() + .ok_or(Error::Taproot("Invalid Taproot Builder"))?; + let (script, consumed) = deserialize_partial::(bytes_iter.as_slice())?; + if consumed > 0 { + bytes_iter.nth(consumed - 1); + } + let leaf_version = + LeafVersion::from_consensus(*version).map_err(|_| Error::InvalidLeafVersion)?; + builder = builder + .add_leaf_with_ver(*depth, script, leaf_version) + .map_err(|_| Error::Taproot("Tree not in DFS order"))?; + } + TapTree::try_from(builder).map_err(Error::TapTree) + } + } + + // Helper function to compute key source len + fn key_source_len(key_source: &KeySource) -> usize { + 4 + 4 * (key_source.1).as_ref().len() + } +} diff --git a/bitcoin_client_rs/src/wallet.rs b/bitcoin_client_rs/src/wallet.rs new file mode 100644 index 000000000..d7994b890 --- /dev/null +++ b/bitcoin_client_rs/src/wallet.rs @@ -0,0 +1,392 @@ +use core::convert::From; +use core::iter::IntoIterator; +use core::str::FromStr; + +use bitcoin::{ + bip32::{DerivationPath, Error, Fingerprint, KeySource, Xpub}, + consensus::encode::{self, VarInt}, + hashes::{sha256, Hash, HashEngine}, +}; + +use crate::merkle::MerkleTree; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Version { + V1 = 1, + V2 = 2, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum AddressType { + /// Legacy address type. P2PKH for single sig, P2SH for scripts. + Legacy, + /// Native segwit v0 address type. P2WPKH for single sig, P2WPSH for scripts. + NativeSegwit, + /// Nested segwit v0 address type. P2SH-P2WPKH for single sig, P2SH-P2WPSH for scripts. + NestedSegwit, + /// Segwit v1 Taproot address type. P2TR always. + Taproot, +} + +/// Represents a wallet stored with a wallet policy. +pub struct WalletPolicy { + /// wallet name (ASCII string, max 64 bytes) + pub name: String, + /// wallet version + pub version: Version, + /// descriptor with keys aliased by '@i', + /// i the index of the key in the keys array. + pub descriptor_template: String, + /// Keys are the extended pubkeys used in the descriptor. + pub keys: Vec, + /// Threshold of the multisig policy, + /// None if the policy is not a multisig. + pub threshold: Option, +} + +impl WalletPolicy { + pub fn new( + name: String, + version: Version, + descriptor_template: String, + keys: impl IntoIterator>, + ) -> Self { + Self { + name, + version, + descriptor_template, + keys: keys.into_iter().map(|k| k.into()).collect(), + threshold: None, + } + } + + pub fn new_multisig>( + name: String, + version: Version, + address_type: AddressType, + threshold: usize, + keys: impl IntoIterator, + sorted: bool, + ) -> Result { + let keys: Vec = keys.into_iter().map(|k| k.into()).collect(); + if threshold < 1 || threshold > keys.len() { + return Err(WalletError::InvalidThreshold); + } + + let key_placeholder_suffix = if version == Version::V2 { "/**" } else { "" }; + let multisig_op = if sorted { "sortedmulti" } else { "multi" }; + let keys_str = keys + .iter() + .enumerate() + .map(|(i, _)| format!("@{}{}", i, key_placeholder_suffix)) + .collect::>() + .join(","); + + let descriptor_template = match address_type { + AddressType::Legacy => format!("sh({}({},{}))", multisig_op, threshold, keys_str), + AddressType::NativeSegwit => { + format!("wsh({}({},{}))", multisig_op, threshold, keys_str) + } + AddressType::NestedSegwit => { + format!("sh(wsh({}({},{})))", multisig_op, threshold, keys_str) + } + _ => return Err(WalletError::UnsupportedAddressType), + }; + + Ok(Self { + name, + version, + descriptor_template, + keys, + threshold: Some(threshold), + }) + } + + pub fn serialize(&self) -> Vec { + let mut res: Vec = (self.version as u8).to_be_bytes().to_vec(); + res.extend_from_slice(&(self.name.len() as u8).to_be_bytes()); + res.extend_from_slice(self.name.as_bytes()); + res.extend(encode::serialize(&VarInt( + self.descriptor_template.as_bytes().len() as u64, + ))); + + if self.version == Version::V2 { + let mut engine = sha256::Hash::engine(); + engine.input(self.descriptor_template.as_bytes()); + let hash = sha256::Hash::from_engine(engine).to_byte_array(); + res.extend_from_slice(&hash); + } else { + res.extend_from_slice(self.descriptor_template.as_bytes()); + } + + res.extend(encode::serialize(&VarInt(self.keys.len() as u64))); + + res.extend_from_slice( + MerkleTree::new( + self.keys + .iter() + .map(|key| { + let mut preimage = vec![0x00]; + preimage.extend_from_slice(key.to_string().as_bytes()); + let mut engine = sha256::Hash::engine(); + engine.input(&preimage); + sha256::Hash::from_engine(engine).to_byte_array() + }) + .collect(), + ) + .root_hash(), + ); + + res + } + + pub fn get_descriptor(&self, change: bool) -> Result { + let mut desc = self.descriptor_template.clone(); + + for (i, key) in self.keys.iter().enumerate().rev() { + desc = desc.replace(&format!("@{}", i), &key.to_string()); + } + + desc = desc.replace("/**", &format!("/{}/{}", if change { 1 } else { 0 }, "*")); + + // For every "/" expression, replace with M if not change, or with N if change + while let Some(start) = desc.find("/<") { + if let Some(end) = desc.find(">") { + let nums: Vec<&str> = desc[start + 2..end].split(";").collect(); + if nums.len() == 2 { + let replacement = if change { nums[1] } else { nums[0] }; + desc = format!("{}{}{}", &desc[..start + 1], replacement, &desc[end + 1..]); + } else { + return Err(WalletError::InvalidPolicy); + } + } + } + + Ok(desc) + } + + pub fn id(&self) -> [u8; 32] { + let mut engine = sha256::Hash::engine(); + engine.input(&self.serialize()); + sha256::Hash::from_engine(engine).to_byte_array() + } +} + +#[derive(Debug)] +pub enum WalletError { + InvalidThreshold, + UnsupportedAddressType, + InvalidPolicy, +} + +#[derive(PartialEq, Eq)] +pub struct WalletPubKey { + pub inner: Xpub, + pub source: Option, + + /// Used by Version V1 + /// either /** or //* + pub multipath: Option, +} + +impl From for WalletPubKey { + fn from(inner: Xpub) -> Self { + Self { + inner, + source: None, + multipath: None, + } + } +} + +impl From<(KeySource, Xpub)> for WalletPubKey { + fn from(source_xpub: (KeySource, Xpub)) -> Self { + Self { + inner: source_xpub.1, + source: Some(source_xpub.0), + multipath: None, + } + } +} + +impl From<(KeySource, Xpub, String)> for WalletPubKey { + fn from(source_xpub: (KeySource, Xpub, String)) -> Self { + Self { + inner: source_xpub.1, + source: Some(source_xpub.0), + multipath: Some(source_xpub.2), + } + } +} + +impl FromStr for WalletPubKey { + type Err = Error; + + fn from_str(s: &str) -> Result { + if let Ok(key) = Xpub::from_str(s) { + Ok(WalletPubKey { + inner: key, + source: None, + multipath: None, + }) + } else { + let (keysource_str, xpub_str) = s + .strip_prefix('[') + .and_then(|s| s.rsplit_once(']')) + .ok_or(Error::InvalidDerivationPathFormat)?; + let (f_str, path_str) = keysource_str.split_once('/').unwrap_or((keysource_str, "")); + let fingerprint = + Fingerprint::from_str(f_str).map_err(|_| Error::InvalidDerivationPathFormat)?; + let derivation_path = if path_str.is_empty() { + DerivationPath::master() + } else { + DerivationPath::from_str(&format!("m/{}", path_str))? + }; + let (xpub_str, multipath) = if let Some((xpub, multipath)) = xpub_str.rsplit_once('/') { + (xpub, Some(format!("/{}", multipath))) + } else { + (xpub_str, None) + }; + Ok(WalletPubKey { + inner: Xpub::from_str(xpub_str)?, + source: Some((fingerprint, derivation_path)), + multipath, + }) + } + } +} + +impl core::fmt::Display for WalletPubKey { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if self.source.is_none() { + write!(f, "{}", self.inner) + } else { + write!( + f, + "[{}{}]{}{}", + self.source.as_ref().unwrap().0, + self.source + .as_ref() + .unwrap() + .1 + .to_string() + .strip_prefix('m') + .unwrap_or(""), + self.inner, + self.multipath.as_ref().unwrap_or(&"".to_string()) + ) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitcoin::hashes::hex::FromHex; + use core::str::FromStr; + + const MASTER_KEY_EXAMPLE: &str = "[5c9e228d]tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW/**"; + const KEY_EXAMPLE: &str = "[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW/**"; + + #[test] + fn test_master_walletpubkey_fromstr() { + let key = WalletPubKey::from_str(MASTER_KEY_EXAMPLE).unwrap(); + assert_eq!( + key.source.as_ref().unwrap().0, + Fingerprint::from_str("5c9e228d").unwrap() + ); + assert_eq!(key.source.as_ref().unwrap().1, DerivationPath::master()); + assert_eq!(key.inner, Xpub::from_str("tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW").unwrap()); + assert_eq!(key.multipath, Some("/**".to_string())); + } + + #[test] + fn test_walletpubkey_fromstr() { + let key = WalletPubKey::from_str(KEY_EXAMPLE).unwrap(); + assert_eq!( + key.source.as_ref().unwrap().0, + Fingerprint::from_str("5c9e228d").unwrap() + ); + assert_eq!( + key.source.as_ref().unwrap().1, + DerivationPath::from_str("m/48'/1'/0'/0'").unwrap() + ); + assert_eq!(key.inner, Xpub::from_str("tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW").unwrap()); + assert_eq!(key.multipath, Some("/**".to_string())); + } + + #[test] + fn test_walletpubkey_tostr() { + let key = WalletPubKey::from_str(KEY_EXAMPLE).unwrap(); + assert_eq!(key.to_string(), format!("{}", KEY_EXAMPLE)); + } + + #[test] + fn test_wallet_serialize_v2() { + let wallet = WalletPolicy::new( + "Cold storage".to_string(), + Version::V2, + "wsh(sortedmulti(2,@0/**,@1/**))".to_string(), + vec![ + WalletPubKey::from_str("[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF").unwrap(), + WalletPubKey::from_str("[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK").unwrap(), + ], + ); + assert_eq!(wallet.serialize().as_slice(), Vec::::from_hex("020c436f6c642073746f726167651fb56c3d5542fa09b3956834a9ff6a1df5c36a38e5b02c63c54b41a9a04403b82602516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb").unwrap()); + } + + #[test] + fn test_get_descriptor() { + let wallet = WalletPolicy::new( + "Cold storage".to_string(), + Version::V2, + "wsh(sortedmulti(2,@0/**,@1/<12;3>/*))".to_string(), + vec![ + WalletPubKey::from_str("[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF").unwrap(), + WalletPubKey::from_str("[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK").unwrap(), + ], + ); + + assert_eq!(wallet.get_descriptor(false).unwrap(), "wsh(sortedmulti(2,[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/0/*,[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/12/*))"); + assert_eq!(wallet.get_descriptor(true).unwrap(), "wsh(sortedmulti(2,[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/1/*,[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/3/*))"); + + let wallet = WalletPolicy::new( + "Cold storage".to_string(), + Version::V2, + "wsh(or_d(pk(@0/<0;1>/*),and_v(v:pkh(@1/<0;1>/*),older(65535))))".to_string(), + vec![ + WalletPubKey::from_str("[ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N").unwrap(), + WalletPubKey::from_str("[053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp").unwrap(), + ], + ); + assert_eq!(wallet.get_descriptor(false).unwrap(), "wsh(or_d(pk([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/0/*),and_v(v:pkh([053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp/0/*),older(65535))))"); + + assert_eq!(wallet.get_descriptor(true).unwrap(), "wsh(or_d(pk([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/1/*),and_v(v:pkh([053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp/1/*),older(65535))))"); + + let wallet = WalletPolicy::new( + "Cold storage".to_string(), + Version::V2, + "wsh(or_d(pk(@0/<0;1>/*),and_v(v:pkh(@1/**),older(65535))))".to_string(), + vec![ + WalletPubKey::from_str("[ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N").unwrap(), + WalletPubKey::from_str("[053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp").unwrap(), + ], + ); + assert_eq!(wallet.get_descriptor(false).unwrap(), "wsh(or_d(pk([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/0/*),and_v(v:pkh([053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp/0/*),older(65535))))"); + + assert_eq!(wallet.get_descriptor(true).unwrap(), "wsh(or_d(pk([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/1/*),and_v(v:pkh([053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp/1/*),older(65535))))"); + + let wallet = WalletPolicy::new( + "Cold storage".to_string(), + Version::V2, + "wsh(or_d(pk(@0/**),and_v(v:pkh(@1/**),older(65535))))".to_string(), + vec![ + WalletPubKey::from_str("[ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N").unwrap(), + WalletPubKey::from_str("[053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp").unwrap(), + ], + ); + assert_eq!(wallet.get_descriptor(false).unwrap(), "wsh(or_d(pk([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/0/*),and_v(v:pkh([053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp/0/*),older(65535))))"); + + assert_eq!(wallet.get_descriptor(true).unwrap(), "wsh(or_d(pk([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/1/*),and_v(v:pkh([053f423f/48'/1'/0'/2']tpubDEGZMZiz8Vnp7N7cTM9Cty897GJpQ8jqmw2yyDKMPfbMzqPtRbo8wViKtkx6zfrzY6jW5NPNULeN9j7oYCqvrFxCkhSdJs7QxwZ3qQ1PXSp/1/*),older(65535))))"); + } +} diff --git a/bitcoin_client_rs/tests/client.rs b/bitcoin_client_rs/tests/client.rs new file mode 100644 index 000000000..53b72016d --- /dev/null +++ b/bitcoin_client_rs/tests/client.rs @@ -0,0 +1,370 @@ +mod utils; +use std::str::FromStr; + +use bitcoin::{ + bip32::DerivationPath, + hashes::{hex::FromHex, Hash}, + psbt::Psbt, +}; +use ledger_bitcoin_client::{async_client, client, psbt::PartialSignature, wallet}; + +fn test_cases(path: &str) -> Vec { + let data = std::fs::read_to_string(path).expect("Unable to read file"); + serde_json::from_str(&data).expect("Wrong tests data") +} + +#[tokio::test] +async fn test_get_version() { + let exchanges: Vec = vec![ + "=> b001000000".into(), + "<= 010c426974636f696e205465737405322e312e3001009000".into(), + ]; + + let store = utils::RecordStore::new(&exchanges); + let (name, version, flags) = + client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .get_version() + .unwrap(); + + assert_eq!(name, "Bitcoin Test".to_string()); + assert_eq!(version, "2.1.0".to_string()); + assert_eq!(flags, vec![0x00]); + + let (name, version, flags) = + async_client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .get_version() + .await + .unwrap(); + + assert_eq!(name, "Bitcoin Test".to_string()); + assert_eq!(version, "2.1.0".to_string()); + assert_eq!(flags, vec![0x00]); +} + +#[tokio::test] +async fn test_sign_message() { + let exchanges: Vec = vec![ + "=> e110000132048000002c800000018000000000000000058a2a5c9b768827de5a9552c38a044c66959c68f6d2f21b5260af54d2f87db827".into(), + "<= 418a2a5c9b768827de5a9552c38a044c66959c68f6d2f21b5260af54d2f87db8270100e000".into(), + "=> f8010001228a2a5c9b768827de5a9552c38a044c66959c68f6d2f21b5260af54d2f87db8270000".into(), + "<= 40008a2a5c9b768827de5a9552c38a044c66959c68f6d2f21b5260af54d2f87db827e000".into(), + "=> f80100010806060068656c6c6f".into(), + "<= 20bdeef462c0ce01b905db5206a51ed05a36671d1494ac12b18c764dbb955f45542c5819611050096d16ed03a5b01fc9806c163619777986235ed75fc91ee933e69000".into(), + ]; + + let path = DerivationPath::from_str("m/44'/1'/0'/0").unwrap(); + let store = utils::RecordStore::new(&exchanges); + let (header, ecdsa_sig) = + client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .sign_message("hello".as_bytes(), &path) + .unwrap(); + + assert_eq!(header, 0x20); + let mut sig = vec![header]; + sig.extend(ecdsa_sig.serialize_compact()); + assert_eq!( + "IL3u9GLAzgG5BdtSBqUe0Fo2Zx0UlKwSsYx2TbuVX0VULFgZYRBQCW0W7QOlsB/JgGwWNhl3eYYjXtdfyR7pM+Y=", + base64::encode(sig) + ); + + let (header, ecdsa_sig) = + async_client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .sign_message("hello".as_bytes(), &path) + .await + .unwrap(); + + assert_eq!(header, 0x20); + let mut sig = vec![header]; + sig.extend(ecdsa_sig.serialize_compact()); + assert_eq!( + "IL3u9GLAzgG5BdtSBqUe0Fo2Zx0UlKwSsYx2TbuVX0VULFgZYRBQCW0W7QOlsB/JgGwWNhl3eYYjXtdfyR7pM+Y=", + base64::encode(sig) + ); +} + +#[tokio::test] +async fn test_get_extended_pubkey() { + for case in test_cases("./tests/data/get_extended_pubkey.json") { + let exchanges: Vec = case + .get("exchanges") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let derivation_path: DerivationPath = case + .get("derivation_path") + .map(|v| v.as_str().unwrap()) + .map(|s| DerivationPath::from_str(&s).unwrap()) + .unwrap(); + + let display: bool = case + .get("display") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let xpk_str: String = case + .get("result") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let store = utils::RecordStore::new(&exchanges); + let key = client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .get_extended_pubkey(&derivation_path, display) + .unwrap(); + + assert_eq!(key.to_string(), xpk_str); + + let key = async_client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .get_extended_pubkey(&derivation_path, display) + .await + .unwrap(); + + assert_eq!(key.to_string(), xpk_str); + } +} + +#[tokio::test] +async fn test_register_wallet() { + for case in test_cases("./tests/data/register_wallet.json") { + let exchanges: Vec = case + .get("exchanges") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let name: String = case + .get("name") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let policy: String = case + .get("policy") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let keys_str: Vec = case + .get("keys") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let keys: Vec = keys_str + .iter() + .map(|s| wallet::WalletPubKey::from_str(s).unwrap()) + .collect(); + + let hmac_result: String = case + .get("hmac") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let version: usize = case + .get("version") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let version = if version == 1 { + wallet::Version::V1 + } else { + wallet::Version::V2 + }; + + let wallet = wallet::WalletPolicy::new(name, version, policy, keys); + + let store = utils::RecordStore::new(&exchanges); + let (_id, hmac) = client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .register_wallet(&wallet) + .unwrap(); + + assert_eq!(hmac, <[u8; 32]>::from_hex(&hmac_result).unwrap()); + + let (_id, hmac) = + async_client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .register_wallet(&wallet) + .await + .unwrap(); + + assert_eq!(hmac, <[u8; 32]>::from_hex(&hmac_result).unwrap()); + } +} + +#[tokio::test] +async fn test_get_wallet_address() { + for case in test_cases("./tests/data/get_wallet_address.json") { + let exchanges: Vec = case + .get("exchanges") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let name: String = case + .get("name") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let policy: String = case + .get("policy") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let keys_str: Vec = case + .get("keys") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let keys: Vec = keys_str + .iter() + .map(|s| wallet::WalletPubKey::from_str(s).unwrap()) + .collect(); + + let hmac: Option = case + .get("hmac") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + let hmac = hmac.map(|s| { + let mut h = [b'\0'; 32]; + h.copy_from_slice(&Vec::from_hex(&s).unwrap()); + h + }); + + let change: bool = case + .get("change") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let display: bool = case + .get("display") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let address_index: u32 = case + .get("address_index") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let address_result: String = case + .get("address") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let wallet = wallet::WalletPolicy::new(name, wallet::Version::V2, policy, keys); + + let store = utils::RecordStore::new(&exchanges); + let address = client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .get_wallet_address(&wallet, hmac.as_ref(), change, address_index, display) + .unwrap(); + + assert_eq!(address.assume_checked().to_string(), address_result); + + let address = + async_client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .get_wallet_address(&wallet, hmac.as_ref(), change, address_index, display) + .await + .unwrap(); + + assert_eq!(address.assume_checked().to_string(), address_result); + } +} + +#[tokio::test] +async fn test_sign_psbt() { + for case in test_cases("./tests/data/sign_psbt.json") { + let exchanges: Vec = case + .get("exchanges") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let name: String = case + .get("name") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let policy: String = case + .get("policy") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let keys_str: Vec = case + .get("keys") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let keys: Vec = keys_str + .iter() + .map(|s| wallet::WalletPubKey::from_str(s).unwrap()) + .collect(); + + let hmac: Option = case + .get("hmac") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + let hmac = hmac.map(|s| { + let mut h = [b'\0'; 32]; + h.copy_from_slice(&Vec::from_hex(&s).unwrap()); + h + }); + + let sigs: Vec = case + .get("sigs") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let psbt_str: String = case + .get("psbt") + .map(|v| serde_json::from_value(v.clone()).unwrap()) + .unwrap(); + + let psbt = Psbt::deserialize(&base64::decode(&psbt_str).unwrap()).unwrap(); + + let wallet = wallet::WalletPolicy::new(name, wallet::Version::V2, policy, keys); + + let store = utils::RecordStore::new(&exchanges); + let res = client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .sign_psbt(&psbt, &wallet, hmac.as_ref()) + .unwrap(); + + let check_signatures = |sigs: &[serde_json::Value], res: Vec<(usize, PartialSignature)>| { + for (i, psbt_sig) in res { + for (j, res_sig) in sigs.iter().enumerate() { + if i == j { + match psbt_sig { + PartialSignature::TapScriptSig(key, tapleaf_hash, sig) => { + assert_eq!( + res_sig + .get("key") + .map(|v| serde_json::from_value::(v.clone()) + .unwrap()) + .unwrap(), + key.to_string() + ); + if let Some(tapleaf_hash_res) = res_sig + .get("tapleaf_hash") + .map(|v| serde_json::from_value::(v.clone()).unwrap()) + { + assert_eq!( + tapleaf_hash_res, + hex::encode(tapleaf_hash.unwrap().to_byte_array()) + ); + } + assert_eq!( + res_sig + .get("sig") + .map(|v| serde_json::from_value::(v.clone()) + .unwrap()) + .unwrap(), + hex::encode(sig.to_vec()) + ); + } + _ => {} + } + } + } + } + }; + + check_signatures(&sigs, res); + + let res = async_client::BitcoinClient::new(utils::TransportReplayer::new(store.clone())) + .sign_psbt(&psbt, &wallet, hmac.as_ref()) + .await + .unwrap(); + + check_signatures(&sigs, res); + } +} diff --git a/bitcoin_client_rs/tests/data/get_extended_pubkey.json b/bitcoin_client_rs/tests/data/get_extended_pubkey.json new file mode 100644 index 000000000..6d8123ff9 --- /dev/null +++ b/bitcoin_client_rs/tests/data/get_extended_pubkey.json @@ -0,0 +1,45 @@ +[{ + "derivation_path": "m/44'/0'/0'/0/0", + "display": false, + "exchanges": [ + "=> e10000011600058000002c80000000800000000000000000000000", + "<= 7870756236474c3651666638763454545438446b645031426a5067655862324866465739433731544a64347a5750484d677837316b3438443179323852736a55784a5157716d34314c484b506567446f4637766b6b75524353673765736761626d65516442635a775a586b666f46529000" + ], + "result": "xpub6GL6Qff8v4TTT8DkdP1BjPgeXb2HfFW9C71TJd4zWPHMgx71k48D1y28RsjUxJQWqm41LHKPegDoF7vkkuRCSg7esgabmeQdBcZwZXkfoFR" +}, +{ + "derivation_path": "m/44'/0'/0'/0/1", + "display": true, + "exchanges": [ + "=> e10000011601058000002c80000000800000000000000000000001", + "<= 7870756236474c36516666387634545455663536716d3437475054724b6f6632746f50326b717434326a5664787a52536f38723874326d715a5a41533776723770436f3337485750685067707938616d7268544a6a66546e716b4d31645a33383761437a4e7a64416a666e3653516d9000" + ], + "result": "xpub6GL6Qff8v4TTUf56qm47GPTrKof2toP2kqt42jVdxzRSo8r8t2mqZZAS7vr7pCo37HWPhPgpy8amrhTJjfTnqkM1dZ387aCzNzdAjfn6SQm" +}, +{ + "derivation_path": "m/44'/1'/0'", + "display": false, + "exchanges": [ + "=> e10000010e00038000002c8000000180000000", + "<= 74707562444377596a70446855645047503572533377674e6731336d5472726a427547385639567057627970745836545250624e6f5a5658736f5655536b436a6d51386a4a79636a75444b4262396561746153796d58616b5454614769667852366b6d56736646656848315a674a549000" + ], + "result": "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT" +}, +{ + "derivation_path": "m/49'/1'/1'/1/3", + "display": false, + "exchanges": [ + "=> e10000011600058000003180000001800000010000000100000003", + "<= 7470756244476e65746d4a44434c3138547961616f79524159626b5345397762486b7453645453346d66735236696e433863327236546a64427433776b7145516848595074587061343678707844614358553250524e55475676447a414850473668485261765962774147666e46729000" + ], + "result": "tpubDGnetmJDCL18TyaaoyRAYbkSE9wbHktSdTS4mfsR6inC8c2r6TjdBt3wkqEQhHYPtXpa46xpxDaCXU2PRNUGVvDzAHPG6hHRavYbwAGfnFr" +}, +{ + "derivation_path": "m/84'/1'/2'/0/10", + "display": false, + "exchanges": [ + "=> e1000001160005800000548000000180000002000000000000000a", + "<= 7470756244473959705355775363574a42425372686e415434374e6354344e5a474c6359313863706b616957486e6b554369313945744368384865656f783236384e614646366f35366e566553587554794b366a707a547656316836384b723365644138415a7032374d694c554e749000" + ], + "result": "tpubDG9YpSUwScWJBBSrhnAT47NcT4NZGLcY18cpkaiWHnkUCi19EtCh8Heeox268NaFF6o56nVeSXuTyK6jpzTvV1h68Kr3edA8AZp27MiLUNt" +}] diff --git a/bitcoin_client_rs/tests/data/get_wallet_address.json b/bitcoin_client_rs/tests/data/get_wallet_address.json new file mode 100644 index 000000000..08ecc9660 --- /dev/null +++ b/bitcoin_client_rs/tests/data/get_wallet_address.json @@ -0,0 +1,61 @@ +[{ + "name": "", + "policy": "tr(@0/**)", + "keys": [ + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U" + ], + "hmac": null, + "change": false, + "address_index": 0, + "display": true, + "exchanges": [ + "=> e10300014601627535418bc03eeee2b62b3a0254dc0624881f8bc6fc20c4d3b2c1c4fc92989300000000000000000000000000000000000000000000000000000000000000000000000000", + "<= 4000627535418bc03eeee2b62b3a0254dc0624881f8bc6fc20c4d3b2c1c4fc929893e000", + "=> f80100014644440200097c54d8c8cd3bac81abf56463d3d3ed2efa94afd9678707fcd68a4c990a71ea6b01feacb8b161672bffec7b35f4035eceb1d9c918e2507b51d1716324b968856803", + "<= 40007c54d8c8cd3bac81abf56463d3d3ed2efa94afd9678707fcd68a4c990a71ea6be000", + "=> f80100010b090974722840302f2a2a29", + "<= 41feacb8b161672bffec7b35f4035eceb1d9c918e2507b51d1716324b9688568030100e000", + "=> f801000122feacb8b161672bffec7b35f4035eceb1d9c918e2507b51d1716324b9688568030000", + "<= 4000feacb8b161672bffec7b35f4035eceb1d9c918e2507b51d1716324b968856803e000", + "=> f8010001868484005b66356163633266642f3836272f31272f30275d7470756244444b59453642524576447353574d617a67486f795157694a775961444459506243466a59784e334846584a5035666f6b65694b3468774b3574544c424e454442777244586e3863513476396232786457363258723579786f51644d753176366337554458595648323755", + "<= 41feacb8b161672bffec7b35f4035eceb1d9c918e2507b51d1716324b9688568030100e000", + "=> f801000122feacb8b161672bffec7b35f4035eceb1d9c918e2507b51d1716324b9688568030000", + "<= 4000feacb8b161672bffec7b35f4035eceb1d9c918e2507b51d1716324b968856803e000", + "=> f8010001868484005b66356163633266642f3836272f31272f30275d7470756244444b59453642524576447353574d617a67486f795157694a775961444459506243466a59784e334846584a5035666f6b65694b3468774b3574544c424e454442777244586e3863513476396232786457363258723579786f51644d753176366337554458595648323755", + "<= 7462317077733877766e6a3939636136616366386b7137706a6b377679786b6e61683064396d6578636b6835733076753263637936386a7339616d3675379000" + ], + "address": "tb1pws8wvnj99ca6acf8kq7pjk7vyxknah0d9mexckh5s0vu2ccy68js9am6u7" +}, +{ + "name": "Cold storage", + "policy": "wsh(sortedmulti(2,@0/**,@1/**))", + "keys": [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK" + ], + "hmac": "d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb371", + "change": false, + "address_index": 0, + "display": true, + "exchanges": [ + "=> e10300014601cd9474ae9e74403128477789789db43a215e996af80d60120f0d844f8404ac64d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb3710000000000", + "<= 4000cd9474ae9e74403128477789789db43a215e996af80d60120f0d844f8404ac64e000", + "=> f8010001525050020c436f6c642073746f726167651fb56c3d5542fa09b3956834a9ff6a1df5c36a38e5b02c63c54b41a9a04403b82602516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb", + "<= 4000b56c3d5542fa09b3956834a9ff6a1df5c36a38e5b02c63c54b41a9a04403b826e000", + "=> f8010001211f1f77736828736f727465646d756c746928322c40302f2a2a2c40312f2a2a2929", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0200e000", + "=> f801000142521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775010179ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1", + "<= 4000521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775e000", + "=> f8010001898787005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b3233417746", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 746231716d796175797a6e30386364757a647177656578676e61327370776430726e646a35356673726b65667279326370757974346370736e32706732389000" + ], + "address": "tb1qmyauyzn08cduzdqweexgna2spwd0rndj55fsrkefry2cpuyt4cpsn2pg28" +}] diff --git a/bitcoin_client_rs/tests/data/register_wallet.json b/bitcoin_client_rs/tests/data/register_wallet.json new file mode 100644 index 000000000..d3bea619b --- /dev/null +++ b/bitcoin_client_rs/tests/data/register_wallet.json @@ -0,0 +1,89 @@ +[{ + "name": "Cold storage", + "policy": "wsh(sortedmulti(2,@0,@1))", + "keys": [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**" + ], + "version": 1, + "threshold": 2, + "exchanges": [ + "=> e10200014a49010c436f6c642073746f726167651977736828736f727465646d756c746928322c40302c4031292902ab6711da5c7daaaba19169f693013ef5139eee794d4997acb8a40d1c25f87838", + "<= 41ab6711da5c7daaaba19169f693013ef5139eee794d4997acb8a40d1c25f878380200e000", + "=> f8010001428781445a699699be617cd74b7a311ad30d77fe480e32c2bb96ae04bdebecf5a70101e160a27fd6b868d8a958160b2c913def129ad0f98ee1dce60e095bbb2102f47c", + "<= 40008781445a699699be617cd74b7a311ad30d77fe480e32c2bb96ae04bdebecf5a7e000", + "=> f80100018c8a8a005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b32334177462f2a2a", + "<= 41ab6711da5c7daaaba19169f693013ef5139eee794d4997acb8a40d1c25f878380201e000", + "=> f801000142e160a27fd6b868d8a958160b2c913def129ad0f98ee1dce60e095bbb2102f47c01018781445a699699be617cd74b7a311ad30d77fe480e32c2bb96ae04bdebecf5a7", + "<= 4000e160a27fd6b868d8a958160b2c913def129ad0f98ee1dce60e095bbb2102f47ce000", + "=> f80100018c8a8a005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b2f2a2a", + "<= 19ead5d0873068a8eab4df5dfefb2080c9c7d8bfd2fc2fea27f26b299cf4c46bd6434852fb3caa7edbd1165084968f1691444b3cfc10cf1e431acbbc7f48451f9000" + ], + "hmac": "d6434852fb3caa7edbd1165084968f1691444b3cfc10cf1e431acbbc7f48451f" +}, +{ + "name": "Cold storage", + "policy": "wsh(sortedmulti(2,@0/**,@1/**))", + "keys": [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK" + ], + "version": 2, + "threshold": 2, + "exchanges": [ + "=> e10200015150020c436f6c642073746f726167651fb56c3d5542fa09b3956834a9ff6a1df5c36a38e5b02c63c54b41a9a04403b82602516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb", + "<= 4000b56c3d5542fa09b3956834a9ff6a1df5c36a38e5b02c63c54b41a9a04403b826e000", + "=> f8010001211f1f77736828736f727465646d756c746928322c40302f2a2a2c40312f2a2a2929", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0200e000", + "=> f801000142521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775010179ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1", + "<= 4000521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775e000", + "=> f8010001898787005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b3233417746", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0200e000", + "=> f801000142521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775010179ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1", + "<= 4000521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775e000", + "=> f8010001898787005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b3233417746", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= cd9474ae9e74403128477789789db43a215e996af80d60120f0d844f8404ac64d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb3719000" + ], + "hmac": "d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb371" +}, +{ + "name": "Decaying key", + "policy": "wsh(or_d(pk(@0/**),and_v(v:pkh(@1/**),older(100))))", + "keys": [ + "[f5acc2fd]tpubD6NzVbkrYhZ4YgUx2ZLNt2rLYAMTdYysCRzKoLu2BeSHKvzqPaBDvf17GeBPnExUVPkuBpx4kniP964e2MxyzzazcXLptxLXModSVCVEV1T", + "[8a64f2a9]tpubD6NzVbkrYhZ4WmzFjvQrp7sDa4ECUxTi9oby8K4FZkd3XCBtEdKwUiQyYJaxiJo5y42gyDWEczrFpozEjeLxMPxjf2WtkfcbpUdfvNnozWF" + ], + "version": 2, + "exchanges": [ + "// e10200015150020c4465636179696e67206b657933ebddbc24e92652fbfeff6fcd547e81559732cba36990dc6f09638dd23195815902b216ad12c8f4e81c60908f7ff97307c469ff956ec16c62ec3802730a487af6f2", + "=> e10200015150020c4465636179696e67206b657933ebddbc24e92652fbfeff6fcd547e81559732cba36990dc6f09638dd23195815902445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd8", + "<= 4000ebddbc24e92652fbfeff6fcd547e81559732cba36990dc6f09638dd231958159e000", + "=> f8010001353333777368286f725f6428706b2840302f2a2a292c616e645f7628763a706b682840312f2a2a292c6f6c6465722831303029292929", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80200e000", + "=> f801000142ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf010160dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224", + "<= 4000ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbfe000", + "=> f80100017c7a7a005b66356163633266645d7470756244364e7a56626b7259685a3459675578325a4c4e7432724c59414d546459797343527a4b6f4c7532426553484b767a715061424476663137476542506e45785556506b75427078346b6e695039363465324d78797a7a617a63584c7074784c584d6f645356435645563154", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80201e000", + "=> f80100014260dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa432240101ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf", + "<= 400060dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224e000", + "=> f80100017c7a7a005b38613634663261395d7470756244364e7a56626b7259685a34576d7a466a765172703773446134454355785469396f6279384b34465a6b64335843427445644b7755695179594a6178694a6f357934326779445745637a7246706f7a456a654c784d50786a663257746b66636270556466764e6e6f7a5746", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80200e000", + "=> f801000142ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf010160dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224", + "<= 4000ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbfe000", + "=> f80100017c7a7a005b66356163633266645d7470756244364e7a56626b7259685a3459675578325a4c4e7432724c59414d546459797343527a4b6f4c7532426553484b767a715061424476663137476542506e45785556506b75427078346b6e695039363465324d78797a7a617a63584c7074784c584d6f645356435645563154", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80201e000", + "=> f80100014260dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa432240101ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf", + "<= 400060dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224e000", + "=> f80100017c7a7a005b38613634663261395d7470756244364e7a56626b7259685a34576d7a466a765172703773446134454355785469396f6279384b34465a6b64335843427445644b7755695179594a6178694a6f357934326779445745637a7246706f7a456a654c784d50786a663257746b66636270556466764e6e6f7a5746", + "<= 174e7f4806b9ce07967b25b6c82f27e9790ca9212a280c2e01d32eca06970e13c77fad66112f554d449c46ee2f63ea86be65a1bd7c4faa8f9eb5391c524067c29000" + ], + "hmac": "c77fad66112f554d449c46ee2f63ea86be65a1bd7c4faa8f9eb5391c524067c2" +}] diff --git a/bitcoin_client_rs/tests/data/sign_psbt.json b/bitcoin_client_rs/tests/data/sign_psbt.json new file mode 100644 index 000000000..593afcff4 --- /dev/null +++ b/bitcoin_client_rs/tests/data/sign_psbt.json @@ -0,0 +1,1303 @@ +[{ + "name": "", + "policy": "sh(wpkh(@0/**))", + "keys": [ + "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3" + ], + "hmac": null, + "psbt": "cHNidP8BAHICAAAAAXT0yaTajRSLu1boaayjaQ3aDOOsvPgWCcyUbRtvFkrOAQAAAAD9////AlDUEgAAAAAAFgAUMxjgT65sEq/LAJxpzVflslBK5rT1cQgAAAAAABepFG1IUtrzpUCfdyFtu46j1ZIxLX7phwAAAAAAAQCMAgAAAAHQ47WR3EhO23HqtmoOmUcxAH/rfQgqUMdC8CPqCQFNHgEAAAAXFgAU4xDQRPiNqxtCdp5KhMrwg2P57MH9////AmDqAAAAAAAAGXapFEWIHtDTWHVQ95SEe3yLn6A+3Qo8iKx/ZhsAAAAAABepFPBGTZ+g6kLYDk1fFFeIOYLiO47shwAAAAABASB/ZhsAAAAAABepFPBGTZ+g6kLYDk1fFFeIOYLiO47shwEEFgAUyweAh+/0haqiJg6UpT19bRxd0VEiBgJLo7d9kz3p+j+VgzSMQPPKry7/rVtuJE7Oirv8xyRPZxj1rML9MQAAgAEAAIAAAACAAQAAAAAAAAAAAAEAFgAUTLRHxTu3NSNPKxOQ1F2dhksVdtMiAgOKsR70a0i1XwDFPv3fOM3f+dYzW8r1L6n5k4R/LM0vVxj1rML9MQAAgAEAAIAAAACAAQAAAAIAAAAA", + "exchanges": [ + "=> e1040001c305519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e3132162086d8d9498a323006ec5982eeb4ea7c41d27020d57985512ab59ff8f40d50150701185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d502f4c4f92e968760845d5694ce5ac4ad9ed0a33d00278d6e7fe89e807f899d2637be693418eee0c522b55f74c62b6ecad9697ef1e6e8bb973ff29740432960db480000000000000000000000000000000000000000000000000000000000000000", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200500e000", + "=> f801000182fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f0303583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4000fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177fe000", + "=> f80100010402020002", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200501e000", + "=> f801000182583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0303fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200502e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a403039f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d52c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed4679782595811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200503e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d03034f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a452c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed4679782595811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 40009f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f80100010402020005", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200504e000", + "=> f80100014295811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926010112885c5025dece82b9e180bdaf19d6e5571772906c9c24de31790023755c8888", + "<= 400095811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926e000", + "=> f801000104020200fb", + "<= 42519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e31321620fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177fe000", + "=> f8010001020100", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200500e000", + "=> f801000182fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f0303583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4186d8d9498a323006ec5982eeb4ea7c41d27020d57985512ab59ff8f40d5015070500e000", + "=> f8010001820bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b903038855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a46bcf0e2e93e0a18e22789aee965e6553f4fbe93f0acfc4a705d691c8311c49650bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9", + "<= 40000bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9e000", + "=> f80100010705050002000000", + "<= 42519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e31321620583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020101", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200501e000", + "=> f801000182583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0303fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4186d8d9498a323006ec5982eeb4ea7c41d27020d57985512ab59ff8f40d5015070501e000", + "=> f8010001828855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a403030bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b96bcf0e2e93e0a18e22789aee965e6553f4fbe93f0acfc4a705d691c8311c49650bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9", + "<= 40008855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4e000", + "=> f80100010705050000000000", + "<= 4000be693418eee0c522b55f74c62b6ecad9697ef1e6e8bb973ff29740432960db48e000", + "=> f801000146444402000f5ab1bed30ec27c4fdc3ba1136dd48bd338abbc9c8acbe29e350d45137f9a4c4601aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d9", + "<= 40005ab1bed30ec27c4fdc3ba1136dd48bd338abbc9c8acbe29e350d45137f9a4c46e000", + "=> f8010001110f0f73682877706b682840302f2a2a2929", + "<= 41aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90100e000", + "=> f801000122aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90000", + "<= 4000aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d9e000", + "=> f8010001868484005b66356163633266642f3439272f31272f30275d74707562444338373176474c41694b50637741773232456a684b564c6b354c393855475842456347523867706369674c5156444466676359573234514245795448545346456a674a6762614855384364526939766d473463506d316b504c6d5a684a45503137464d42644e68656833", + "<= 41aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90100e000", + "=> f801000122aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90000", + "<= 4000aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d9e000", + "=> f8010001868484005b66356163633266642f3439272f31272f30275d74707562444338373176474c41694b50637741773232456a684b564c6b354c393855475842456347523867706369674c5156444466676359573234514245795448545346456a674a6762614855384364526939766d473463506d316b504c6d5a684a45503137464d42644e68656833", + "<= 41185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50100e000", + "=> f801000122185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50000", + "<= 4000185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d5e000", + "=> f801000144424200075db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c09", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470700e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d29ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470702e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4030322b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626ba20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470703e000", + "=> f80100018222b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626b03034f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400022b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626be000", + "=> f80100012523230006024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090703e000", + "=> f80100018206926cbaf52867d55e7cc3fa229bddde40a669a9db2ff5b030b4d7f1cb6219ef0303d49f480874f2962de229abe9e05b66a3edc62a141206ef314febf28bacfc88f35cff501957d2881fdd31885a4781a443a7642980d82b5b0a0b4a4047646c729bbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 400006926cbaf52867d55e7cc3fa229bddde40a669a9db2ff5b030b4d7f1cb6219efe000", + "=> f80100011b191900f5acc2fd3100008001000080000000800100000000000000", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470704e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905090298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470705e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1960298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470706e000", + "=> f8010001620298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70202e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6ba41b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb479f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001020104", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470704e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905090298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090704e000", + "=> f80100018299c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144030386f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3bb2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98d23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 400099c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144e000", + "=> f80100012321210074f4c9a4da8d148bbb56e869aca3690dda0ce3acbcf81609cc946d1b6f164ace", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb473b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001020105", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470705e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1960298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090705e000", + "=> f80100018286f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b030399c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98d23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 400086f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3be000", + "=> f80100010705050001000000", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb4796a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f8010001020100", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470700e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d29ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090700e000", + "=> f801000182524b62b12a29bb8a1e47760efe6f1d40999d524cffb37a199d3c9714ac6b60b103035e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002e31efa8ceeba29c958140ba4f3b42522d76940d284d4787b95434d7a88b65101fbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 4000524b62b12a29bb8a1e47760efe6f1d40999d524cffb37a199d3c9714ac6b60b1e000", + "=> f80100018f8d8d000200000001d0e3b591dc484edb71eab66a0e994731007feb7d082a50c742f023ea09014d1e0100000017160014e310d044f88dab1b42769e4a84caf08363f9ecc1fdffffff0260ea0000000000001976a91445881ed0d3587550f794847b7c8b9fa03edd0a3c88ac7f661b000000000017a914f0464d9fa0ea42d80e4d5f1457883982e23b8eec8700000000", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090701e000", + "=> f8010001825e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002e0303524b62b12a29bb8a1e47760efe6f1d40999d524cffb37a199d3c9714ac6b60b131efa8ceeba29c958140ba4f3b42522d76940d284d4787b95434d7a88b65101fbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 40005e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002ee000", + "=> f8010001232121007f661b000000000017a914f0464d9fa0ea42d80e4d5f1457883982e23b8eec87", + "<= 41aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90100e000", + "=> f801000122aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90000", + "<= 4000aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d9e000", + "=> f8010001868484005b66356163633266642f3439272f31272f30275d74707562444338373176474c41694b50637741773232456a684b564c6b354c393855475842456347523867706369674c5156444466676359573234514245795448545346456a674a6762614855384364526939766d473463506d316b504c6d5a684a45503137464d42644e68656833", + "<= 41aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90100e000", + "=> f801000122aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90000", + "<= 4000aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d9e000", + "=> f8010001868484005b66356163633266642f3439272f31272f30275d74707562444338373176474c41694b50637741773232456a684b564c6b354c393855475842456347523867706369674c5156444466676359573234514245795448545346456a674a6762614855384364526939766d473463506d316b504c6d5a684a45503137464d42644e68656833", + "<= 41f4c4f92e968760845d5694ce5ac4ad9ed0a33d00278d6e7fe89e807f899d26370200e000", + "=> f8010001425beb3dccbdaafff7bc848930b20471ae36d36930ecde26529a5e8de93b7e995d01017b4ce78ca662f6f0563d93324c1e4f7b48176ba46c599990fd8723ccaf6eccf6", + "<= 40005beb3dccbdaafff7bc848930b20471ae36d36930ecde26529a5e8de93b7e995de000", + "=> f8010001444242000278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8dab87f2b73191a9f783cbb8521fdba8927f4a06e81b7d5d89f46c950a08a5727b", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020100", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 41ab87f2b73191a9f783cbb8521fdba8927f4a06e81b7d5d89f46c950a08a5727b0200e000", + "=> f8010001424a59f387199f104044cf6a49e4ff56d7a879d65f0fb64e6e0d5a27df0610e5ff01013cf62b1b5247aa06ddd823fe7303f7b1a210df8b78844f3490b22a04949180bd", + "<= 40004a59f387199f104044cf6a49e4ff56d7a879d65f0fb64e6e0d5a27df0610e5ffe000", + "=> f80100010b09090050d4120000000000", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 41ab87f2b73191a9f783cbb8521fdba8927f4a06e81b7d5d89f46c950a08a5727b0201e000", + "=> f8010001423cf62b1b5247aa06ddd823fe7303f7b1a210df8b78844f3490b22a04949180bd01014a59f387199f104044cf6a49e4ff56d7a879d65f0fb64e6e0d5a27df0610e5ff", + "<= 40003cf62b1b5247aa06ddd823fe7303f7b1a210df8b78844f3490b22a04949180bde000", + "=> f80100011917170000143318e04fae6c12afcb009c69cd57e5b2504ae6b4", + "<= 41f4c4f92e968760845d5694ce5ac4ad9ed0a33d00278d6e7fe89e807f899d26370201e000", + "=> f8010001427b4ce78ca662f6f0563d93324c1e4f7b48176ba46c599990fd8723ccaf6eccf601015beb3dccbdaafff7bc848930b20471ae36d36930ecde26529a5e8de93b7e995d", + "<= 40007b4ce78ca662f6f0563d93324c1e4f7b48176ba46c599990fd8723ccaf6eccf6e000", + "=> f80100014442420004abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b69e8c704c3a6486b58cfafec8acb9ecc80e298e2b5f18ed79b1150da93ea9c4e", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0400e000", + "=> f80100016296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc702026da3617e3bf0fd3b55939f8a193a7f7c63c7988572084ac793b609d54a34eae978850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0401e000", + "=> f8010001626da3617e3bf0fd3b55939f8a193a7f7c63c7988572084ac793b609d54a34eae9020296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc778850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d", + "<= 40006da3617e3bf0fd3b55939f8a193a7f7c63c7988572084ac793b609d54a34eae9e000", + "=> f80100012523230002038ab11ef46b48b55f00c53efddf38cddff9d6335bcaf52fa9f993847f2ccd2f57", + "<= 4169e8c704c3a6486b58cfafec8acb9ecc80e298e2b5f18ed79b1150da93ea9c4e0401e000", + "=> f8010001625e645f9c8dcce58b4c6cc0e24f68c31472f4c4ba9660890b2453d6b269fae5460202bf75113f4cb8d618da99aea63b2628e422514c2556855381320360614db469393af96250c7ed19c0855fdd4441f0e8c48e7906a79fce0547e184c7784ac90275", + "<= 40005e645f9c8dcce58b4c6cc0e24f68c31472f4c4ba9660890b2453d6b269fae546e000", + "=> f80100011b191900f5acc2fd3100008001000080000000800100000002000000", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0402e000", + "=> f801000162583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d02024f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a407c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0403e000", + "=> f8010001624f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40202583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d07c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 42abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020102", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0402e000", + "=> f801000162583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d02024f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a407c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 4169e8c704c3a6486b58cfafec8acb9ecc80e298e2b5f18ed79b1150da93ea9c4e0402e000", + "=> f801000162683d2e718805c631347e63ebac02d0bde77cfc60360cc4575265944f41dc3e11020223a411f1c8c7e29552ff2e7dbd041743b39ff62bf8ec7c8d992fd74f1287e4d001e34d32abeddcbdc3c292be29e3eb706073be3c159d9051aa26e2ee4205deaa", + "<= 4000683d2e718805c631347e63ebac02d0bde77cfc60360cc4575265944f41dc3e11e000", + "=> f80100010b090900f571080000000000", + "<= 42abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020103", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0403e000", + "=> f8010001624f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40202583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d07c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 4169e8c704c3a6486b58cfafec8acb9ecc80e298e2b5f18ed79b1150da93ea9c4e0403e000", + "=> f80100016223a411f1c8c7e29552ff2e7dbd041743b39ff62bf8ec7c8d992fd74f1287e4d00202683d2e718805c631347e63ebac02d0bde77cfc60360cc4575265944f41dc3e1101e34d32abeddcbdc3c292be29e3eb706073be3c159d9051aa26e2ee4205deaa", + "<= 400023a411f1c8c7e29552ff2e7dbd041743b39ff62bf8ec7c8d992fd74f1287e4d0e000", + "=> f80100011a181800a9146d4852daf3a5409f77216dbb8ea3d592312d7ee987", + "<= 41aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90100e000", + "=> f801000122aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90000", + "<= 4000aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d9e000", + "=> f8010001868484005b66356163633266642f3439272f31272f30275d74707562444338373176474c41694b50637741773232456a684b564c6b354c393855475842456347523867706369674c5156444466676359573234514245795448545346456a674a6762614855384364526939766d473463506d316b504c6d5a684a45503137464d42644e68656833", + "<= 41aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90100e000", + "=> f801000122aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d90000", + "<= 4000aa8087e303482fa846597853314be4e1ed3096d7efadf1b257dd324d70dd13d9e000", + "=> f8010001868484005b66356163633266642f3439272f31272f30275d74707562444338373176474c41694b50637741773232456a684b564c6b354c393855475842456347523867706369674c5156444466676359573234514245795448545346456a674a6762614855384364526939766d473463506d316b504c6d5a684a45503137464d42644e68656833", + "<= 41185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50100e000", + "=> f801000122185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50000", + "<= 4000185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d5e000", + "=> f801000144424200075db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c09", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470700e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d29ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470702e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4030322b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626ba20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470703e000", + "=> f80100018222b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626b03034f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400022b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626be000", + "=> f80100012523230006024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090703e000", + "=> f80100018206926cbaf52867d55e7cc3fa229bddde40a669a9db2ff5b030b4d7f1cb6219ef0303d49f480874f2962de229abe9e05b66a3edc62a141206ef314febf28bacfc88f35cff501957d2881fdd31885a4781a443a7642980d82b5b0a0b4a4047646c729bbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 400006926cbaf52867d55e7cc3fa229bddde40a669a9db2ff5b030b4d7f1cb6219efe000", + "=> f80100011b191900f5acc2fd3100008001000080000000800100000000000000", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470704e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905090298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470705e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1960298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470706e000", + "=> f8010001620298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70202e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6ba41b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090701e000", + "=> f8010001825e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002e0303524b62b12a29bb8a1e47760efe6f1d40999d524cffb37a199d3c9714ac6b60b131efa8ceeba29c958140ba4f3b42522d76940d284d4787b95434d7a88b65101fbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 40005e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002ee000", + "=> f8010001232121007f661b000000000017a914f0464d9fa0ea42d80e4d5f1457883982e23b8eec87", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb474f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020102", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470702e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4030322b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626ba20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090702e000", + "=> f801000182d49f480874f2962de229abe9e05b66a3edc62a141206ef314febf28bacfc88f3030306926cbaf52867d55e7cc3fa229bddde40a669a9db2ff5b030b4d7f1cb6219ef5cff501957d2881fdd31885a4781a443a7642980d82b5b0a0b4a4047646c729bbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 4000d49f480874f2962de229abe9e05b66a3edc62a141206ef314febf28bacfc88f3e000", + "=> f8010001191717000014cb078087eff485aaa2260e94a53d7d6d1c5dd151", + "<= 41185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50100e000", + "=> f801000122185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50000", + "<= 4000185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d5e000", + "=> f801000144424200075db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c09", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470700e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d29ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470702e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4030322b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626ba20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470703e000", + "=> f80100018222b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626b03034f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400022b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626be000", + "=> f80100012523230006024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470704e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905090298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470705e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1960298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470706e000", + "=> f8010001620298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70202e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6ba41b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb479f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001020104", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470704e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905090298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090704e000", + "=> f80100018299c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144030386f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3bb2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98d23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 400099c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144e000", + "=> f80100012321210074f4c9a4da8d148bbb56e869aca3690dda0ce3acbcf81609cc946d1b6f164ace", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb473b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001020105", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470705e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1960298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090705e000", + "=> f80100018286f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b030399c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98d23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 400086f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3be000", + "=> f80100010705050001000000", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f8010001020106", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470706e000", + "=> f8010001620298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70202e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6ba41b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090706e000", + "=> f801000162b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d980202fc7d8483180a78b82fb5a4f706aa6857cfb0e54535424bdaea858144e99e2e3cd23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 4000b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98e000", + "=> f801000107050500fdffffff", + "<= 41f4c4f92e968760845d5694ce5ac4ad9ed0a33d00278d6e7fe89e807f899d26370200e000", + "=> f8010001425beb3dccbdaafff7bc848930b20471ae36d36930ecde26529a5e8de93b7e995d01017b4ce78ca662f6f0563d93324c1e4f7b48176ba46c599990fd8723ccaf6eccf6", + "<= 40005beb3dccbdaafff7bc848930b20471ae36d36930ecde26529a5e8de93b7e995de000", + "=> f8010001444242000278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8dab87f2b73191a9f783cbb8521fdba8927f4a06e81b7d5d89f46c950a08a5727b", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020100", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 41ab87f2b73191a9f783cbb8521fdba8927f4a06e81b7d5d89f46c950a08a5727b0200e000", + "=> f8010001424a59f387199f104044cf6a49e4ff56d7a879d65f0fb64e6e0d5a27df0610e5ff01013cf62b1b5247aa06ddd823fe7303f7b1a210df8b78844f3490b22a04949180bd", + "<= 40004a59f387199f104044cf6a49e4ff56d7a879d65f0fb64e6e0d5a27df0610e5ffe000", + "=> f80100010b09090050d4120000000000", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 41ab87f2b73191a9f783cbb8521fdba8927f4a06e81b7d5d89f46c950a08a5727b0201e000", + "=> f8010001423cf62b1b5247aa06ddd823fe7303f7b1a210df8b78844f3490b22a04949180bd01014a59f387199f104044cf6a49e4ff56d7a879d65f0fb64e6e0d5a27df0610e5ff", + "<= 40003cf62b1b5247aa06ddd823fe7303f7b1a210df8b78844f3490b22a04949180bde000", + "=> f80100011917170000143318e04fae6c12afcb009c69cd57e5b2504ae6b4", + "<= 41f4c4f92e968760845d5694ce5ac4ad9ed0a33d00278d6e7fe89e807f899d26370201e000", + "=> f8010001427b4ce78ca662f6f0563d93324c1e4f7b48176ba46c599990fd8723ccaf6eccf601015beb3dccbdaafff7bc848930b20471ae36d36930ecde26529a5e8de93b7e995d", + "<= 40007b4ce78ca662f6f0563d93324c1e4f7b48176ba46c599990fd8723ccaf6eccf6e000", + "=> f80100014442420004abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b69e8c704c3a6486b58cfafec8acb9ecc80e298e2b5f18ed79b1150da93ea9c4e", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0400e000", + "=> f80100016296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc702026da3617e3bf0fd3b55939f8a193a7f7c63c7988572084ac793b609d54a34eae978850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0401e000", + "=> f8010001626da3617e3bf0fd3b55939f8a193a7f7c63c7988572084ac793b609d54a34eae9020296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc778850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d", + "<= 40006da3617e3bf0fd3b55939f8a193a7f7c63c7988572084ac793b609d54a34eae9e000", + "=> f80100012523230002038ab11ef46b48b55f00c53efddf38cddff9d6335bcaf52fa9f993847f2ccd2f57", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0402e000", + "=> f801000162583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d02024f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a407c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0403e000", + "=> f8010001624f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40202583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d07c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 42abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020102", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0402e000", + "=> f801000162583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d02024f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a407c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 4169e8c704c3a6486b58cfafec8acb9ecc80e298e2b5f18ed79b1150da93ea9c4e0402e000", + "=> f801000162683d2e718805c631347e63ebac02d0bde77cfc60360cc4575265944f41dc3e11020223a411f1c8c7e29552ff2e7dbd041743b39ff62bf8ec7c8d992fd74f1287e4d001e34d32abeddcbdc3c292be29e3eb706073be3c159d9051aa26e2ee4205deaa", + "<= 4000683d2e718805c631347e63ebac02d0bde77cfc60360cc4575265944f41dc3e11e000", + "=> f80100010b090900f571080000000000", + "<= 42abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020103", + "<= 41abca6f64bf88995f2028feaf1c91dc5e4e2b6cd28b1c13727e306f394d94a31b0403e000", + "=> f8010001624f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40202583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d07c7ee5b5dc5d2ad2e00e58ad09f0eb5645f3682a2a64885d727b7d78c27ec80", + "<= 4169e8c704c3a6486b58cfafec8acb9ecc80e298e2b5f18ed79b1150da93ea9c4e0403e000", + "=> f80100016223a411f1c8c7e29552ff2e7dbd041743b39ff62bf8ec7c8d992fd74f1287e4d00202683d2e718805c631347e63ebac02d0bde77cfc60360cc4575265944f41dc3e1101e34d32abeddcbdc3c292be29e3eb706073be3c159d9051aa26e2ee4205deaa", + "<= 400023a411f1c8c7e29552ff2e7dbd041743b39ff62bf8ec7c8d992fd74f1287e4d0e000", + "=> f80100011a181800a9146d4852daf3a5409f77216dbb8ea3d592312d7ee987", + "<= 41185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50100e000", + "=> f801000122185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d50000", + "<= 4000185a2fac562419c1ce8ed936d13cfe9ca4be0bec1a0d84f8ce433763fc6d41d5e000", + "=> f801000144424200075db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c09", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470700e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d29ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470702e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4030322b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626ba20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470703e000", + "=> f80100018222b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626b03034f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 400022b60d1dd304e8034bc9b59d9cde3d3ecb2a33e8eab07c2d4219e38bd35e626be000", + "=> f80100012523230006024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470704e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905090298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470705e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1960298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470706e000", + "=> f8010001620298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70202e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6ba41b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090701e000", + "=> f8010001825e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002e0303524b62b12a29bb8a1e47760efe6f1d40999d524cffb37a199d3c9714ac6b60b131efa8ceeba29c958140ba4f3b42522d76940d284d4787b95434d7a88b65101fbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 40005e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002ee000", + "=> f8010001232121007f661b000000000017a914f0464d9fa0ea42d80e4d5f1457883982e23b8eec87", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb479f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001020104", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470704e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905090298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090704e000", + "=> f80100018299c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144030386f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3bb2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98d23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 400099c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144e000", + "=> f80100012321210074f4c9a4da8d148bbb56e869aca3690dda0ce3acbcf81609cc946d1b6f164ace", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb473b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001020105", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470705e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1960298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe741b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090705e000", + "=> f80100018286f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b030399c5a9d616bcef076acd13f00af6f8a92dda8dda4b7b52400f76e841b502b144b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98d23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 400086f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3be000", + "=> f80100010705050001000000", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb47b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470701e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc79ec7e94594c19f8df3adf81222ed71ec0249dbf196079fb25e3ee4086122c081556f87d6bab12cfa0bcc37ae4314c85806da36666703a8d4c14ab6dc753f2744", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090701e000", + "=> f8010001825e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002e0303524b62b12a29bb8a1e47760efe6f1d40999d524cffb37a199d3c9714ac6b60b131efa8ceeba29c958140ba4f3b42522d76940d284d4787b95434d7a88b65101fbdb89efd14836c2acfc10d1e0e3ad12bbc390f3ed61965e3cc82eeb85e0eeacb", + "<= 40005e638757b66f5ff0846e56011c1386362df1e7371ba2f140616dcf0da6ef002ee000", + "=> f8010001232121007f661b000000000017a914f0464d9fa0ea42d80e4d5f1457883982e23b8eec87", + "<= 425db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f8010001020106", + "<= 415db13d2321099314884a7e75d2e7d226489fd8e4db5afbd695b92ae31e66eb470706e000", + "=> f8010001620298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70202e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6ba41b9294b7a661990a19adec4b47beafc01622c906df7d4d71be96671ed33a927", + "<= 41f55bba17c04a1932f489a21cfebc3cec358b6f0a36eb1bef0781a2079b975c090706e000", + "=> f801000162b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d980202fc7d8483180a78b82fb5a4f706aa6857cfb0e54535424bdaea858144e99e2e3cd23fa41668b8601a8d5886f931e7d68dd5812cfc4f77cb8a516712c6d84b72ca", + "<= 4000b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98e000", + "=> f801000107050500fdffffff", + "<= 100021024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f6730440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201e000", + "=> f801000100", + "<= 9000" + ], + "sigs": [] + }, + { + "name": "Liana", + "policy": "wsh(or_d(pk(@0/<0;1>/*),and_v(v:pkh(@1/<0;1>/*),older(10))))", + "keys": [ + "[f5acc2fd]tpubD6NzVbkrYhZ4YgUx2ZLNt2rLYAMTdYysCRzKoLu2BeSHKvzqPaBDvf17GeBPnExUVPkuBpx4kniP964e2MxyzzazcXLptxLXModSVCVEV1T", + "[8a64f2a9]tpubD6NzVbkrYhZ4WmzFjvQrp7sDa4ECUxTi9oby8K4FZkd3XCBtEdKwUiQyYJaxiJo5y42gyDWEczrFpozEjeLxMPxjf2WtkfcbpUdfvNnozWF" + ], + "hmac": "c8a3e4599fb45c3f0bb19cc0527082a0f7260c3e2d9572b89b9e56c015d46571", + "psbt": "cHNidP8BAFICAAAAAUSHuliRtuCX1S6JxRuDRqDCKkWfKmWL5sV9ukZ/wzvfAAAAAAD9////AYgTAAAAAAAAFgAUqo7zdMr638p2kC3bXPYcYLv9nYUAAAAAAAEA/aABAgAAAAABAtAGpuGhTmK0vRJDepa1mynHf0Mkr131IPWntT5688UGAQAAAAD9////mdJfANQE0DT+aJkb7HxEgMXaiQJMG0N2rZ7W8uPNV/IAAAAAAP3///8BECcAAAAAAAAiACCCMkkIxgk3ssf6ylEMfKqG44SXjQolQpYgST3ckV+lygJHMEQCIAwbhvKJJaR0VD9kllZaKuigpIYHCEQLSRM/1MCbQhGtAiBWQnmyk4cbQhTicn19PLftc4LYOimEPsrTuph38fT23QFBIQN45pCxJ5gMJnUJ4+QLYEmWt0cOKESWnlj2y8I31G2X0axzZHapFIxY4f1q3kD+aerUUfBNBOKnkGojiK1asmgCSDBFAiEApwLr/1lmyJXSDrC3rWaHOGl5ls2dU4YoEmLTxKhibVgCIGhtnjpEuQFA7QzhoHCZWz+apKMSnCBS7dW1znAdQjTgAUEhA/bp4cSva5sUDoPHbdrl6Zj1MNMEqaXBgIfjiPRXWHxurHNkdqkUyXCPVy2ZM1C6QosNzsZs5dtd1AGIrVqyaAAAAAABASsQJwAAAAAAACIAIIIySQjGCTeyx/rKUQx8qobjhJeNCiVCliBJPdyRX6XKAQVBIQI2cqWpc9UAW2gZt2WkKjvi8KoMCui00pRlL6wG32uKDKxzZHapFNYASzIYkEdH9bJz6nnqUG3uBB8kiK1asmgiBgI2cqWpc9UAW2gZt2WkKjvi8KoMCui00pRlL6wG32uKDAz1rML9AAAAAG8AAAAiBgMLcbOxsfLe6+3r1UcjQo77HY0As8OKE4l37yj0/qhIyQyKZPKpAAAAAG8AAAAAAA==", + "exchanges": [ + "=> e1040001c305519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216206d0d3e783926a53f1a696f04944e03bc43440cf47684d9a959e98d2add8510f101037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a018b5def765f486d77c85fbc5d730caedebc856a58a1cdfc3dad112d7e0690fdf1dbe3eabdd4ddf8480934d54c293003eb1da52339ac441b6439eea4cbce0fe337c8a3e4599fb45c3f0bb19cc0527082a0f7260c3e2d9572b89b9e56c015d46571", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200500e000", + "=> f801000182fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f0303583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4000fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177fe000", + "=> f80100010402020002", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200501e000", + "=> f801000182583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0303fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200502e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a403039f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d52c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed4679782595811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200503e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d03034f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a452c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed4679782595811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 40009f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f80100010402020005", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200504e000", + "=> f80100014295811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926010112885c5025dece82b9e180bdaf19d6e5571772906c9c24de31790023755c8888", + "<= 400095811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926e000", + "=> f801000104020200fb", + "<= 42519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e31321620fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177fe000", + "=> f8010001020100", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200500e000", + "=> f801000182fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f0303583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 416d0d3e783926a53f1a696f04944e03bc43440cf47684d9a959e98d2add8510f10500e000", + "=> f8010001820bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b903038855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4c178813b8617f884a8135fd9f95e1a4596188ab7705a3356480c01c0f977db930bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9", + "<= 40000bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9e000", + "=> f80100010705050002000000", + "<= 42519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e31321620583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020101", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200501e000", + "=> f801000182583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0303fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 416d0d3e783926a53f1a696f04944e03bc43440cf47684d9a959e98d2add8510f10501e000", + "=> f8010001828855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a403030bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9c178813b8617f884a8135fd9f95e1a4596188ab7705a3356480c01c0f977db930bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9", + "<= 40008855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4e000", + "=> f80100010705050000000000", + "<= 4000dbe3eabdd4ddf8480934d54c293003eb1da52339ac441b6439eea4cbce0fe337e000", + "=> f80100014b494902054c69616e613c23cb3aec15a5b15abd88ae00c265e39047a899deadba2c181dd48712f2ccc1c902445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd8", + "<= 400023cb3aec15a5b15abd88ae00c265e39047a899deadba2c181dd48712f2ccc1c9e000", + "=> f80100013e3c3c777368286f725f6428706b2840302f3c303b313e2f2a292c616e645f7628763a706b682840312f3c303b313e2f2a292c6f6c64657228313029292929", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80200e000", + "=> f801000142ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf010160dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224", + "<= 4000ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbfe000", + "=> f80100017c7a7a005b66356163633266645d7470756244364e7a56626b7259685a3459675578325a4c4e7432724c59414d546459797343527a4b6f4c7532426553484b767a715061424476663137476542506e45785556506b75427078346b6e695039363465324d78797a7a617a63584c7074784c584d6f645356435645563154", + "<= 41037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0100e000", + "=> f801000122037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0000", + "<= 4000037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436ae000", + "=> f80100014442420008c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0800e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0802e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d0303ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 40009f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f80100010402020005", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0803e000", + "=> f801000182ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a324803039f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3da20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248e000", + "=> f80100012523230006023672a5a973d5005b6819b765a42a3be2f0aa0c0ae8b4d294652fac06df6b8a0c", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0803e000", + "=> f8010001827f3adc432d3c39917e976e58ec485e527f289d392ab33b31916764f36c9d894303035d268f3545bd312a3c5a295201ef2aa703e369de7d86c803fbb0e109466d6dffc614a95f1ac1b9df12642b5a63f6fe46b24f698279d31b7fdc0932cd659249481f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40007f3adc432d3c39917e976e58ec485e527f289d392ab33b31916764f36c9d8943e000", + "=> f80100010f0d0d00f5acc2fd000000006f000000", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0804e000", + "=> f8010001823ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5b03039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196d19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5be000", + "=> f80100012523230006030b71b3b1b1f2deebedebd54723428efb1d8d00b3c38a138977ef28f4fea848c9", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0805e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5bd19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0806e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903030298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe79c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0807e000", + "=> f8010001820298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe703033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905099c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c9f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001020105", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0805e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5bd19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0805e000", + "=> f8010001821818880692dd7fb3356ce5d278aa10156a9596c5f8fcac63b2798676bff307560303d36539a2ab83df7adf832fe88afc0b1ce50963c75a0824e077c5375d64db69fc323555107b1388d25445e79ff3d0806e8c9e9eb4c03589a10b492778a9883aa7e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 40001818880692dd7fb3356ce5d278aa10156a9596c5f8fcac63b2798676bff30756e000", + "=> f8010001232121004487ba5891b6e097d52e89c51b8346a0c22a459f2a658be6c57dba467fc33bdf", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c3b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001020106", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0806e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903030298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe79c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0806e000", + "=> f8010001828855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a40303b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98183fe9cedb724d22f9bee015c3a71c631d2f3891c802b8131aab4749ead59a05e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 40008855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4e000", + "=> f80100010705050000000000", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f8010001020100", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0800e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0800e000", + "=> f8010001821c987ce5ebb77d2f6b3ccbb175ccc7fffebaae9f1c4d84665698fec4974223e303036bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099bd8c104da07e82df9279b23059d05c947b3848bc31d70d2196aaeaa6f098c6bd31f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40001c987ce5ebb77d2f6b3ccbb175ccc7fffebaae9f1c4d84665698fec4974223e3e000", + "=> f8010001fffda101fb0002000000000102d006a6e1a14e62b4bd12437a96b59b29c77f4324af5df520f5a7b53e7af3c5060100000000fdffffff99d25f00d404d034fe68991bec7c4480c5da89024c1b4376ad9ed6f2e3cd57f20000000000fdffffff01102700000000000022002082324908c60937b2c7faca510c7caa86e384978d0a25429620493ddc915fa5ca0247304402200c1b86f28925a474543f6496565a2ae8a0a4860708440b49133fd4c09b4211ad0220564279b293871b4214e2727d7d3cb7ed7382d83a29843ecad3ba9877f1f4f6dd0141210378e690b127980c267509e3e40b604996b7470e2844969e58f6cbc237d46d97d1ac736476a9148c58e1", + "<= a0e000", + "=> f8010001a8a601fd6ade40fe69ead451f04d04e2a7906a2388ad5ab26802483045022100a702ebff5966c895d20eb0b7ad668738697996cd9d5386281262d3c4a8626d580220686d9e3a44b90140ed0ce1a070995b3f9aa4a3129c2052edd5b5ce701d4234e001412103f6e9e1c4af6b9b140e83c76ddae5e998f530d304a9a5c18087e388f457587c6eac736476a914c9708f572d993350ba428b0dcec66ce5db5dd40188ad5ab26800000000", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2cb413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0801e000", + "=> f8010001826bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099b03031c987ce5ebb77d2f6b3ccbb175ccc7fffebaae9f1c4d84665698fec4974223e3d8c104da07e82df9279b23059d05c947b3848bc31d70d2196aaeaa6f098c6bd31f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40006bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099be000", + "=> f80100012e2c2c00102700000000000022002082324908c60937b2c7faca510c7caa86e384978d0a25429620493ddc915fa5ca", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80200e000", + "=> f801000142ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf010160dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224", + "<= 4000ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbfe000", + "=> f80100017c7a7a005b66356163633266645d7470756244364e7a56626b7259685a3459675578325a4c4e7432724c59414d546459797343527a4b6f4c7532426553484b767a715061424476663137476542506e45785556506b75427078346b6e695039363465324d78797a7a617a63584c7074784c584d6f645356435645563154", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80201e000", + "=> f80100014260dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa432240101ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf", + "<= 400060dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224e000", + "=> f80100017c7a7a005b38613634663261395d7470756244364e7a56626b7259685a34576d7a466a765172703773446134454355785469396f6279384b34465a6b64335843427445644b7755695179594a6178694a6f357934326779445745637a7246706f7a456a654c784d50786a663257746b66636270556466764e6e6f7a5746", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80200e000", + "=> f801000142ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf010160dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224", + "<= 4000ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbfe000", + "=> f80100017c7a7a005b66356163633266645d7470756244364e7a56626b7259685a3459675578325a4c4e7432724c59414d546459797343527a4b6f4c7532426553484b767a715061424476663137476542506e45785556506b75427078346b6e695039363465324d78797a7a617a63584c7074784c584d6f645356435645563154", + "<= 418b5def765f486d77c85fbc5d730caedebc856a58a1cdfc3dad112d7e0690fdf10100e000", + "=> f8010001228b5def765f486d77c85fbc5d730caedebc856a58a1cdfc3dad112d7e0690fdf10000", + "<= 40008b5def765f486d77c85fbc5d730caedebc856a58a1cdfc3dad112d7e0690fdf1e000", + "=> f8010001444242000278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d344c0e5eab7ed16a6c5d495911399831475d62c728ad1de4bc119a5fe349c1d9", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020100", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 41344c0e5eab7ed16a6c5d495911399831475d62c728ad1de4bc119a5fe349c1d90200e000", + "=> f8010001421518b41d0f3a080793b8822a573ad4b8628027d88c1c3edab2162f6202742f910101f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e", + "<= 40001518b41d0f3a080793b8822a573ad4b8628027d88c1c3edab2162f6202742f91e000", + "=> f80100010b0909008813000000000000", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 41344c0e5eab7ed16a6c5d495911399831475d62c728ad1de4bc119a5fe349c1d90201e000", + "=> f801000142f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e01011518b41d0f3a080793b8822a573ad4b8628027d88c1c3edab2162f6202742f91", + "<= 4000f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364ee000", + "=> f8010001191717000014aa8ef374cafadfca76902ddb5cf61c60bbfd9d85", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80200e000", + "=> f801000142ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf010160dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224", + "<= 4000ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbfe000", + "=> f80100017c7a7a005b66356163633266645d7470756244364e7a56626b7259685a3459675578325a4c4e7432724c59414d546459797343527a4b6f4c7532426553484b767a715061424476663137476542506e45785556506b75427078346b6e695039363465324d78797a7a617a63584c7074784c584d6f645356435645563154", + "<= 41037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0100e000", + "=> f801000122037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0000", + "<= 4000037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436ae000", + "=> f80100014442420008c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0800e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0802e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d0303ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 40009f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f80100010402020005", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0803e000", + "=> f801000182ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a324803039f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3da20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248e000", + "=> f80100012523230006023672a5a973d5005b6819b765a42a3be2f0aa0c0ae8b4d294652fac06df6b8a0c", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0803e000", + "=> f8010001827f3adc432d3c39917e976e58ec485e527f289d392ab33b31916764f36c9d894303035d268f3545bd312a3c5a295201ef2aa703e369de7d86c803fbb0e109466d6dffc614a95f1ac1b9df12642b5a63f6fe46b24f698279d31b7fdc0932cd659249481f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40007f3adc432d3c39917e976e58ec485e527f289d392ab33b31916764f36c9d8943e000", + "=> f80100010f0d0d00f5acc2fd000000006f000000", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0804e000", + "=> f8010001823ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5b03039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196d19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5be000", + "=> f80100012523230006030b71b3b1b1f2deebedebd54723428efb1d8d00b3c38a138977ef28f4fea848c9", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0805e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5bd19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0806e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903030298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe79c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0807e000", + "=> f8010001820298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe703033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905099c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2cb413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0801e000", + "=> f8010001826bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099b03031c987ce5ebb77d2f6b3ccbb175ccc7fffebaae9f1c4d84665698fec4974223e3d8c104da07e82df9279b23059d05c947b3848bc31d70d2196aaeaa6f098c6bd31f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40006bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099be000", + "=> f80100012e2c2c00102700000000000022002082324908c60937b2c7faca510c7caa86e384978d0a25429620493ddc915fa5ca", + "<= 41037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0100e000", + "=> f801000122037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0000", + "<= 4000037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436ae000", + "=> f80100014442420008c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0800e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0802e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d0303ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 40009f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f80100010402020005", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0803e000", + "=> f801000182ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a324803039f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3da20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248e000", + "=> f80100012523230006023672a5a973d5005b6819b765a42a3be2f0aa0c0ae8b4d294652fac06df6b8a0c", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0804e000", + "=> f8010001823ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5b03039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196d19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5be000", + "=> f80100012523230006030b71b3b1b1f2deebedebd54723428efb1d8d00b3c38a138977ef28f4fea848c9", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0805e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5bd19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0806e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903030298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe79c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0807e000", + "=> f8010001820298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe703033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905099c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c9f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001020105", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0805e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5bd19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0805e000", + "=> f8010001821818880692dd7fb3356ce5d278aa10156a9596c5f8fcac63b2798676bff307560303d36539a2ab83df7adf832fe88afc0b1ce50963c75a0824e077c5375d64db69fc323555107b1388d25445e79ff3d0806e8c9e9eb4c03589a10b492778a9883aa7e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 40001818880692dd7fb3356ce5d278aa10156a9596c5f8fcac63b2798676bff30756e000", + "=> f8010001232121004487ba5891b6e097d52e89c51b8346a0c22a459f2a658be6c57dba467fc33bdf", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c3b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001020106", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0806e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903030298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe79c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0806e000", + "=> f8010001828855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a40303b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98183fe9cedb724d22f9bee015c3a71c631d2f3891c802b8131aab4749ead59a05e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 40008855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4e000", + "=> f80100010705050000000000", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f8010001020107", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0807e000", + "=> f8010001820298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe703033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905099c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0807e000", + "=> f801000182b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d9803038855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4183fe9cedb724d22f9bee015c3a71c631d2f3891c802b8131aab4749ead59a05e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 4000b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98e000", + "=> f801000107050500fdffffff", + "<= 418b5def765f486d77c85fbc5d730caedebc856a58a1cdfc3dad112d7e0690fdf10100e000", + "=> f8010001228b5def765f486d77c85fbc5d730caedebc856a58a1cdfc3dad112d7e0690fdf10000", + "<= 40008b5def765f486d77c85fbc5d730caedebc856a58a1cdfc3dad112d7e0690fdf1e000", + "=> f8010001444242000278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d344c0e5eab7ed16a6c5d495911399831475d62c728ad1de4bc119a5fe349c1d9", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020100", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 41344c0e5eab7ed16a6c5d495911399831475d62c728ad1de4bc119a5fe349c1d90200e000", + "=> f8010001421518b41d0f3a080793b8822a573ad4b8628027d88c1c3edab2162f6202742f910101f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e", + "<= 40001518b41d0f3a080793b8822a573ad4b8628027d88c1c3edab2162f6202742f91e000", + "=> f80100010b0909008813000000000000", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 41344c0e5eab7ed16a6c5d495911399831475d62c728ad1de4bc119a5fe349c1d90201e000", + "=> f801000142f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e01011518b41d0f3a080793b8822a573ad4b8628027d88c1c3edab2162f6202742f91", + "<= 4000f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364ee000", + "=> f8010001191717000014aa8ef374cafadfca76902ddb5cf61c60bbfd9d85", + "<= 41037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0100e000", + "=> f801000122037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436a0000", + "<= 4000037c228f5f56f52fe83ea1c3b7572dd63822f29d9d833c0f98ebed414933436ae000", + "=> f80100014442420008c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0800e000", + "=> f80100018296a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc70303b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 400096a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e000", + "=> f80100010402020000", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0802e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d0303ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 40009f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f80100010402020005", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0803e000", + "=> f801000182ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a324803039f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3da20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 4000ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248e000", + "=> f80100012523230006023672a5a973d5005b6819b765a42a3be2f0aa0c0ae8b4d294652fac06df6b8a0c", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0804e000", + "=> f8010001823ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5b03039f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196d19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5be000", + "=> f80100012523230006030b71b3b1b1f2deebedebd54723428efb1d8d00b3c38a138977ef28f4fea848c9", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0805e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5bd19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0806e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903030298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe79c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0807e000", + "=> f8010001820298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe703033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905099c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2cb413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0801e000", + "=> f8010001826bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099b03031c987ce5ebb77d2f6b3ccbb175ccc7fffebaae9f1c4d84665698fec4974223e3d8c104da07e82df9279b23059d05c947b3848bc31d70d2196aaeaa6f098c6bd31f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40006bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099be000", + "=> f80100012e2c2c00102700000000000022002082324908c60937b2c7faca510c7caa86e384978d0a25429620493ddc915fa5ca", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c9f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001020105", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0805e000", + "=> f8010001829f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19603033ab22b6a812def10b050a160971b6cf3b65b527d14907ff06719ed801080ff5bd19379c3b959d2f633568f4389726312ac69bd8e3783436175c33216b545ff0130265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0805e000", + "=> f8010001821818880692dd7fb3356ce5d278aa10156a9596c5f8fcac63b2798676bff307560303d36539a2ab83df7adf832fe88afc0b1ce50963c75a0824e077c5375d64db69fc323555107b1388d25445e79ff3d0806e8c9e9eb4c03589a10b492778a9883aa7e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 40001818880692dd7fb3356ce5d278aa10156a9596c5f8fcac63b2798676bff30756e000", + "=> f8010001232121004487ba5891b6e097d52e89c51b8346a0c22a459f2a658be6c57dba467fc33bdf", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c3b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001020106", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0806e000", + "=> f8010001823b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050903030298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe79c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0806e000", + "=> f8010001828855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a40303b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98183fe9cedb724d22f9bee015c3a71c631d2f3891c802b8131aab4749ead59a05e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 40008855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4e000", + "=> f80100010705050000000000", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c9f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f8010001020102", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0802e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d0303ee2630dfb041c6cd08b691fb53e9ebe70b5ba7c2505395b976580b295c0a3248a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0802e000", + "=> f8010001825d268f3545bd312a3c5a295201ef2aa703e369de7d86c803fbb0e109466d6dff03037f3adc432d3c39917e976e58ec485e527f289d392ab33b31916764f36c9d8943c614a95f1ac1b9df12642b5a63f6fe46b24f698279d31b7fdc0932cd659249481f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40005d268f3545bd312a3c5a295201ef2aa703e369de7d86c803fbb0e109466d6dffe000", + "=> f80100014442420021023672a5a973d5005b6819b765a42a3be2f0aa0c0ae8b4d294652fac06df6b8a0cac736476a914d6004b3218904747f5b273ea79ea506dee041f2488ad5ab268", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2cb413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020101", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0801e000", + "=> f801000182b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2030396a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7e32ce487982439e86bb2e85fbf23a23e57e9e9c035b48ab36503db94bf7f2c95937c22af41cd594d19a02a1aae0a467884cb6fbf58f018ea7021ed1f34254774", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0801e000", + "=> f8010001826bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099b03031c987ce5ebb77d2f6b3ccbb175ccc7fffebaae9f1c4d84665698fec4974223e3d8c104da07e82df9279b23059d05c947b3848bc31d70d2196aaeaa6f098c6bd31f9328faa79270ddd8f0fea6ea43743b0a7684896a10b6f5b406ae6d2f02ab7e", + "<= 40006bdf666fb71bcaff0b8ea4589bbc640ed9deafa80d73053f5a6483ae45bb099be000", + "=> f80100012e2c2c00102700000000000022002082324908c60937b2c7faca510c7caa86e384978d0a25429620493ddc915fa5ca", + "<= 42c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f8010001020107", + "<= 41c64ee17e0417d2ed804e1d54e706a49b507f1ee9e3919f137c885bcb89d71f2c0807e000", + "=> f8010001820298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe703033b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905099c91404e1a62af83954606c76656027170bb2435beb199254669e98b6a6fa2eb30265cc21422ccfa903efa759135ab5f6c609b3dd1a5a0e486e39c19f46c7cc6", + "<= 41534a627f1af4b7364354aed3f80c68ca92c8556f167b5ac3ed22491e8b476f1f0807e000", + "=> f801000182b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d9803038855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4183fe9cedb724d22f9bee015c3a71c631d2f3891c802b8131aab4749ead59a05e893093cdbdae78c6b639d9e8cc0dc926ec3387eb7742d459fbfb72d141f9738", + "<= 4000b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98e000", + "=> f801000107050500fdffffff", + "<= 100021023672a5a973d5005b6819b765a42a3be2f0aa0c0ae8b4d294652fac06df6b8a0c304402201292a1d5c1ae3211e4c365553a187175fe01e82da5c67d8ab6681068241f57e60220429db8adf3b0c560c4ef5bb635521d58807f112080523136743e22fe7ec075c801e000", + "=> f801000100", + "<= 41445ba5a280b8491a67992b3d7a722dbe6e89cda68112dd72cb1816fd70f1cfd80201e000", + "=> f80100014260dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa432240101ad123cb4a90beb3e9b9e3eb811b9119a8f0b2c011f9833d86aadaec5c4937fbf", + "<= 400060dcf3ace170d59f287f4047d6d7ac279e2bd16f6a64a06424bebdf9dfa43224e000", + "=> f80100017c7a7a005b38613634663261395d7470756244364e7a56626b7259685a34576d7a466a765172703773446134454355785469396f6279384b34465a6b64335843427445644b7755695179594a6178694a6f357934326779445745637a7246706f7a456a654c784d50786a663257746b66636270556466764e6e6f7a5746", + "<= 9000" + ], + "sigs": [] + }, + { + "name": "Taproot foreign internal key, and our script key", + "policy": "tr(@0/**,pk(@1/**))", + "keys": [ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK" + ], + "hmac": "dae925660e20859ed8833025d46444483ce264fdb77e34569aabe9d590da8fb7", + "psbt": "cHNidP8BAFICAAAAAR/BzFdxy4OGDMVtlLz+2ThgjBf2NmJDW0HpxE/8/TFCAQAAAAD9////ATkFAAAAAAAAFgAUqo7zdMr638p2kC3bXPYcYLv9nYUAAAAAAAEBK0wGAAAAAAAAIlEg/AoQ0wjH5BtLvDZC+P2KwomFOxznVaDG0NSV8D2fLaQBAwQBAAAAIhXBUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUjIGsW6MH5efpMwPBbajAK//+UFFm28g3nfeVbAWDvjkysrMAhFlAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1HQB2IjpuMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIRZrFujB+Xn6TMDwW2owCv//lBRZtvIN533lWwFg745MrD0BCS7aAzYX4hDuf30ON4pASuocSLVqoQMCK+z3dG5HAKT1rML9MAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAARcgUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUBGCAJLtoDNhfiEO5/fQ43ikBK6hxItWqhAwIr7Pd0bkcApAAA", + "exchanges": [ + "=> e1040001c305519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216206d0d3e783926a53f1a696f04944e03bc43440cf47684d9a959e98d2add8510f10152a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b01f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141fba1a89c470ab0c36c38beaebb577b754c33d90b51df53977e2b01018fbe25f33dae925660e20859ed8833025d46444483ce264fdb77e34569aabe9d590da8fb7", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200500e000", + "=> f801000182fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f0303583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4000fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177fe000", + "=> f80100010402020002", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200501e000", + "=> f801000182583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0303fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200502e000", + "=> f8010001824f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a403039f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d52c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed4679782595811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200503e000", + "=> f8010001829f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d03034f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a452c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed4679782595811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 40009f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3de000", + "=> f80100010402020005", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200504e000", + "=> f80100014295811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926010112885c5025dece82b9e180bdaf19d6e5571772906c9c24de31790023755c8888", + "<= 400095811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926e000", + "=> f801000104020200fb", + "<= 42519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e31321620fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177fe000", + "=> f8010001020100", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200500e000", + "=> f801000182fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f0303583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 416d0d3e783926a53f1a696f04944e03bc43440cf47684d9a959e98d2add8510f10500e000", + "=> f8010001820bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b903038855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4c178813b8617f884a8135fd9f95e1a4596188ab7705a3356480c01c0f977db930bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9", + "<= 40000bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9e000", + "=> f80100010705050002000000", + "<= 42519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e31321620583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020101", + "<= 41519b38dae74447b72151f354cb138ca3591a5ff8ac813289b18a004e313216200501e000", + "=> f801000182583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0303fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d195811f41d3d5c58240be155bb7d1dcb8f47add7e3417c24e1d52d41653013926", + "<= 416d0d3e783926a53f1a696f04944e03bc43440cf47684d9a959e98d2add8510f10501e000", + "=> f8010001828855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a403030bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9c178813b8617f884a8135fd9f95e1a4596188ab7705a3356480c01c0f977db930bd288cecce2dfc6c9b9245ab747a10870f84c16e986e61b259603b59cf1f3b9", + "<= 40008855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4e000", + "=> f80100010705050000000000", + "<= 4000ba1a89c470ab0c36c38beaebb577b754c33d90b51df53977e2b01018fbe25f33e000", + "=> f80100017674740230546170726f6f7420666f726569676e20696e7465726e616c206b65792c20616e64206f757220736372697074206b657913da4cb76634983c02c657de46cbe9a23e5c3ec2a41a02f41dd65bf065469c352402516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb", + "<= 4000da4cb76634983c02c657de46cbe9a23e5c3ec2a41a02f41dd65bf065469c3524e000", + "=> f801000115131374722840302f2a2a2c706b2840312f2a2a2929", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0200e000", + "=> f801000142521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775010179ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1", + "<= 4000521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775e000", + "=> f8010001898787005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b3233417746", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 4152a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0100e000", + "=> f80100012252a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0000", + "<= 400052a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302be000", + "=> f8010001444242000a2ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fab13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a00e000", + "=> f8010001a2b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d20404583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a01e000", + "=> f8010001a2583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0404b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a02e000", + "=> f8010001a29f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19604043b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905096c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a03e000", + "=> f8010001a23b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050904049f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1966c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a04e000", + "=> f8010001a20298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70404d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a05e000", + "=> f8010001a2d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b5904040298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59e000", + "=> f80100012523230015c15017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a06e000", + "=> f8010001a2fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d310404cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f1982c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d31e000", + "=> f801000124222200165017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a06e000", + "=> f8010001a2e15593bf6543bfabc3aec6dd64b41ef6f6a54bd28a342a7dfa27486de4738aae04048ab4a42bd45e15046ab71d43f935d45cb8ca1243cce0ffc4d8686df1119cf187c538f7b0c49c8ebdbce106354ec9a98c2055a9266918bfcafd0ad2fb747188ae0abe70738502d881c6fc68bab1aab8becbf0a892f500e8f5da49732af7ce81216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 4000e15593bf6543bfabc3aec6dd64b41ef6f6a54bd28a342a7dfa27486de4738aaee000", + "=> f8010001201e1e000076223a6e300000800100008000000080020000800000000000000000", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a07e000", + "=> f8010001a2cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f190404fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d3182c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f19e000", + "=> f801000124222200166b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a07e000", + "=> f8010001a28ab4a42bd45e15046ab71d43f935d45cb8ca1243cce0ffc4d8686df1119cf1870404e15593bf6543bfabc3aec6dd64b41ef6f6a54bd28a342a7dfa27486de4738aaec538f7b0c49c8ebdbce106354ec9a98c2055a9266918bfcafd0ad2fb747188ae0abe70738502d881c6fc68bab1aab8becbf0a892f500e8f5da49732af7ce81216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 40008ab4a42bd45e15046ab71d43f935d45cb8ca1243cce0ffc4d8686df1119cf187e000", + "=> f8010001403e3e0001092eda033617e210ee7f7d0e378a404aea1c48b56aa103022becf7746e4700a4f5acc2fd300000800100008000000080020000800000000000000000", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a08e000", + "=> f80100016267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed019040120202b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 400067a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012e000", + "=> f80100010402020017", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a09e000", + "=> f801000162b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0020267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 4000b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0e000", + "=> f80100010402020018", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fab413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020100", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a00e000", + "=> f8010001a2b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d20404583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a00e000", + "=> f8010001a2eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521040486f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b833f38e6af142b139e21811914f8a4079e2702a261477eacde0f0653cbe3e7fa04a4cbafeb669042a318ab80179304e2ffdbdc33ff0d06ebdbac655d12c9c4216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 4000eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521e000", + "=> f80100012e2c2c004c06000000000000225120fc0a10d308c7e41b4bbc3642f8fd8ac289853b1ce755a0c6d0d495f03d9f2da4", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0200e000", + "=> f801000142521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775010179ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1", + "<= 4000521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775e000", + "=> f8010001898787005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b3233417746", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020101", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a01e000", + "=> f8010001a2583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0404b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a01e000", + "=> f8010001a286f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b0404eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521833f38e6af142b139e21811914f8a4079e2702a261477eacde0f0653cbe3e7fa04a4cbafeb669042a318ab80179304e2ffdbdc33ff0d06ebdbac655d12c9c4216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 400086f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3be000", + "=> f80100010705050001000000", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0200e000", + "=> f801000142521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775010179ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1", + "<= 4000521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775e000", + "=> f8010001898787005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b3233417746", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 41f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141f0100e000", + "=> f801000122f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141f0000", + "<= 4000f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141fe000", + "=> f8010001444242000278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8dfdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff256101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 41fdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff2561010201e000", + "=> f801000142f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e010160b259ffaad54b5fa53da25cef41baef40707b982a39aaa1386ba593fbf0ceeb", + "<= 4000f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364ee000", + "=> f8010001191717000014aa8ef374cafadfca76902ddb5cf61c60bbfd9d85", + "<= 41f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141f0100e000", + "=> f801000122f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141f0000", + "<= 4000f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141fe000", + "=> f8010001444242000278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8dfdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff256101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020100", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 41fdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff2561010200e000", + "=> f80100014260b259ffaad54b5fa53da25cef41baef40707b982a39aaa1386ba593fbf0ceeb0101f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e", + "<= 400060b259ffaad54b5fa53da25cef41baef40707b982a39aaa1386ba593fbf0ceebe000", + "=> f80100010b0909003905000000000000", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 41fdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff2561010201e000", + "=> f801000142f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e010160b259ffaad54b5fa53da25cef41baef40707b982a39aaa1386ba593fbf0ceeb", + "<= 4000f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364ee000", + "=> f8010001191717000014aa8ef374cafadfca76902ddb5cf61c60bbfd9d85", + "<= 4152a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0100e000", + "=> f80100012252a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0000", + "<= 400052a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302be000", + "=> f8010001444242000a2ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fab13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a00e000", + "=> f8010001a2b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d20404583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a01e000", + "=> f8010001a2583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0404b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a02e000", + "=> f8010001a29f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19604043b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905096c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a03e000", + "=> f8010001a23b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050904049f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1966c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a04e000", + "=> f8010001a20298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70404d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a05e000", + "=> f8010001a2d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b5904040298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59e000", + "=> f80100012523230015c15017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a06e000", + "=> f8010001a2fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d310404cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f1982c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d31e000", + "=> f801000124222200165017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a07e000", + "=> f8010001a2cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f190404fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d3182c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f19e000", + "=> f801000124222200166b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a08e000", + "=> f80100016267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed019040120202b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 400067a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012e000", + "=> f80100010402020017", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a09e000", + "=> f801000162b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0020267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 4000b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0e000", + "=> f80100010402020018", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa9f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001020102", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a02e000", + "=> f8010001a29f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19604043b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905096c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a02e000", + "=> f8010001a259dfa3432052a07e205c5852a8115f36b5751bc38c0d16e99339bf11786cb3eb040486f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3bfbd01be915e2775cfe7a13b8a797721df7906f1303bf566c1dc1d93c1cf57b9504a4cbafeb669042a318ab80179304e2ffdbdc33ff0d06ebdbac655d12c9c4216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 400059dfa3432052a07e205c5852a8115f36b5751bc38c0d16e99339bf11786cb3ebe000", + "=> f8010001232121001fc1cc5771cb83860cc56d94bcfed938608c17f63662435b41e9c44ffcfd3142", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa3b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001020103", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a03e000", + "=> f8010001a23b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050904049f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1966c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a03e000", + "=> f8010001a286f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b040459dfa3432052a07e205c5852a8115f36b5751bc38c0d16e99339bf11786cb3ebfbd01be915e2775cfe7a13b8a797721df7906f1303bf566c1dc1d93c1cf57b9504a4cbafeb669042a318ab80179304e2ffdbdc33ff0d06ebdbac655d12c9c4216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 400086f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3be000", + "=> f80100010705050001000000", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f8010001020104", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a04e000", + "=> f8010001a20298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70404d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a04e000", + "=> f8010001a2b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d980404e349f9d63a6274c4ac4d94f77cb5c37e690004074eb0eda8ebd0212051c09b37a76b95c125e315db4e6cbbe2d64b2ff399194bc71841593586bfd4efa850d6b10abe70738502d881c6fc68bab1aab8becbf0a892f500e8f5da49732af7ce81216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 4000b2db18c190abf44354f0286c60b2a6b6a2db6d1a36a6829e66298918b55e1d98e000", + "=> f801000107050500fdffffff", + "<= 41f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141f0100e000", + "=> f801000122f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141f0000", + "<= 4000f812f66891ac8edd9c988a8766f261a416f8cec1124ff7c778359f061abc141fe000", + "=> f8010001444242000278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8dfdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff256101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 40004f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f80100010402020004", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020100", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0200e000", + "=> f801000142583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d01014f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + "<= 41fdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff2561010200e000", + "=> f80100014260b259ffaad54b5fa53da25cef41baef40707b982a39aaa1386ba593fbf0ceeb0101f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e", + "<= 400060b259ffaad54b5fa53da25cef41baef40707b982a39aaa1386ba593fbf0ceebe000", + "=> f80100010b0909003905000000000000", + "<= 4278850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4e000", + "=> f8010001020101", + "<= 4178850a5ab36238b076dd99fd258c70d523168704247988a94caa8c9ccd056b8d0201e000", + "=> f8010001424f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a40101583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + "<= 41fdd2e53a1c8aaa496b5d9e9f85de15d038d6c10bee94ffbd749193feff2561010201e000", + "=> f801000142f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364e010160b259ffaad54b5fa53da25cef41baef40707b982a39aaa1386ba593fbf0ceeb", + "<= 4000f6457f2e5f16c18653329752399861b1c47c6ec1e1933f7fdd3ad1c7a6b5364ee000", + "=> f8010001191717000014aa8ef374cafadfca76902ddb5cf61c60bbfd9d85", + "<= 4152a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0100e000", + "=> f80100012252a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0000", + "<= 400052a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302be000", + "=> f8010001444242000a2ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fab13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a00e000", + "=> f8010001a2b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d20404583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a01e000", + "=> f8010001a2583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0404b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a02e000", + "=> f8010001a29f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19604043b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905096c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a03e000", + "=> f8010001a23b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050904049f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1966c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a04e000", + "=> f8010001a20298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70404d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a05e000", + "=> f8010001a2d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b5904040298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59e000", + "=> f80100012523230015c15017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a06e000", + "=> f8010001a2fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d310404cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f1982c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d31e000", + "=> f801000124222200165017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a07e000", + "=> f8010001a2cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f190404fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d3182c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f19e000", + "=> f801000124222200166b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a08e000", + "=> f80100016267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed019040120202b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 400067a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012e000", + "=> f80100010402020017", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a09e000", + "=> f801000162b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0020267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 4000b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0e000", + "=> f80100010402020018", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fab413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020100", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a00e000", + "=> f8010001a2b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d20404583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a00e000", + "=> f8010001a2eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521040486f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b833f38e6af142b139e21811914f8a4079e2702a261477eacde0f0653cbe3e7fa04a4cbafeb669042a318ab80179304e2ffdbdc33ff0d06ebdbac655d12c9c4216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 4000eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521e000", + "=> f80100012e2c2c004c06000000000000225120fc0a10d308c7e41b4bbc3642f8fd8ac289853b1ce755a0c6d0d495f03d9f2da4", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0200e000", + "=> f801000142521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775010179ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1", + "<= 4000521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775e000", + "=> f8010001898787005b37363232336136652f3438272f31272f30272f32275d747075624445374e51796d7234414674657770417357746e726579713967686b7a51425870435a6a574c46565241766e62663776796132654d54765432665061704e714c38537556764c51646255624d66574c5644435a4b6e734542717036554b393351457a4c38436b3233417746", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 4152a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0100e000", + "=> f80100012252a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302b0000", + "<= 400052a2b0a36f6a220ccad78599c1a2ade65a547f60adb4742208004768ce06302be000", + "=> f8010001444242000a2ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fab13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a00e000", + "=> f8010001a2b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d20404583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f80100010402020001", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a01e000", + "=> f8010001a2583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0404b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f80100010402020003", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a02e000", + "=> f8010001a29f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b19604043b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae905096c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40009f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b196e000", + "=> f8010001040202000e", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a03e000", + "=> f8010001a23b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae9050904049f4917386c45e2c0da0d9b475f1a19cf2db1e929195c6a9f4966ca0d2105b1966c0c1f61819138c88692eb2a0d48302b95bb44c5af18dc97de5c9144667791fcb129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40003b2b7c6ee25e2f28a6235e273eaf13f504bd445024147ebacb878262aae90509e000", + "=> f8010001040202000f", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a04e000", + "=> f8010001a20298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe70404d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 40000298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7e000", + "=> f80100010402020010", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a05e000", + "=> f8010001a2d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b5904040298d122906dcfc10892cb53a73992fc5b9f493ea4c9badb27b791b4127a7fe7b3196e9b7edd9bd22d846602ded84174b396f5d6577302f4e80a3eb314317958722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000d6e2dd3d84f0ddcb5213265124a3373e204f5ced6e24f2eea221c906ebf82b59e000", + "=> f80100012523230015c15017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a06e000", + "=> f8010001a2fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d310404cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f1982c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d31e000", + "=> f801000124222200165017108becea8dedc5300bb22386b3ab30367ac262f9ce5e58326eb83e3a22f5", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a06e000", + "=> f8010001a2e15593bf6543bfabc3aec6dd64b41ef6f6a54bd28a342a7dfa27486de4738aae04048ab4a42bd45e15046ab71d43f935d45cb8ca1243cce0ffc4d8686df1119cf187c538f7b0c49c8ebdbce106354ec9a98c2055a9266918bfcafd0ad2fb747188ae0abe70738502d881c6fc68bab1aab8becbf0a892f500e8f5da49732af7ce81216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 4000e15593bf6543bfabc3aec6dd64b41ef6f6a54bd28a342a7dfa27486de4738aaee000", + "=> f8010001201e1e000076223a6e300000800100008000000080020000800000000000000000", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a07e000", + "=> f8010001a2cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f190404fe9f3fe1fece3c192f8e8f139062141d102b0294e4171f02a27b857e38e07d3182c227d895877751e69ee9c140d456e98ecf404afc55e5d1dbc0d05c4be86c37722331f4d1466415a9ed493cda597538431c8bea1b14bed934ba20a1f565b4c0625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 4000cc42900f19b21f54505e980a9e29fa9325906d230e46009a28fa41f6ef891f19e000", + "=> f801000124222200166b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a07e000", + "=> f8010001a28ab4a42bd45e15046ab71d43f935d45cb8ca1243cce0ffc4d8686df1119cf1870404e15593bf6543bfabc3aec6dd64b41ef6f6a54bd28a342a7dfa27486de4738aaec538f7b0c49c8ebdbce106354ec9a98c2055a9266918bfcafd0ad2fb747188ae0abe70738502d881c6fc68bab1aab8becbf0a892f500e8f5da49732af7ce81216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 40008ab4a42bd45e15046ab71d43f935d45cb8ca1243cce0ffc4d8686df1119cf187e000", + "=> f8010001403e3e0001092eda033617e210ee7f7d0e378a404aea1c48b56aa103022becf7746e4700a4f5acc2fd300000800100008000000080020000800000000000000000", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a08e000", + "=> f80100016267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed019040120202b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 400067a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012e000", + "=> f80100010402020017", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a09e000", + "=> f801000162b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0020267a88a2dc9a9749ec611eef8f47ef5ffcc335f2950d1bfaaf31169ed01904012454f801d01b657e627ac67c96acac49dbe705107621002dba84d17ceac19609d", + "<= 4000b5609376c87f00c645433e48648cb02e6a3f83467f2c827194de5d58f971c8f0e000", + "=> f80100010402020018", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 41516d2c50a89476ecffeec658057f0110674bbfafc18797dc480c7ed53802f3fb0201e000", + "=> f80100014279ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade10101521a79b1ec8019f7b8291af131d33a9dd39252161c6c8fc1f47c4edd9cfc2775", + "<= 400079ad51261747bf60b55f8900bb82bfc5dc7f52b9eb056bee94442ced92e1ade1e000", + "=> f8010001898787005b66356163633266642f3438272f31272f30272f32275d747075624446417145474e79616433356142434b554158625147446a6456684e75656e6f355a5a56456e3373516257356369343537674c52374879546d48426739336f6f757242737367557875577a316a583575686331716171466f395673796259314a35467565644c666d34644b", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de000", + "=> f8010001020101", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a01e000", + "=> f8010001a2583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d0404b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a01e000", + "=> f8010001a286f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b0404eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521833f38e6af142b139e21811914f8a4079e2702a261477eacde0f0653cbe3e7fa04a4cbafeb669042a318ab80179304e2ffdbdc33ff0d06ebdbac655d12c9c4216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 400086f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3be000", + "=> f80100010705050001000000", + "<= 422ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fab413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2e000", + "=> f8010001020100", + "<= 412ccf7826e726332a6e434b27fafbff92163ef2a69521a5d6c686399a2a18c1fa0a00e000", + "=> f8010001a2b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d20404583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5de80cc247985bb408a9484b6fd53b538c321cab413033bb288ba55747dfadb6bab129229731a274be9e04d70d1f19c2b461edb2f29ddfce9309073458e039219f625b913ee9c3b93595e0eff3c6027145f86b094ecc1062d4f9cc4b1f22170534", + "<= 41b13db13cbb4cc5e475ca353b6ba53953d8c87677f1766d4e91988f7924d7b9be0a00e000", + "=> f8010001a2eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521040486f9649499b0080656c014aa244f654864bad4145c8513e9c8409f437d4a2b3b833f38e6af142b139e21811914f8a4079e2702a261477eacde0f0653cbe3e7fa04a4cbafeb669042a318ab80179304e2ffdbdc33ff0d06ebdbac655d12c9c4216e49a585e41fcb396de837e564293763fb71addf4377f356a574345fb16228fe", + "<= 4000eb8e6fe5c6218ad65d91351e9e7537081d0148ae3510d897833c1d1d69b6d521e000", + "=> f80100012e2c2c004c06000000000000225120fc0a10d308c7e41b4bbc3642f8fd8ac289853b1ce755a0c6d0d495f03d9f2da4", + "<= 1000406b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac092eda033617e210ee7f7d0e378a404aea1c48b56aa103022becf7746e4700a443493158062db6905dea9ba3ae6c14e1e155ba47aa1cfb35282052ac4dbc1c6718cda5c911a11599a869557ab34242cb0a227836e98976061530ca4de49eed9e01e000", + "=> f801000100", + "<= 9000" + ], + "sigs": [ + { + "key": "6b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac", + "tapleaf_hash": "092eda033617e210ee7f7d0e378a404aea1c48b56aa103022becf7746e4700a4", + "sig": "43493158062db6905dea9ba3ae6c14e1e155ba47aa1cfb35282052ac4dbc1c6718cda5c911a11599a869557ab34242cb0a227836e98976061530ca4de49eed9e01" + } + ] + } +] diff --git a/bitcoin_client_rs/tests/utils/mod.rs b/bitcoin_client_rs/tests/utils/mod.rs new file mode 100644 index 000000000..ccd053564 --- /dev/null +++ b/bitcoin_client_rs/tests/utils/mod.rs @@ -0,0 +1,89 @@ +use core::convert::TryFrom; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use async_trait::async_trait; +use bitcoin::hashes::hex::FromHex; + +use ledger_bitcoin_client::{ + apdu::{APDUCommand, StatusWord}, + async_client, client, +}; + +#[derive(Default, Clone)] +pub struct RecordStore { + pub queue: Vec<(Vec, Vec)>, +} + +impl RecordStore { + pub fn new(exchanges: &Vec) -> RecordStore { + let mut store = RecordStore::default(); + let mut command: Vec = Vec::new(); + for (i, exchange) in exchanges.iter().enumerate() { + let exchange = exchange.replace(" ", ""); + if let Some(cmd) = exchange.strip_prefix("=>") { + command = Vec::from_hex(cmd).expect(&format!("Wrong tests data {}: {}", i, cmd)); + } + if let Some(resp) = exchange.strip_prefix("<=") { + let resp = Vec::from_hex(resp).expect(&format!("Wrong tests data {}: {}", i, resp)); + store.queue.push((command.clone(), resp)); + } + } + + store + } +} + +pub struct TransportReplayer { + store: RecordStore, + current: AtomicUsize, +} + +impl TransportReplayer { + pub fn new(store: RecordStore) -> TransportReplayer { + TransportReplayer { + store, + current: AtomicUsize::new(0), + } + } + + fn replay(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), MockError> { + let payload = command.encode(); + let current = self.current.load(Ordering::Relaxed); + if let Some((req, res)) = self.store.queue.get(current) { + if payload != *req { + return Err(MockError::ExchangeNotFound(current, hex::encode(payload))); + } + self.current.store(current + 1, Ordering::Relaxed); + let res = res.as_slice(); + let mut buff = [b'\0'; 2]; + buff.copy_from_slice(&res[res.len() - 2..res.len()]); + let sw = u16::from_be_bytes(buff); + let answer = &res[0..res.len() - 2]; + return Ok(( + StatusWord::try_from(sw).expect("Wrong tests data"), + answer.to_vec(), + )); + } + Err(MockError::ExchangeNotFound(current, hex::encode(payload))) + } +} + +impl client::Transport for TransportReplayer { + type Error = MockError; + fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error> { + self.replay(command) + } +} + +#[async_trait] +impl async_client::Transport for TransportReplayer { + type Error = MockError; + async fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec), Self::Error> { + self.replay(command) + } +} + +#[derive(Debug)] +pub enum MockError { + ExchangeNotFound(usize, String), +} diff --git a/dev-tools/test_sign_psbt_with_sighash_types.ipynb b/dev-tools/test_sign_psbt_with_sighash_types.ipynb new file mode 100644 index 000000000..78f2233a8 --- /dev/null +++ b/dev-tools/test_sign_psbt_with_sighash_types.ipynb @@ -0,0 +1,936 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "c823d12e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'f5acc2fd'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from io import BytesIO\n", + "\n", + "from bitcoin_client.ledger_bitcoin.common import hash160\n", + "from bitcoin_client.ledger_bitcoin.psbt import PSBT, KeyOriginInfo, PartiallySignedInput\n", + "from bitcoin_client.ledger_bitcoin.tx import CTransaction, CTxWitness, CTxInWitness, COutPoint, uint256_from_str, CTxIn, CTxOut\n", + "from embit.descriptor import Descriptor, Key\n", + "from embit.descriptor.miniscript import Miniscript\n", + "from embit.networks import NETWORKS\n", + "from bip32 import BIP32\n", + "from mnemonic import Mnemonic\n", + "mnemo = Mnemonic(\"english\")\n", + "\n", + "DEFAULT_SPECULOS_MNEMONIC = \"glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster seven myth punch hobby comfort wild raise skin\"\n", + "seed = mnemo.to_seed(DEFAULT_SPECULOS_MNEMONIC)\n", + "bip32 = BIP32.from_seed(seed, network=\"test\")\n", + "\n", + "master_extended_privkey = bip32.get_xpriv()\n", + "master_extended_pubkey = bip32.get_xpub()\n", + "fpr = hash160(bip32.pubkey)[0:4]\n", + "H = 0x80000000\n", + "fpr.hex()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "91209acd", + "metadata": {}, + "outputs": [], + "source": [ + "PSBT_IMPLICIT_ALL=\"cHNidP8BAH0CAAAAAaG4I9IzbWlLSTTvm25bfeF6BVE9qKKdsCouy8eppv5tAQAAAAD9////AlX/pwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg+ghgEAAAAAABYAFBOZuKCYR6A5sDUvWNISwYC6sX93AAAAAAABASunhqkAAAAAACJRINj08dGJltthuxyvVCPeJdih7unJUNN+b/oCMBLV5i4NIRYhLqKFalzxEOZqK+nXNTFHk/28s4iyuPE/K2remC569RkA9azC/VYAAIABAACAAAAAgAEAAAAAAAAAARcgIS6ihWpc8RDmaivp1zUxR5P9vLOIsrjxPytq3pguevUAAQUgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwohBwKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KGQD1rML9VgAAgAEAAIAAAACAAQAAAAIAAAAAAA==\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f5114876", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwQBAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBAEAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA\n" + ] + } + ], + "source": [ + "# SIGHASH_ALL_SIGN\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 1\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 1\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "06f4dce1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0200000000010216f79a3163ec37e83c54c6e2e8ff6cd8838e835ef3adc3dfd59627c949ec24090000000000fdfffffff9e21b877c81e1862254fd03941c0391122854284c6bb7fbb11d8acdcaeac0b30000000000fdffffff02d2339700000000002251200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f55260000000000001600141399b8a09847a039b0352f58d212c180bab17f77014199c2aa5f6f6929e5adfdc978a867fdae94227cbf8d0caa1ae1b27b88fe4ac2ba61da7795226bc381476ad6f142023881325e32d72523f31587b28027ec473e47010141c572196e9aa50c9eb5a1996d12fc7f57e5ff826636e06959fa7021aaef8eb88a09a038c6033b6acb76205808c4d7a88f1a15d4824e301e1934683c3bc2c47c980100000000'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# signature produced for the input:\n", + "\n", + "input_sig0 = b'\\xb8.i1\\xaf\\xeb\\xb0\\xa0{\\xad\\xfaU1\\xc9f\\\\@\\x1f_\\x15\\xe2\\xdd\\xf2\\xf7\\x052\\x91\\xc1PR\\xd3\\x83\\xe9\\xd8\\xbe\\xc2\\xbbhKT;\\x97\\xaem\\xb3>%\\xa0\\xa6\\x04\\xfdQy\\x03,k}\\x97\\xc1\\x12\\x0b\\xc6\\x16e'\n", + "input_sig1 = b'\\x0e\\xf8\\xa4hSZ\\x16c\\xfe\\x9e{|Q\\x1bz\\xe3y\\xec\\xf4\\xc5A\\x86#7\\xbed\\xab\\xa9r\\xa7\\x95?H\\x08G\\xe3Ut\\\\\\x94d{w\\x9a\\x1b\\xe6\\xe8s\\r\\xcd\\xbb\\xe12mk\\xd1O\\xf9J\\xe6\\x89\\xda\\xb9\\x11'\n", + "input_sig0 = b'\\x99\\xc2\\xaa_oi)\\xe5\\xad\\xfd\\xc9x\\xa8g\\xfd\\xae\\x94\"|\\xbf\\x8d\\x0c\\xaa\\x1a\\xe1\\xb2{\\x88\\xfeJ\\xc2\\xbaa\\xdaw\\x95\"k\\xc3\\x81Gj\\xd6\\xf1B\\x028\\x812^2\\xd7%#\\xf3\\x15\\x87\\xb2\\x80\\'\\xecG>G\\x01'\n", + "input_sig1 = b'\\xc5r\\x19n\\x9a\\xa5\\x0c\\x9e\\xb5\\xa1\\x99m\\x12\\xfc\\x7fW\\xe5\\xff\\x82f6\\xe0iY\\xfap!\\xaa\\xef\\x8e\\xb8\\x8a\\t\\xa08\\xc6\\x03;j\\xcbv X\\x08\\xc4\\xd7\\xa8\\x8f\\x1a\\x15\\xd4\\x82N0\\x1e\\x194h<;\\xc2\\xc4|\\x98\\x01'\n", + "stx = CTransaction()\n", + "stx.deserialize(BytesIO(psbt.tx.serialize()))\n", + "\n", + "in_wit0 = CTxInWitness()\n", + "in_wit0.scriptWitness.stack = [input_sig0]\n", + "\n", + "in_wit1 = CTxInWitness()\n", + "in_wit1.scriptWitness.stack = [input_sig1]\n", + "\n", + "wit = CTxWitness()\n", + "wit.vtxinwit = [in_wit0, in_wit1]\n", + "\n", + "wit.serialize()\n", + "\n", + "stx.wit = wit\n", + "stx.serialize_with_witness().hex()\n", + "\n", + "\n", + "#ACCEPTED BY MEMPOOL" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8b3bf7a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwQCAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBAIAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA\n" + ] + } + ], + "source": [ + "# SIGHASH_NONE_SIGN\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 2\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 2\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "61306ef5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0200000000010216f79a3163ec37e83c54c6e2e8ff6cd8838e835ef3adc3dfd59627c949ec24090000000000fdfffffff9e21b877c81e1862254fd03941c0391122854284c6bb7fbb11d8acdcaeac0b30000000000fdffffff02d2339700000000002251200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f55260000000000001600141399b8a09847a039b0352f58d212c180bab17f7701416d8a903726f338a8a0a81879c9aa6da505364ff3081a712dad9671046dc20e6d3ee34506ab22361ce0d6b9384e1e18aab34872dcbe78499a5161372b025ae529020141f65cdc2c8f496ca998190590ac0edce14b3ea4ae4779b4057a0e689f2a479e9053c069baf6d6b977b302bdd5d7a657e0deb051ee70cea24253d074191fb24fc50200000000'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# signature produced for the input:\n", + "input_sig0 = b'm\\x8a\\x907&\\xf38\\xa8\\xa0\\xa8\\x18y\\xc9\\xaam\\xa5\\x056O\\xf3\\x08\\x1aq-\\xad\\x96q\\x04m\\xc2\\x0em>\\xe3E\\x06\\xab\"6\\x1c\\xe0\\xd6\\xb98N\\x1e\\x18\\xaa\\xb3Hr\\xdc\\xbexI\\x9aQa7+\\x02Z\\xe5)\\x02'\n", + "input_sig1 = b'\\xf6\\\\\\xdc,\\x8fIl\\xa9\\x98\\x19\\x05\\x90\\xac\\x0e\\xdc\\xe1K>\\xa4\\xaeGy\\xb4\\x05z\\x0eh\\x9f*G\\x9e\\x90S\\xc0i\\xba\\xf6\\xd6\\xb9w\\xb3\\x02\\xbd\\xd5\\xd7\\xa6W\\xe0\\xde\\xb0Q\\xeep\\xce\\xa2BS\\xd0t\\x19\\x1f\\xb2O\\xc5\\x02'\n", + "\n", + "stx = CTransaction()\n", + "stx.deserialize(BytesIO(psbt.tx.serialize()))\n", + "\n", + "in_wit0 = CTxInWitness()\n", + "in_wit0.scriptWitness.stack = [input_sig0]\n", + "\n", + "in_wit1 = CTxInWitness()\n", + "in_wit1.scriptWitness.stack = [input_sig1]\n", + "\n", + "wit = CTxWitness()\n", + "wit.vtxinwit = [in_wit0, in_wit1]\n", + "\n", + "wit.serialize()\n", + "\n", + "stx.wit = wit\n", + "stx.serialize_with_witness().hex()\n", + "\n", + "#ACCEPTED BY MEMPOOL" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0c744ca2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAH0CAAAAARb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////AtIzlwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg+ghgEAAAAAABYAFBOZuKCYR6A5sDUvWNISwYC6sX93AAAAAAABASuNNJcAAAAAACJRIAuOdIa8MGoK77enwArwQFVC2xrNc+7MqCdxzPX+XrYPAQMEAgAAACEWApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAEXIAKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KAAEFIAKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KIQcCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAAAA=\n" + ] + } + ], + "source": [ + "# SIGHASH_NONE_1_IN_2_OUTS\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 2\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d5dc8324", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwQDAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBAMAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA\n" + ] + } + ], + "source": [ + "# SIGHASH_SINGLE_SIGN\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 3\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 3\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b09c1bfe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0200000000010216f79a3163ec37e83c54c6e2e8ff6cd8838e835ef3adc3dfd59627c949ec24090000000000fdfffffff9e21b877c81e1862254fd03941c0391122854284c6bb7fbb11d8acdcaeac0b30000000000fdffffff02d2339700000000002251200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f55260000000000001600141399b8a09847a039b0352f58d212c180bab17f770141deed3fd31d682fca617491df151f510fd0c51b7dda5ff4af86fbfc91665ad10368a9de2c20dd8adc2d626e69384ddf43e65345df28be811d6d58fc2485d5d0ff0301417be52997e1fd2ac3dea2cf9a6938c413efc8f7f9fcfdd29d0c2c13d42a59d29721f46571347a636ff896c0beafa7bd68590cc94fd79c4ca9e07eef0297bf2e910300000000'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# signature produced for the input:\n", + "input_sig0 =b'\\xde\\xed?\\xd3\\x1dh/\\xcaat\\x91\\xdf\\x15\\x1fQ\\x0f\\xd0\\xc5\\x1b}\\xda_\\xf4\\xaf\\x86\\xfb\\xfc\\x91fZ\\xd1\\x03h\\xa9\\xde, \\xdd\\x8a\\xdc-bni8M\\xdfC\\xe6SE\\xdf(\\xbe\\x81\\x1dmX\\xfc$\\x85\\xd5\\xd0\\xff\\x03'\n", + "input_sig1 =b'{\\xe5)\\x97\\xe1\\xfd*\\xc3\\xde\\xa2\\xcf\\x9ai8\\xc4\\x13\\xef\\xc8\\xf7\\xf9\\xfc\\xfd\\xd2\\x9d\\x0c,\\x13\\xd4*Y\\xd2\\x97!\\xf4eq4zco\\xf8\\x96\\xc0\\xbe\\xaf\\xa7\\xbdhY\\x0c\\xc9O\\xd7\\x9cL\\xa9\\xe0~\\xef\\x02\\x97\\xbf.\\x91\\x03'\n", + "\n", + "stx = CTransaction()\n", + "stx.deserialize(BytesIO(psbt.tx.serialize()))\n", + "\n", + "in_wit0 = CTxInWitness()\n", + "in_wit0.scriptWitness.stack = [input_sig0]\n", + "\n", + "in_wit1 = CTxInWitness()\n", + "in_wit1.scriptWitness.stack = [input_sig1]\n", + "\n", + "wit = CTxWitness()\n", + "wit.vtxinwit = [in_wit0, in_wit1]\n", + "\n", + "wit.serialize()\n", + "\n", + "stx.wit = wit\n", + "stx.serialize_with_witness().hex()\n", + "\n", + "#ACCEPTED BY MEMPOOL" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7ece141a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAM8CAAAAAxb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///9zWd5vSCPj9qJ8WTucQPYsO8WJCiKhkJZDR4O4ZyXRtAAAAAAA/f///wLSM5cAAAAAACJRIAuOdIa8MGoK77enwArwQFVC2xrNc+7MqCdxzPX+XrYPVSYAAAAAAAAWABQTmbigmEegObA1L1jSEsGAurF/dwAAAAAAAQErjTSXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62DwEDBAMAAAAhFgKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KGQD1rML9VgAAgAEAAIAAAACAAQAAAAIAAAABFyACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCgABASsQJwAAAAAAACJRIEFGFImkPLZ+LGTzlCcnjt0wwD1tcqS4mYYUfEKPDMXxAQMEAwAAACEW6vdlNEO+xlQ326HaUvnKl+IxeFr1Zifo+cMRz3MA9t4ZAPWswv1WAACAAQAAgAAAAIAAAAAAAgAAAAEXIOr3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeAAEBK6CGAQAAAAAAIlEgaEmQnCLZJcKrtpXBQGIWvtL9aEdSTcZDdPa+sr8VmH8BAwQDAAAAIRZqOZFvW8uwbi8/4lpNmxK0CS/IGntnzSXeRuC6EKU2BxkA9azC/VYAAIABAACAAAAAgAAAAAAEAAAAARcgajmRb1vLsG4vP+JaTZsStAkvyBp7Z80l3kbguhClNgcAAQUgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwohBwKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KGQD1rML9VgAAgAEAAIAAAACAAQAAAAIAAAAAAA==\n" + ] + } + ], + "source": [ + "# SIGHASH_SINGLE_3_INS_2_OUT\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 3\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 3\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 2\n", + "psbt.inputs[2].witness_utxo = CTxOut()\n", + "\n", + "input_pk2 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/4\")\n", + "input_pk2 = input_pk2[1:] \n", + "\n", + "psbt.tx.vin[2].prevout.hash = int(\"b4d12567b88347439690a1220a89c53b2cf6409c3b597ca2f6e323486fde5973\", 16)\n", + "psbt.tx.vin[2].prevout.n = 0\n", + "psbt.tx.vin[2].nSequence = 4294967293\n", + "psbt.inputs[2].sighash = 3\n", + "psbt.inputs[2].witness_utxo.nValue = 100000\n", + "psbt.inputs[2].witness_utxo.scriptPubKey = bytes.fromhex(\"51206849909c22d925c2abb695c1406216bed2fd6847524dc64374f6beb2bf15987f\")\n", + "psbt.inputs[2].tap_internal_key=bytes.fromhex(\"6a39916f5bcbb06e2f3fe25a4d9b12b4092fc81a7b67cd25de46e0ba10a53607\")\n", + "psbt.inputs[2].tap_bip32_paths = {input_pk2: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 4]))}\n", + "\n", + "psbt.inputs[2].witness_utxo.scriptPubKey.hex()\n", + "\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "39141940", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSBAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIEAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA\n" + ] + } + ], + "source": [ + "# SIGHASH_ALL_ANYONE_SIGN\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 0x81\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 0x81\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "80ef4173", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0200000000010216f79a3163ec37e83c54c6e2e8ff6cd8838e835ef3adc3dfd59627c949ec24090000000000fdfffffff9e21b877c81e1862254fd03941c0391122854284c6bb7fbb11d8acdcaeac0b30000000000fdffffff02d2339700000000002251200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f55260000000000001600141399b8a09847a039b0352f58d212c180bab17f770141ace43a06b18433d887ce9e159ce66ddfe4a6f4cf8838b3554552ccaf9d524745fe796c4a0e33f5e99ecfdb79bf11b856958dd22a8fe93988b0fb219083d4b567810141d2007dad52801c34d6f45a97367582192b0478655fcf51a8b3c5d857c6ce9206b4297832cbf1f00734433ca45731a584db78fe73bcaecc09cf73ec0342d1dde28100000000'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# signature produced for the input:\n", + "\n", + "# signature produced for the input:\n", + "input_sig0 = b'\\xac\\xe4:\\x06\\xb1\\x843\\xd8\\x87\\xce\\x9e\\x15\\x9c\\xe6m\\xdf\\xe4\\xa6\\xf4\\xcf\\x888\\xb3UER\\xcc\\xaf\\x9dRGE\\xfeylJ\\x0e3\\xf5\\xe9\\x9e\\xcf\\xdby\\xbf\\x11\\xb8V\\x95\\x8d\\xd2*\\x8f\\xe99\\x88\\xb0\\xfb!\\x90\\x83\\xd4\\xb5g\\x81'\n", + "input_sig1 = b'\\xd2\\x00}\\xadR\\x80\\x1c4\\xd6\\xf4Z\\x976u\\x82\\x19+\\x04xe_\\xcfQ\\xa8\\xb3\\xc5\\xd8W\\xc6\\xce\\x92\\x06\\xb4)x2\\xcb\\xf1\\xf0\\x074C<\\xa4W1\\xa5\\x84\\xdbx\\xfes\\xbc\\xae\\xcc\\t\\xcfs\\xec\\x03B\\xd1\\xdd\\xe2\\x81'\n", + "\n", + "stx = CTransaction()\n", + "stx.deserialize(BytesIO(psbt.tx.serialize()))\n", + "\n", + "in_wit0 = CTxInWitness()\n", + "in_wit0.scriptWitness.stack = [input_sig0]\n", + "\n", + "in_wit1 = CTxInWitness()\n", + "in_wit1.scriptWitness.stack = [input_sig1]\n", + "\n", + "wit = CTxWitness()\n", + "wit.vtxinwit = [in_wit0, in_wit1]\n", + "\n", + "wit.serialize()\n", + "\n", + "stx.wit = wit\n", + "stx.serialize_with_witness().hex()\n", + "\n", + "#ACCEPTED BY MEMPOOL" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "e8f1be79", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSCAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIIAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA\n" + ] + } + ], + "source": [ + "# SIGHASH_NONE_ANYONE_SIGN\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 0x82\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 0x82\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "00ad3c09", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0200000000010216f79a3163ec37e83c54c6e2e8ff6cd8838e835ef3adc3dfd59627c949ec24090000000000fdfffffff9e21b877c81e1862254fd03941c0391122854284c6bb7fbb11d8acdcaeac0b30000000000fdffffff02d2339700000000002251200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f55260000000000001600141399b8a09847a039b0352f58d212c180bab17f770141f08ead57bd4bc0e0bc179c64c95a2e5723b03fb88c636d9d713501e8dd2a4bf4f76d72ee7d5e54a879a8d6bb98d6762462ea6af5706514875c34735eea355d10820141fb8439d97240a9920d236e4d145208491ca9f0805e3363851cc4ccb2276d335aa89abf5eea8523d7ab991d23ab19790c495ec2a46313c9c298b8609e4df8ba588200000000'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# signature produced for the input:\n", + "input_sig0 =b'\\xf0\\x8e\\xadW\\xbdK\\xc0\\xe0\\xbc\\x17\\x9cd\\xc9Z.W#\\xb0?\\xb8\\x8ccm\\x9dq5\\x01\\xe8\\xdd*K\\xf4\\xf7mr\\xee}^T\\xa8y\\xa8\\xd6\\xbb\\x98\\xd6v$b\\xeaj\\xf5pe\\x14\\x87\\\\4s^\\xea5]\\x10\\x82'\n", + "input_sig1 = b\"\\xfb\\x849\\xd9r@\\xa9\\x92\\r#nM\\x14R\\x08I\\x1c\\xa9\\xf0\\x80^3c\\x85\\x1c\\xc4\\xcc\\xb2'm3Z\\xa8\\x9a\\xbf^\\xea\\x85#\\xd7\\xab\\x99\\x1d#\\xab\\x19y\\x0cI^\\xc2\\xa4c\\x13\\xc9\\xc2\\x98\\xb8`\\x9eM\\xf8\\xbaX\\x82\"\n", + "stx = CTransaction()\n", + "stx.deserialize(BytesIO(psbt.tx.serialize()))\n", + "\n", + "in_wit0 = CTxInWitness()\n", + "in_wit0.scriptWitness.stack = [input_sig0]\n", + "\n", + "in_wit1 = CTxInWitness()\n", + "in_wit1.scriptWitness.stack = [input_sig1]\n", + "\n", + "wit = CTxWitness()\n", + "wit.vtxinwit = [in_wit0, in_wit1]\n", + "\n", + "wit.serialize()\n", + "\n", + "stx.wit = wit\n", + "stx.serialize_with_witness().hex()\n", + "#ACCEPTED BY MEMPOOL" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "aabbd26d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSDAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIMAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA\n" + ] + } + ], + "source": [ + "# SIGHASH_SINGLE_ANYONE_SIGN\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 0x83\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 0x83\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "2e670f1b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0200000000010216f79a3163ec37e83c54c6e2e8ff6cd8838e835ef3adc3dfd59627c949ec24090000000000fdfffffff9e21b877c81e1862254fd03941c0391122854284c6bb7fbb11d8acdcaeac0b30000000000fdffffff02d2339700000000002251200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f55260000000000001600141399b8a09847a039b0352f58d212c180bab17f770141174c67a5629a05ca885bb25ebc75673ef13a39288ccec71435d22d0539d07aaf7fe5d5d9fb63ee7768a52e7d4c38b4116d35d29af0e98c486051a0cf626f43388301412a90c8307e3bc9dd15cb180356d0d6134bd7c254c2dbe6b1cf5bdf686cb8ee9b35c77e12ace8235ee11563e2043ef3a28b0c5aa4c123be7b678395e538a666388300000000'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# signature produced for the input:\n", + "input_sig0 = b'\\x17Lg\\xa5b\\x9a\\x05\\xca\\x88[\\xb2^\\xbcug>\\xf1:9(\\x8c\\xce\\xc7\\x145\\xd2-\\x059\\xd0z\\xaf\\x7f\\xe5\\xd5\\xd9\\xfbc\\xeewh\\xa5.}L8\\xb4\\x11m5\\xd2\\x9a\\xf0\\xe9\\x8cH`Q\\xa0\\xcfboC8\\x83'\n", + "input_sig1 = b'*\\x90\\xc80~;\\xc9\\xdd\\x15\\xcb\\x18\\x03V\\xd0\\xd6\\x13K\\xd7\\xc2T\\xc2\\xdb\\xe6\\xb1\\xcf[\\xdfhl\\xb8\\xee\\x9b5\\xc7~\\x12\\xac\\xe8#^\\xe1\\x15c\\xe2\\x04>\\xf3\\xa2\\x8b\\x0cZ\\xa4\\xc1#\\xbe{g\\x83\\x95\\xe58\\xa6f8\\x83'\n", + "\n", + "stx = CTransaction()\n", + "stx.deserialize(BytesIO(psbt.tx.serialize()))\n", + "\n", + "in_wit0 = CTxInWitness()\n", + "in_wit0.scriptWitness.stack = [input_sig0]\n", + "\n", + "in_wit1 = CTxInWitness()\n", + "in_wit1.scriptWitness.stack = [input_sig1]\n", + "\n", + "wit = CTxWitness()\n", + "wit.vtxinwit = [in_wit0, in_wit1]\n", + "\n", + "wit.serialize()\n", + "\n", + "stx.wit = wit\n", + "stx.serialize_with_witness().hex()\n", + "\n", + "#ACCEPTED BY MEMPOOL " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "75e7662d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSEAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIMAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA\n" + ] + } + ], + "source": [ + "# SIGHASH_UNSUPPORTED\n", + "psbt = PSBT()\n", + "psbt.deserialize(PSBT_IMPLICIT_ALL)\n", + "\n", + "# input 0 \n", + "input_pk0 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/1/2\")\n", + "input_pk0 = input_pk0[1:] \n", + "\n", + "psbt.tx.vin[0].prevout.hash = int(\"0924ec49c92796d5dfc3adf35e838e83d86cffe8e2c6543ce837ec63319af716\", 16)\n", + "psbt.tx.vin[0].prevout.n = 0\n", + "psbt.tx.vin[0].nSequence = 4294967293\n", + "psbt.tx.vout[0].nValue = 9909202\n", + "psbt.inputs[0].sighash = 0x84\n", + "psbt.inputs[0].witness_utxo.nValue = 9909389\n", + "psbt.inputs[0].witness_utxo.scriptPubKey = bytes.fromhex(\"51200b8e7486bc306a0aefb7a7c00af0405542db1acd73eecca82771ccf5fe5eb60f\")\n", + "psbt.inputs[0].tap_internal_key=bytes.fromhex(\"029081ece5616aa1cb99319fc4874efee47f08ceba5f6004636c8c43409a5f0a\")\n", + "psbt.inputs[0].tap_bip32_paths = {input_pk0: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 1, 2]))}\n", + "\n", + "psbt.tx.vin.append(CTxIn())\n", + "psbt.inputs.append(PartiallySignedInput(0))\n", + "\n", + "#input 1\n", + "psbt.inputs[1].witness_utxo = CTxOut()\n", + "\n", + "input_pk1 = bip32.get_pubkey_from_path(\"m/86'/1'/0'/0/2\")\n", + "input_pk1 = input_pk1[1:] \n", + "\n", + "psbt.tx.vin[1].prevout.hash = int(\"b3c0eacacd8a1db1fbb76b4c2854281291031c9403fd542286e1817c871be2f9\", 16)\n", + "psbt.tx.vin[1].prevout.n = 0\n", + "psbt.tx.vin[1].nSequence = 4294967293\n", + "psbt.tx.vout[1].nValue = 9813\n", + "psbt.inputs[1].sighash = 0x83\n", + "psbt.inputs[1].witness_utxo.nValue = 10000\n", + "psbt.inputs[1].witness_utxo.scriptPubKey = bytes.fromhex(\"512041461489a43cb67e2c64f39427278edd30c03d6d72a4b89986147c428f0cc5f1\")\n", + "psbt.inputs[1].tap_internal_key=bytes.fromhex(\"eaf7653443bec65437dba1da52f9ca97e231785af56627e8f9c311cf7300f6de\")\n", + "psbt.inputs[1].tap_bip32_paths = {input_pk1: (set(), KeyOriginInfo(fpr, [86^H, 1^H, 0^H, 0, 2]))}\n", + "\n", + "psbt.inputs[1].witness_utxo.scriptPubKey.hex()\n", + "\n", + "NEW_PSBT = psbt.serialize()\n", + "print(NEW_PSBT)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f502ee40", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SIGHASH1\n", + "020000000001017a2a997956c09f8ea7fd2819c1a987bb14e22bf9adcdaf20a89763722ceee2640100000000fdffffff02a0bb0d00000000001976a914344a0f48ca150ec2b903817660b9b68b13a6702688ac7438230000000000160014eb38fa9b8128f81f26e95edb0c5ffaea83690fe402483045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa012103ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d306800000000\n", + "SIGHASH2\n", + "020000000001017a2a997956c09f8ea7fd2819c1a987bb14e22bf9adcdaf20a89763722ceee2640100000000fdffffff02a0bb0d00000000001976a914344a0f48ca150ec2b903817660b9b68b13a6702688ac7438230000000000160014eb38fa9b8128f81f26e95edb0c5ffaea83690fe40247304402206f863ed58bb5a5a24b5ace7ab292d0ce04214c5f8f39eb236d339eb48dc6734b0220708d950b3442025ef16e42d2ea846214c7008822ed196f3c667d45ccfac2fcd3022103ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d306800000000\n", + "SIGHASH3\n", + "020000000001017a2a997956c09f8ea7fd2819c1a987bb14e22bf9adcdaf20a89763722ceee2640100000000fdffffff02a0bb0d00000000001976a914344a0f48ca150ec2b903817660b9b68b13a6702688ac7438230000000000160014eb38fa9b8128f81f26e95edb0c5ffaea83690fe4024730440220112e7666be1b64321c7889cfca2803b0c10386cb08e4e9bfef2f1ea19302014302202e2958439931a685a206a4f7defcb7ce0bc7f6d66f768adda9b5f98fb80782c2032103ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d306800000000\n", + "SIGHASH81\n", + "020000000001017a2a997956c09f8ea7fd2819c1a987bb14e22bf9adcdaf20a89763722ceee2640100000000fdffffff02a0bb0d00000000001976a914344a0f48ca150ec2b903817660b9b68b13a6702688ac7438230000000000160014eb38fa9b8128f81f26e95edb0c5ffaea83690fe402483045022100deaefd1f67969a2cb90efea9c334334cca3d9feb34cf67d63275c463a52730d902207264887f207393d097eac140c8beed7520377734047a992e26d939a1496c2f82812103ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d306800000000\n", + "SIGHASH82\n", + "020000000001017a2a997956c09f8ea7fd2819c1a987bb14e22bf9adcdaf20a89763722ceee2640100000000fdffffff02a0bb0d00000000001976a914344a0f48ca150ec2b903817660b9b68b13a6702688ac7438230000000000160014eb38fa9b8128f81f26e95edb0c5ffaea83690fe402483045022100e50d376da21ab489d4386b14ebd0a9cc00179c688b16b59d26ab946d643992390220221539dca306069c520af19afb5ede291ae91e075396ed41524efe59a4c141d4822103ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d306800000000\n", + "SIGHASH83\n", + "020000000001017a2a997956c09f8ea7fd2819c1a987bb14e22bf9adcdaf20a89763722ceee2640100000000fdffffff02a0bb0d00000000001976a914344a0f48ca150ec2b903817660b9b68b13a6702688ac7438230000000000160014eb38fa9b8128f81f26e95edb0c5ffaea83690fe40247304402200771b3e405a37cd4aa24951c088d7e4c37093a7cdd7037a7688114d524560376022040fff9bcd07c00fa912d7d1eed040ecc9dd4e44e4d5cf6ef9a94af836cd87fdd832103ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d306800000000\n" + ] + } + ], + "source": [ + "PSBT_WPKH = \"cHNidP8BAHQCAAAAAXoqmXlWwJ+Op/0oGcGph7sU4iv5rc2vIKiXY3Is7uJkAQAAAAD9////AqC7DQAAAAAAGXapFDRKD0jKFQ7CuQOBdmC5tosTpnAmiKx0OCMAAAAAABYAFOs4+puBKPgfJule2wxf+uqDaQ/kAAAAAAABAH0CAAAAAa+/rgZZD3Qf8a9ZtqxGESYzakxKgttVPfb++rc3rDPzAQAAAAD9////AnARAQAAAAAAIgAg/e5EHFblsG0N+CwSTHBwFKXKGWWL4LmFa8oW8e0yWfel9DAAAAAAABYAFDr4QprVlUql7oozyYP9ih6GeZJLAAAAAAEBH6X0MAAAAAAAFgAUOvhCmtWVSqXuijPJg/2KHoZ5kksiBgPuLD2Y6x+TwKGqjlpACbcOt7ROrRXxZm8TawEq1Y0waBj1rML9VAAAgAEAAIAAAACAAQAAAAgAAAAAACICAinsR3JxMe0liKIMRu2pq7fapvSf1Quv5wucWqaWHE7MGPWswv1UAACAAQAAgAAAAIABAAAACgAAAAA=\"\n", + "\n", + "pubkey = bytes.fromhex(\"03ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d3068\")\n", + "\n", + "sig_sighash1 = b\"0E\\x02!\\x00\\xabD\\xf3M\\xd7\\xe8|\\x90TY\\x12\\x97\\xa1\\x01\\xe8P\\n\\x06A\\xd1\\xd5\\x91\\x87\\x8d\\r#\\xcf\\x80\\x96\\xfay\\xe8\\x02 ]\\x12\\xd1\\x06-\\x92^'\\xb5{\\xdc\\xf9\\x94\\xec\\xf32\\xad\\n\\x8eg\\xb8\\xfe@{\\xab!\\x01%]\\xa62\\xaa\\x01\"\n", + "sig_sighash2 = b'0D\\x02 o\\x86>\\xd5\\x8b\\xb5\\xa5\\xa2KZ\\xcez\\xb2\\x92\\xd0\\xce\\x04!L_\\x8f9\\xeb#m3\\x9e\\xb4\\x8d\\xc6sK\\x02 p\\x8d\\x95\\x0b4B\\x02^\\xf1nB\\xd2\\xea\\x84b\\x14\\xc7\\x00\\x88\"\\xed\\x19o +This page details the protocol implemented since version 2.1.0 of the app. +The protocol documentation for version from 2.0.0 and before 2.1.0 is [here](./v0/bitcoin.md) and is now deprecated. ## Framework ### 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/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 main commands use `CLA = 0xE1`, unlike the legacy Bitcoin application that used `CLA = 0xE0`. -| CLA | INS | COMMAND NAME | DESCRIPTION | -|-----|-----|---------------------|-------------| -| E1 | 00 | GET_EXTENDED_PUBKEY | Return (and optionally show on screen) extended pubkey | -| E1 | 02 | REGISTER_WALLET | Registers a wallet on the device (with user's approval) | -| E1 | 03 | GET_WALLET_ADDRESS | Return and show on screen an address for a registered or default wallet | -| E1 | 04 | SIGN_PSBT | Signs a PSBT with a registered or default wallet | -| E1 | 10 | SIGN_MESSAGE | Sign a message with a key from a BIP32 path (Bitcoin Message Signing) | +| CLA | INS | COMMAND NAME | DESCRIPTION | +|-----|-----|------------------------|-------------| +| E1 | 00 | GET_EXTENDED_PUBKEY | Return (and optionally show on screen) extended pubkey | +| E1 | 02 | REGISTER_WALLET | Register a wallet policy on the device (with user's approval) | +| E1 | 03 | GET_WALLET_ADDRESS | Return and show on screen an address for a registered or default wallet | +| E1 | 04 | SIGN_PSBT | Sign a PSBT with a registered or default wallet | +| E1 | 05 | GET_MASTER_FINGERPRINT | Return the fingerprint of the master public key | +| E1 | 10 | SIGN_MESSAGE | Sign a message with a key from a BIP32 path (Bitcoin Message Signing) | The `CLA = 0xF8` is used for framework-specific (rather than app-specific) APDUs; at this time, only one command is present. @@ -84,7 +86,7 @@ Returns an extended public key at the given derivation path, serialized as per B | Length | Name | Description | |--------|-------------------|-------------| | `1` | `display` | `0` or `1` | -| `1` | `n` | Number of derivation steps (maximum 6) | +| `1` | `n` | Number of derivation steps (maximum 8) | | `4` | `bip32_path[0]` | First derivation step (big endian) | | `4` | `bip32_path[1]` | Second derivation step (big endian) | | | ... | | @@ -142,6 +144,8 @@ After user's validation is completed successfully, the application returns the ` #### Client commands +`GET_PREIMAGE` must know and respond for the full serialized wallet policy whose sha256 hash is `wallet_id`; moreover, it must know and respond for the sha256 hash of its descriptor template. + The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_MERKLE_LEAF_INDEX` queries related to the Merkle tree of the list of keys information. The `GET_MORE_ELEMENTS` command must be handled. @@ -185,7 +189,7 @@ If the `display` parameter is `1`, the resulting wallet address is also shown on #### Client commands -`GET_PREIMAGE` must know and respond for the full serialized wallet policy whose sha256 hash is `wallet_id`. +`GET_PREIMAGE` must know and respond for the full serialized wallet policy whose sha256 hash is `wallet_id`; moreover, it must know and respond for the sha256 hash of its descriptor template. The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_MERKLE_LEAF_INDEX` queries related to the Merkle tree of the list of keys information. @@ -223,16 +227,23 @@ No output data; the signature are returned using the YIELD client command. #### Description -Using the information in the PSBT and the wallet description, this command verifies what inputs are internal and what output matches the pattern for a change address. After validating all the external outputs and the transaction fee with the user, it signs each of the internal inputs; each signature is sent to the client using the YIELD command, encoded as ` `, where the `input_index` is a Bitcoin style varint (currently, always 1 byte). +Using the information in the PSBT and the wallet description, this command verifies what inputs are internal and what outputs match the pattern for a change address. After validating all the external outputs and the transaction fee with the user, it signs each of the internal inputs; each signature is sent to the client using the YIELD command, in the format described below. If multiple key placeholders of the wallet policy are internal, the process is repeated for each of them. + +The results yielded via the YIELD command respect the following format: ` `, where: +- `input_index` is a Bitcoin style varint, the index input of the input being signed (starting from 0); +- `pubkey_augm_len` is an unsigned byte equal to the length of `pubkey_augm`; +- `pubkey_augm` is the `pubkey` used for signing for legacy, segwit or taproot script path spends (a compressed pubkey if non-taproot, a 32-byte x-only pubkey if taproot); for taproot script path spends, it is the concatenation of the `x-only` pubkey and the 32-byte *tapleaf hash* as defined in [BIP-0341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki); +- `signature` is the returned signature, possibly concatenated with the sighash byte (as it would be pushed on the stack). + +If `P2` is `0` (version `0` of the protocol), `pubkey_augm_len` and `pubkey_augm` are omitted in the YIELD messages. For a registered wallet, the hmac must be correct. For a default wallet, `hmac` must be equal to 32 bytes `0`. - #### Client commands -`GET_PREIMAGE` must know and respond for the full serialized wallet policy whose sha256 hash is `wallet_id`. +`GET_PREIMAGE` must know and respond for the full serialized wallet policy whose sha256 hash is `wallet_id`; moreover, it must know and respond for the sha256 hash of its descriptor template. The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_MERKLE_LEAF_INDEX` queries for all the Merkle trees in the input, including each of the Merkle trees for keys and values of the Merkleized map commitments of each of the inputs/outputs maps of the psbt. @@ -287,7 +298,7 @@ The device shows on its secure screen the BIP-32 path used for signing, and the | Length | Name | Description | |---------|-------------------|-------------| -| `1` | `n` | Number of derivation steps (maximum 6) | +| `1` | `n` | Number of derivation steps (maximum 8) | | `4` | `bip32_path[0]` | First derivation step (big endian) | | `4` | `bip32_path[1]` | Second derivation step (big endian) | | | ... | | @@ -338,7 +349,7 @@ The `YIELD` client command is sent to the client to communicate some result duri The client must respond with an empty message. -### 40 GET_PREIMAGE +### GET_PREIMAGE **Command code**: 0x40 diff --git a/doc/merkle.md b/doc/merkle.md index 3013df360..85e16a37a 100644 --- a/doc/merkle.md +++ b/doc/merkle.md @@ -35,7 +35,7 @@ Note that the 1-byte prefix `0x00` is prepended when computing the leaf hashes, The Merkle proof (called *Merkle audit path* in the language for RFC 6962) for a leaf node is the minimal set of additional nodes that is necessary to compute the Merkle Tree Hash. See section 2.1.3. of RFC 6962 for some examples. -For any non-root node of the tree (either internal or leaf), let the *brother* be the unique other note sharing the same parent, that is the unique other node whose hash is combined together to compute an internal node. +For any non-root node of the tree (either internal or leaf), let the *brother* be the unique other node sharing the same parent, that is the unique other node whose hash is combined together to compute an internal node. In the typical tree-like representation of the Merkle tree, the Merkle proof for a leaf is the list of the brothers of all the internal nodes diff --git a/doc/v0/bitcoin.md b/doc/v0/bitcoin.md new file mode 100644 index 000000000..04bc5665d --- /dev/null +++ b/doc/v0/bitcoin.md @@ -0,0 +1,417 @@ +# Bitcoin application: Technical Specifications + +This page described the _deprecated_ version 0 of the protocol, as implemented in the v2.0.0 of the Bitcoin app. This protocol is still supported at this time, but we encourage integrations to switch to [version 1 of the protocol](../bitcoin.md). + +## Framework + +### 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 main commands use `CLA = 0xE1`, unlike the legacy Bitcoin application that used `CLA = 0xE0`. + +| CLA | INS | COMMAND NAME | DESCRIPTION | +|-----|-----|---------------------|-------------| +| E1 | 00 | GET_EXTENDED_PUBKEY | Return (and optionally show on screen) extended pubkey | +| E1 | 02 | REGISTER_WALLET | Registers a wallet on the device (with user's approval) | +| E1 | 03 | GET_WALLET_ADDRESS | Return and show on screen an address for a registered or default wallet | +| E1 | 04 | SIGN_PSBT | Signs a PSBT with a registered or default wallet | +| E1 | 10 | SIGN_MESSAGE | Sign a message with a key from a BIP32 path (Bitcoin Message Signing) | + +The `CLA = 0xF8` is used for framework-specific (rather than app-specific) APDUs; at this time, only one command is present. + +| CLA | INS | COMMAND NAME | DESCRIPTION | +|-----|-----|--------------|-------------| +| F8 | 01 | CONTINUE | Respond to an interruption and continue processing a command | + +The `CONTINUE` command is sent as a response to a client command from the Hardware Wallet; the format and content on the response depends on the client command, and is documented below for each client command. + +### Interactive commands + +Several commands are executed via an interactive protocol that requires multiple rounds. At any time after receiving the command and before returning the commands final response (which is status word `0x9000` in case of success), the Hardware Wallet can respond with a special status word `SW_INTERRUPTED_EXECUTION` (`0xE000`), containing a request for the client in the response data. The first byte of the response is the *client command code*, identified what kind of request the Hardware Wallet is asking the client to perform. The client *must* comply with the request and send a special *CONTINUE* command `CLA = 0xF8` and `INS = 0x01`, with the appropriate response. + +The specs for the client commands are detailed below. + +## Descriptors and wallet policies + +The Bitcoin app uses a language similar to [output script descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) in order to represent the wallets that can be used to sign transactions. +Wallet policies need to be registered on the device, with an interactive process that requires user's approval. + +See [here](wallet.md) for detailed information on the wallet policy language. + +## Wallet registration flow + +In order to use a wallet policy that is not one of the default ones, the policy must first be registered on the wallet, which is a protocol that requires explicit approval from the user. + +A wallet policy is initiated using the `REGISTER_WALLET` command. The screen of the hardware wallet will ask the user to inspect the wallet descriptor template, followed by each of the keys of the cosigners that are part of the wallet policy. + +Once the user approves, the `REGISTER_WALLET` returns to the client a 32-byte HMAC-SHA256. This will be provided to any future command that makes use of the wallet policy; therefore, the HMAC should be permanently stored on the client. In case of loss of the HMAC, the registration flow must be repeated from scratch. + +## Status Words + +| SW | SW name | Description | +|--------|------------------------------|-------------| +| 0x6985 | `SW_DENY` | Rejected by user | +| 0x6A86 | `SW_WRONG_P1P2` | Either `P1` or `P2` is incorrect | +| 0x6A87 | `SW_WRONG_DATA_LENGTH` | `Lc` or minimum APDU length is incorrect | +| 0x6D00 | `SW_INS_NOT_SUPPORTED` | No command exists with `INS` | +| 0x6E00 | `SW_CLA_NOT_SUPPORTED` | Bad `CLA` used for this application | +| 0xB000 | `SW_WRONG_RESPONSE_LENGTH` | Wrong response length (buffer size problem) | +| 0xB007 | `SW_BAD_STATE` | Abrted because unexpected state reached | +| 0xB008 | `SW_SIGNATURE_FAIL` | Invalid signature or HMAC | +| 0xE000 | `SW_INTERRUPTED_EXECUTION` | The command is interrupted, and requires the client's response | +| 0x9000 | `SW_OK` | Success | + + + +## Commands + +### GET_EXTENDED_PUBKEY + +Returns an extended public key at the given derivation path, serialized as per BIP-32. + +#### Encoding + +**Command** + +| *CLA* | *INS* | +|-------|-------| +| E1 | 00 | + +**Input data** + +| Length | Name | Description | +|--------|-------------------|-------------| +| `1` | `display` | `0` or `1` | +| `1` | `n` | Number of derivation steps (maximum 6) | +| `4` | `bip32_path[0]` | First derivation step (big endian) | +| `4` | `bip32_path[1]` | Second derivation step (big endian) | +| | ... | | +| `4` | `bip32_path[n-1]` | `n`-th derivation step (big endian) | + +**Output data** + +| Length | Description | +|--------|-------------| +| `` | The full serialized extended public key as per BIP-32 | + +#### Description + +This command returns the extended public key for the given BIP 32 path. + +The paths defined in [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki), [BIP-48](https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki), [BIP-49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki), [BIP-84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) and [BIP-86](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki), either in full or are at the deepest hardened level (excluding `change` and `address_index`), are considered standard. + +If the `display` parameter is `0` and the path is not standard, an error is returned. + +If the `display` parameter is `1`, the result is also shown on the secure screen for verification. The UX flow shows on the device screen the exact path and the complete serialized extended pubkey as defined in [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) for that path. If the path is not standard, an additional warning is shown to the user. + +### REGISTER_WALLET + +Registers a wallet policy on the device, after validating it with the user. + +#### Encoding + +**Command** + +| *CLA* | *INS* | +|-------|-------| +| E1 | 02 | + +**Input data** + +| Length | Name | Description | +|-----------------|-----------------|-------------| +| `` | `policy_length` | The length of the policy (unsigned varint) | +| `policy_length` | `policy` | The serialized wallet policy | + +The `policy` is serialized as described [here](wallet.md). At this time, no policy can be longer than 252 bytes, therefore the `policy_length` field is always encoded as 1 byte. + +**Output data** + +| Length | Description | +|--------|----------------------------| +| `32` | The `wallet_id` | +| `32` | The `hmac` for this wallet | + +#### Description + +This command allows to register a wallet policy on the device. The wallet's name, descriptor template and each of the keys information is shown to the user. + +After user's validation is completed successfully, the application returns the `wallet_id` (sha256 of the wallet serialization), and the `hmac` for this wallet. + +#### Client commands + +The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_MERKLE_LEAF_INDEX` queries related to the Merkle tree of the list of keys information. + +The `GET_MORE_ELEMENTS` command must be handled. + +### GET_WALLET_ADDRESS + +Get a receive or change a address for a registered or default wallet, after validating it with the user using the trusted screen. + +#### Encoding + +**Command** + +| *CLA* | *INS* | +|-------|-------| +| E1 | 03 | + +**Input data** + +| Length | Name | Description | +|--------|-----------------|-------------| +| `1` | `display` | `0` or `1` | +| `32` | `wallet_id` | The id of the wallet | +| `32` | `wallet_hmac` | The hmac of a registered wallet, or exactly 32 0 bytes | +| `1` | `change` | `0` for a receive address, `1` for a change address | +| `4` | `address_index` | The desired address index (big-endian) | + + +**Output data** + +| Length | Description | +|-------------|-----------------| +| | The wallet address for the given change/address_index | + +#### Description + +For a registered wallet, the hmac must be correct. Once that is validated, this command computes the address of the wallet for the given `change` and `address_index` choice. + +For a default wallet, `hmac` must be equal to 32 bytes `0`. + +If the `display` parameter is `1`, the resulting wallet address is also shown on the secure screen, and only returns successfully after the user confirms it. If the `display` parameter is `0`, the result is silently returned. + +#### Client commands + +`GET_PREIMAGE` must know and respond for the full serialized wallet policy whose sha256 hash is `wallet_id`. + +The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_MERKLE_LEAF_INDEX` queries related to the Merkle tree of the list of keys information. + +The `GET_MORE_ELEMENTS` command must be handled. + +### SIGN_PSBT + +Given a PSBTv2 and a registered wallet (or a standard one), sign all the inputs that are owned by that wallet. + +#### Encoding + +**Command** + +| *CLA* | *INS* | +|-------|-------| +| E1 | 04 | + +**Input data** + +| Length | Name | Description | +|---------|------------------------|-------------| +| `` | `global_map_size` | The number of key/value pairs of the global map of the psbt | +| `32` | `global_map_keys_root` | The Merkle root of the keys of the global map | +| `32` | `global_map_vals_root` | The Merkle root of the values of the global map | +| `` | `n_inputs` | The number of inputs of the psbt | +| `32` | `inputs_maps_root` | The Merkle root of the vector of Merkleized map commitments for the input maps | +| `` | `n_outputs` | The number of outputs of the psbt | +| `32` | `outputs_maps_root` | The Merkle root of the vector of Merkleized map commitments for the output maps | +| `32` | `wallet_id` | The id of the wallet | +| `32` | `wallet_hmac` | The hmac of a registered wallet, or exactly 32 0 bytes | + +**Output data** + +No output data; the signature are returned using the YIELD client command. + +#### Description + +Using the information in the PSBT and the wallet description, this command verifies what inputs are internal and what output matches the pattern for a change address. After validating all the external outputs and the transaction fee with the user, it signs each of the internal inputs; each signature is sent to the client using the YIELD command, encoded as ` `, where the `input_index` is a Bitcoin style varint (currently, always 1 byte). + +For a registered wallet, the hmac must be correct. + +For a default wallet, `hmac` must be equal to 32 bytes `0`. + + +#### Client commands + +`GET_PREIMAGE` must know and respond for the full serialized wallet policy whose sha256 hash is `wallet_id`. + +The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_MERKLE_LEAF_INDEX` queries for all the Merkle trees in the input, including each of the Merkle trees for keys and values of the Merkleized map commitments of each of the inputs/outputs maps of the psbt. + +The `GET_MORE_ELEMENTS` command must be handled. + +The `YIELD` command must be processed in order to receive the signatures. + +### GET_MASTER_FINGERPRINT + +Returns the fingerprint of the master public key, as defined in [BIP-0032#Key identifiers](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers). + +#### Encoding + +**Command** + +| *CLA* | *INS* | +|-------|-------| +| E1 | 05 | + +**Input data** + +No input data. + +**Output data** + +| Length | Description | +|--------|----------------------------| +| `4` | The master key fingerprint | + +#### Description + +The fingerprint is necessary to fill the key origin information for some PSBT fields, or to create wallet descriptors. + +User interaction is not required for this command. + + +### SIGN_MESSAGE + +Signs a message, according to the standard Bitcoin Message Signing. + +The device shows on its secure screen the BIP-32 path used for signing, and the SHA256 hash of the message; the hash should be verified by the user using an external tool if the client is untrusted. + +#### Encoding + +**Command** + +| *CLA* | *INS* | +|-------|-------| +| E1 | 10 | + +**Input data** + +| Length | Name | Description | +|---------|-------------------|-------------| +| `1` | `n` | Number of derivation steps (maximum 6) | +| `4` | `bip32_path[0]` | First derivation step (big endian) | +| `4` | `bip32_path[1]` | Second derivation step (big endian) | +| | ... | | +| `4` | `bip32_path[n-1]` | `n`-th derivation step (big endian) | +| `` | `msg_length` | The byte length of the message to sign (Bitcoin-style varint) | +| `32` | `msg_merkle_root` | The Merkle root of the message, split in 64-byte chunks | + +The message to be signed is split into `ceil(msg_length/64)` chunks of 64 bytes (except the last chunk that could be smaller); `msg_merkle_root` is the root of the Merkle tree of the corresponding list of chunks. + +The theoretical maximum valid length of the message is 232-1 = 4 294 967 295 bytes. + +**Output data** + +| Length | Description | +|--------|-------------| +| `65` | The returned signature, encoded in the standard Bitcoin message signing format | + +The signature is returned as a 65-byte binary string (1 byte equal to 32 or 33, followed by `r` and `s`, each of them represented as a 32-byte big-endian integer). + +#### Description + +The digest being signed is the double-SHA256 of the message, after prefixing the message with: + +- the magic string `"\x18Bitcoin Signed Message:\n"` (equal to `18426974636f696e205369676e6564204d6573736167653a0a` in hexadecimal) +- the length of the message, encoded as a Bitcoin-style variable length integer. + +#### Client commands + +The client must respond to the `GET_PREIMAGE`, `GET_MERKLE_LEAF_PROOF` and `GET_MERKLE_LEAF_INDEX` queries for the Merkle tree of the list of chunks in the message. + +## Client commands reference + +This section documents the commands that the Hardware Wallet can request to the client when returning with a `SW_INTERRUPTED_EXECUTION` status word. + +| CMD | COMMAND NAME | DESCRIPTION | +|-----|-----------------------|-------------| +| 10 | YIELD | Receive some elements during command execution | +| 40 | GET_PREIMAGE | Return the preimage corresponding to the given sha256 hash | +| 41 | GET_MERKLE_LEAF_PROOF | Returns the Merkle proof for a given leaf | +| 42 | GET_MERKLE_LEAF_INDEX | Returns the index of a leaf in a Merkle tree | +| A0 | GET_MORE_ELEMENTS | Receive more data that could not fit in the previous responses | + +### YIELD + +**Command code**: 0x10 + +The `YIELD` client command is sent to the client to communicate some result during the execution of a command. Currently only used during `SIGN_PSBT` in order to communicate each of the signatures. The format of the attached message is documented for each command that uses `YIELD`. + +The client must respond with an empty message. + +### 40 GET_PREIMAGE + +**Command code**: 0x40 + +The `GET_PREIMAGE` command requests the client to reveal a SHA-256 preimage. + +The request contains: +- `1` byte: must equal 0, reserved for future usage. (The client should abort if non-zero); +- `32` bytes: a sha-256 hash. + +The response must contain: +- ``: the length of the preimage, encoded as a Bitcoin-style varint; +- `1` byte: a 1-byte unsigned integer `b`, the length of the prefix of the pre-image that is part of the response; +- `b` bytes: corresponding to the first `b` bytes of the preimage. + +If the pre-image is too long to be contained in a single response, the client should choose `b` to be as large as possible; subsequent bytes are enqueued as single-byte elements that the Hardware Wallet will request with one ore more `GET_MORE_ELEMENTS` requests. + +### GET_MERKLE_LEAF_PROOF + +**Command code**: 0x41 + +The `GET_MERKLE_LEAF_PROOF` command requests the hash of a given leaf of a Merkle tree, together with the Merkle proof. + +The request contains: +- `32` bytes: the Merkle root hash; +- `` bytes: the tree size `n`, encoded as a Bitcoin-style varint; +- `` bytes: the leaf index `i`, encoded as a Bitcoin-style varint. + +The client must respond with: +- `32` bytes: the hash of the leaf with index `i` in the requested Merkle tree; +- `1` byte: the length of the Merkle proof; +- `1` byte: the amount `p` of hashes of the proof that are contained in the response; +- `32 * p` bytes: the concatenation of the first `p` hashes in the Merkle proof. + +If the proof is too long to be contained in a single response, the client should choose `p` to be as large as possible; subsequent bytes are enqueued as 32-byte elements that the Hardware Wallet will request with one or more `GET_MORE_ELEMENTS` requests. + +### GET_MERKLE_LEAF_INDEX + +**Command code**: 0x42 + +The `GET_MERKLE_LEAF_INDEX` requests the index of a leaf with a certain hash. if multiple leafs have the same hash, the client could respond with either. + +The request contains: +- `32` bytes: the Merkle root hash; +- `32` bytes: the leaf hash. + +The response contains: +- `1` byte: `1` if the leaf is found, `0` if matching leaf exists; +- ``: the index of the leaf, encoded as a Bitcoin-style varint. + +### GET_MORE_ELEMENTS + +**Command code**: 0xA0 + +The `GET_MORE_ELEMENTS` command requests the client to return more elements that were enqueued by previous client commands (like `GET_PREIMAGE` and `GET_MERKLE_LEAF_PROOF`). + +All of the elements in the queue must all be byte strings of the same length; the command fails otherwise. The client should return as many elements as it is possible to fit in the response, while leaving the remaining ones (if any) in the queue. + +The request is empty. + +The response contains: +- `1` byte: the number `n` of returned element; +- `1` byte: the size `s` of each returned element; +- `n * s` bytes: the concatenation of the `n` returned elements. + + +## Security considerations + +Some of the client commands are used to allow the client to reveal some information that is not known to the hardware wallet. This approach allows to create protocols that work with an amount of data that is too large to fit in a single APDU, or even in the limited RAM of a device like a Ledger Nano S. + +In designing the interactive protocol, care is taken to avoid security risks associated with a malicious, possibly compromised client. + +All the current commands use a commit-and-reveal approach: the APDU that starts the protocol (first message) commits to all the relevant data (for example, the entirety of the PSBT), by using hashes and/or Merkle trees. Any time the client is asked to reveal some committed information, the app does not consider it trusted: +- If a preimage is asked via `GET_PREIMAGE`, the hash is computed to validate that the correct preimage is returned by the client. +- If a Merkle proof is asked via `GET_MERKLE_LEAF_PROOF`, the proof is verified. +- If the index of a leaf is asked `GET_MERKLE_LEAF_INDEX`, the proof for that element is requested via `GET_MERKLE_LEAF_PROOF` and the proof verified, *even if the leaf value is known*. + +Care needs to be taken in designing protocols, as the client might lie by omission (for example, fail to reveal that a leaf of a Merkle tree is present during a call to `GET_MERKLE_LEAF_INDEX`). \ No newline at end of file diff --git a/doc/v0/wallet.md b/doc/v0/wallet.md new file mode 100644 index 000000000..94c925e16 --- /dev/null +++ b/doc/v0/wallet.md @@ -0,0 +1,125 @@ + +This page described the wallet plicies used in the _deprecated_ version 0 of the protocol, as implemented in the v2.0.0 of the Bitcoin app. This protocol is still supported at this time, but we encourage integrations to switch to the [wallet policies in version 1 of the protocol](../wallet.md). + +# Wallet policy + +A _wallet descriptor template_ follows the same language as output descriptor, except that each `KEY` expression is replaced with the `@` character followed by non-negative decimal number (starting with `0`). Each of them is a placeholder for the key information that is kept in a separate vector. +A *wallet policy* is the pair of the _wallet descriptor template_ and the vector of key information; some additional metadata is associated, as described below. + +Each key information is an expression similar to the `KEY` expressions of output descriptors, except that +- only serialized extended public keys ("xpubs") are supported; +- key origin information is compulsory +- it is followed by a `/**` prefix implying the last two steps of derivation (change and address index). A formalized description follows below. + +## Reference + +A wallet descriptor template is a `SCRIPT` expression, described as follows: + +`SCRIPT` expressions: +- `sh(SCRIPT)` (top level only): P2SH embed the argument. +- `wsh(SCRIPT)` (top level or inside `sh` only): P2WSH embed the argument. +- `pkh(KP)` (not inside `tr`): P2PKH output for the given public key (use `addr` if you only know the pubkey hash). +- `wpkh(KP)` (top level or inside `sh` only): P2WPKH output for the given compressed pubkey. +- `multi(k,KP_1,KP_2,...,KP_n)`: k-of-n multisig script. +- `sortedmulti(k,KP_1,KP_2,...,KP_n)`: k-of-n multisig script with keys sorted lexicographically in the resulting script. + +Key placeholder `KP` expressions consist of +- a single character `@` +- followed by a non-negative decimal number, with no leading zeros (except for `@0`). + +The placeholder `@i` for some number *i* represents the *i*-th key in the vector of key orgin informations (which must be of size at least *i* + 1, or the wallet is invalid. + +Each element of the *key origin informations* list is a `KEY` expression. +`KEY` expressions: + +- Key origin information, consisting of: + - An open bracket `[` + - Exactly 8 hex characters for the fingerprint of the master key from which this key is derived from (see [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) for details) + - Followed by zero or more `/NUM'` path elements to indicate hardened derivation steps between the fingerprint and the xpub that follows + - A closing bracket `]` +- Followed by the actual key, which is a serialized extended public key (`xpub`) (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). +- Followed by the string `/**` + +Note that this format is much more restricted (by design) than the format used in output descriptors. In particular, the key origin information is compulsory. + +The `/**` in the descriptor template represents all the possible paths used in the wallet. + +## Descriptor derivation + +From a descriptor template (and the associated vector of keys), one can therefore obtain the descriptor for receive and change addresses by: + +- replacing each key placeholder with the corresponding key / key origin, and then +- replacing `/**` with either `/0/*` (receive addresses descriptor) or `/1/*` (change addresses descriptor). + +For example, the wallet descriptor `pkh(@0)` with key information `["[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/**"]` produces the following two descriptors: + +- Receive descriptor: `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)` +- Change descriptor: `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)` + +# Policy registration and usage +The app supports a number of features related to wallet policies. In order to securely sign transactions with a policy wallet (for example in a multisignature), it is necessary to be able to: + +- register a wallet, validating all the information (policy and keys involved) with the user on the trusted screen; +- show the addresses for a registered wallet on the trusted screen; +- sign spends from the wallet. + +Since the application is stateless, wallet registration is not persisted on device. In order to make it possible to use a registered wallet in future requests, the device returns a hmac-sha256 (32 bytes long) for the wallet upon a successful registration. The client side is responsible for persisting the wallet policy *and* the returned hmac-sha256, and to provide this information in future requests. + +As the symmetric key used for hmac-sha256 is deterministically derived from the hardware wallet seed (using [SLIP-0021](https://github.com/satoshilabs/slips/blob/master/slip-0021.md)), the completed wallet registration is non-revokable. + +## Wallet policy serialization + +A registered wallet policy comprises the following: +- The wallet name, up to 16 bytes long; the name is shown to the user on-screen in order to identify the wallet. +- The wallet descriptor template as a string. +- The list of keys. + +The wallet policy is serialized as the concatenation of: + +- `1 byte`: a byte equal to `0x01`, reserved for future use +- `1 byte`: the length of the wallet name (0 for standard wallet) +- ``: the wallet name (empty for standard wallets) +- ``: the length of the wallet descriptor template, encoded as a Bitcoin-style variable-length integer +- ``: the wallet descriptor template, as an ascii string (no terminating 0) +- ``: the number of keys in the list of keys, encoded as a Bitcoin-style variable-length integer +- `<32 bytes>`: the root of the canonical Merkle tree of the list of keys. + +See [merkle](../merkle.md) for information on Merkle trees. + +The sha256 hash of a serialized wallet policy is used as a *wallet policy id*. + +## Wallet name + +The wallet name must be recognizable from the user when shown on-screen. Currently, the following limitations apply during wallet registration: +- The wallet name must be between 1 and 16 characters long. +- Each character must be an ASCII character with code at least 32 = 0x20 (the 'space' character) and at most 125 = 0x7e (the '~' character). +- The first and the last character must _not_ be spaces. + +The hardware wallet will reject registration for wallet names not respecting the above constraints. + +## Supported policies + +As a precaution, at this time only a limited set of commonly used policies can be registered. More will be added in the future, to support new use cases. + +The following policy types are currently supported: + +- `sh(multi(...))` and `sh(sortedmulti(...))` (legacy multisignature wallets); +- `sh(wsh(multi(...)))` and `sh(wsh(sortedmulti(...)))` (wrapped-segwit multisignature wallets); +- `wsh(multi(...))` and `wsh(sortedmulti(...))` (native segwit multisignature wallets). + +## Other technical limitations + +At this time, there are some technical limitations on the accepted wallet policies: +- `multi` and `sortedmulti` support at most 5 keys; + +These limitations will likely be removed in the future. + +# Default wallets +A few policies that correspond to standardized single-key wallets can be used without requiring any registration; in the serialization, the wallet name must be a zero-length string. Those are the following policies: + +- ``pkh(@0)`` - legacy addresses as per [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) +- ``wpkh(@0)`` - native segwit addresses per [BIP-84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) +- ``sh(wpkh(@0))`` - nested segwit addresses as per [BIP-49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) +- ``tr(@0)`` - single Key P2TR as per [BIP-86](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki) + +Note that the wallet policy is considered standard (and therefore usable for signing without prior registration) only if the signing paths (defined in the key origin information) adheres to the corresponding BIP. \ No newline at end of file diff --git a/doc/wallet.md b/doc/wallet.md index b85f0039c..c5f22aa69 100644 --- a/doc/wallet.md +++ b/doc/wallet.md @@ -1,57 +1,126 @@ # Wallet policy -A _wallet descriptor template_ follows the same language as output descriptor, except that each `KEY` expression is replaced with the `@` character followed by non-negative decimal number (starting with `0`). Each of them is a placeholder for the key information that is kept in a separate vector. -A *wallet policy* is the pair of the _wallet descriptor template_ and the vector of key information; some additional metadata is associated, as described below. +A _wallet policy_ is a structured representation of an account secured by a policy expressed with output script descriptors. It is composed by two parts: +a wallet descriptor template and the vector of key placeholder expressions. -Each key information is an expression similar to the `KEY` expressions of output descriptors, except that -- only serialized extended public keys ("xpubs") are supported; -- key origin information is compulsory -- it is followed by a `/**` prefix implying the last two steps of derivation (change and address index). A formalized description follows below. +A _wallet descriptor template_ follows language very similar to output descriptor, with a few differences; the biggest one is that each `KEY` expression with a key placeholder `KP` expression, that refers to one of the keys in the _keys information vector_, plus the additional derivation steps to use for that key. Contextually, the keys information vector contains all the relevant _xpubs_, and possibly their key origin information. -## Reference +Each entry in the key information vector contains an _xpub_ (other types of keys supported in output script descriptors are not allowed), possible preceeded by the key origin information. The key origin information is compulsory for internal keys. -A wallet descriptor template is a `SCRIPT` expression, described as follows: +This section formally defines wallet policies, and how they relate to +output script descriptors. -`SCRIPT` expressions: -- `sh(SCRIPT)` (top level only): P2SH embed the argument. -- `wsh(SCRIPT)` (top level or inside `sh` only): P2WSH embed the argument. -- `pkh(KP)` (not inside `tr`): P2PKH output for the given public key (use `addr` if you only know the pubkey hash). -- `wpkh(KP)` (top level or inside `sh` only): P2WPKH output for the given compressed pubkey. -- `multi(k,KP_1,KP_2,...,KP_n)`: k-of-n multisig script. -- `sortedmulti(k,KP_1,KP_2,...,KP_n)`: k-of-n multisig script with keys sorted lexicographically in the resulting script. - -Key placeholder `KP` expressions consist of -- a single character `@` -- followed by a non-negative decimal number, with no leading zeros (except for `@0`). - -The placeholder `@i` for some number *i* represents the *i*-th key in the vector of key orgin informations (which must be of size at least *i* + 1, or the wallet is invalid. +## Formal definition -Each element of the *key origin informations* list is a `KEY` expression. -`KEY` expressions: +A _wallet policy_ is composed by a _wallet descriptor template_, together with a vector of _key information items_. -- Key origin information, consisting of: - - An open bracket `[` - - Exactly 8 hex characters for the fingerprint of the master key from which this key is derived from (see [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) for details) - - Followed by zero or more `/NUM'` path elements to indicate hardened derivation steps between the fingerprint and the xpub that follows - - A closing bracket `]` -- Followed by the actual key, which is a serialized extended public key (`xpub`) (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). -- Followed by the string `/**` +### Wallet descriptor template ==== -Note that this format is much more restricted (by design) than the format used in output descriptors. In particular, the key origin information is compulsory. +A wallet descriptor template is a `SCRIPT` expression. -The `/**` in the descriptor template represents all the possible paths used in the wallet. +`SCRIPT` expressions: +- `sh(SCRIPT)` (top level only): P2SH embed the argument. +- `wsh(SCRIPT)` (top level or inside `sh` only): P2WSH embed the argument. +- `pkh(KP)` (not inside `tr`): P2PKH output for the given public key (use +`addr` if you only know the pubkey hash). +- `wpkh(KP)` (top level or inside `sh` only): P2WPKH output for the given +compressed pubkey. +- `multi(k,KP_1,KP_2,...,KP_n)` (not inside `tr`): k-of-n multisig script using OP_CHECKMULTISIG. +- `sortedmulti(k,KP_1,KP_2,...,KP_n)` (not inside `tr`): k-of-n multisig script with keys +sorted lexicographically in the resulting script. +- `multi_a(k,KP_1,KP_2,...,KP_n)` (only inside `tr`): k-of-n multisig script. +- `sortedmulti_a(k,KP_1,KP_2,...,KP_n)` (only inside `tr`): k-of-n multisig script with keys +sorted lexicographically in the resulting script. +- `tr(KP)` or `tr(KP,TREE)`: P2TR output with the specified key placeholder internal key, and optionally a tree of script paths. +- any valid [miniscript](https://bitcoin.sipa.be/miniscript) template (only inside top-level `wsh`, or in `TREE`). + +`TREE` expressions: +- any `SCRIPT`expression. +- An open brace `{`, a `TREE` expression, a comma `,`, a `TREE` expression, and a closing brace `}`. + +`KP` expressions (key placeholders) consist of +- a single character `@` +- followed by a non-negative decimal number, with no leading zeros (except +for `@0`). +- possibly followed by either: + - the string `/**`, or + - a string of the form `//*`, for two distinct decimal numbers +`NUM` representing unhardened derivations. + +The `/**` in the placeholder template represents commonly used paths for +receive/change addresses, and is equivalent to `<0;1>`. + +The placeholder `@i` for some number *i* represents the *i*-th key in the +vector of key origin information (which must be of size at least *i* + 1, +or the wallet policy is invalid). + +### Keys information vector + +Each element of the keys origin information vector is a `KEY` expression. + +`KEY` expressions consist of +- Optionally, key origin information, consisting of: + - An open bracket `[` + - Exactly 8 hex characters for the fingerprint of the master key from +which this key is derived from (see [BIP32]( +https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) for details) + - Followed by zero or more `/NUM'` path elements to indicate hardened +derivation steps between the fingerprint and the xpub that follows + - A closing bracket `]` +- Followed by the actual key, which is either + - a hex-encoded pubkey, which is either + - inside `wpkh` and `wsh`, only compressed public keys are permitted +(exactly 66 hex characters starting with `02` or `03`. + - inside `tr`, x-only pubkeys are also permitted (exactly 64 hex +characters). + - a serialized extended public key (`xpub`) (as defined in [BIP 32]( +https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)) + +The placeholder `@i` for some number *i* represents the *i*-th key in the +vector of key origin information (which must be of size at least *i* + 1, +or the wallet policy is invalid). + +A key with no origin information will be treated as external by the hardware wallet. + +### Additional rules + +The wallet policy is invalid if any placeholder expression with additional +derivation steps is used when the corresponding key information is not an +xpub. + +The key information vector *should* be ordered so that placeholder `@i` +never appear for the first time before an occurrence of `@j` for some `j < i`; for example, the first placeholder is always `@0`, the next one is +`@1`, etc. + +### Implementation-specific restrictions + +- Placeholder _must_ be followed by `/**` or `/<0;1>`. +- Key expressions only support xpubs at this time (no hex-encoded pubkeys). +- Very large policies might not be supported because of the device's memory limitations. ## Descriptor derivation -From a descriptor template (and the associated vector of keys), one can therefore obtain the descriptor for receive and change addresses by: +From a wallet descriptor template (and the associated vector of keys +information), one can therefore obtain the 1-dimensional descriptor for +receive and change addresses by: -- replacing each key placeholder with the corresponding key / key origin, and then -- replacing `/**` with either `/0/*` (receive addresses descriptor) or `/1/*` (change addresses descriptor). +- replacing each key placeholder with the corresponding key origin +information; +- replacing every `/**` with `/0/*` for the receive descriptor, and `/1/*` +for the change descriptor; +- replacing every `/` with `/M` for the receive descriptor, and `/N` +for the change descriptor. -For example, the wallet descriptor `pkh(@0)` with key information `["[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/**"]` produces the following two descriptors: +For example, the wallet descriptor `pkh(@0/**)` with key information +`["[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"]` +produces the following two descriptors: + +- Receive descriptor: +`pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)` + +- Change descriptor: +`pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)` -- Receive descriptor: `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)` -- Change descriptor: `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)` # Policy registration and usage The app supports a number of features related to wallet policies. In order to securely sign transactions with a policy wallet (for example in a multisignature), it is necessary to be able to: @@ -73,13 +142,13 @@ A registered wallet policy comprises the following: The wallet policy is serialized as the concatenation of: -- `1 byte`: a byte equal to `0x01`, reserved for future use +- `1 byte`: a byte equal to `0x02`, the version of the wallet policy language - `1 byte`: the length of the wallet name (0 for standard wallet) -- ``: the wallet name (empty for standard wallets) +- ``: the wallet name (empty for standard wallets) - ``: the length of the wallet descriptor template, encoded as a Bitcoin-style variable-length integer -- ``: the wallet descriptor template, as an ascii string (no terminating 0) +- `32 bytes`: the sha256 hash of the wallet descriptor template - ``: the number of keys in the list of keys, encoded as a Bitcoin-style variable-length integer -- `<32 bytes>`: the root of the canonical Merkle tree of the list of keys. +- `<32 bytes>`: the root of the canonical Merkle tree of the list of keys See [merkle](merkle.md) for information on Merkle trees. @@ -88,7 +157,7 @@ The sha256 hash of a serialized wallet policy is used as a *wallet policy id*. ## Wallet name The wallet name must be recognizable from the user when shown on-screen. Currently, the following limitations apply during wallet registration: -- The wallet name must be between 1 and 16 characters long. +- The wallet name must be at least 1 and at most 64 characters long. - Each character must be an ASCII character with code at least 32 = 0x20 (the 'space' character) and at most 125 = 0x7e (the '~' character). - The first and the last character must _not_ be spaces. @@ -96,27 +165,27 @@ The hardware wallet will reject registration for wallet names not respecting the ## Supported policies -As a precaution, at this time only a limited set of commonly used policies can be registered. More will be added in the future, to support new use cases. - -The following policy types are currently supported: +The following policy types are currently supported as top-level scripts: - `sh(multi(...))` and `sh(sortedmulti(...))` (legacy multisignature wallets); - `sh(wsh(multi(...)))` and `sh(wsh(sortedmulti(...)))` (wrapped-segwit multisignature wallets); -- `wsh(multi(...))` and `wsh(sortedmulti(...))` (native segwit multisignature wallets). - -## Other technical limitations +- `wsh(SCRIPT)`; +- `tr(KP)` and `tr(KP,TREE)`. -At this time, there are some technical limitations on the accepted wallet policies: -- `multi` and `sortedmulti` support at most 5 keys; +`SCRIPT` expression within `wsh` can be: +- `multi` or `sortedmulti`; +- a valid SegWit miniscript template. -These limitations will likely be removed in the future. +`SCRIPT` expression within `TREE` can be: +- `multi_a` or `sortedmulti_a`; +- a valid taproot miniscript template. # Default wallets A few policies that correspond to standardized single-key wallets can be used without requiring any registration; in the serialization, the wallet name must be a zero-length string. Those are the following policies: -- ``pkh(@0)`` - legacy addresses as per [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) -- ``wpkh(@0)`` - native segwit addresses per [BIP-84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) -- ``sh(wpkh(@0))`` - nested segwit addresses as per [BIP-49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) -- ``tr(@0)`` - single Key P2TR as per [BIP-86](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki) +- ``pkh(@0/**)`` - legacy addresses as per [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) +- ``wpkh(@0/**)`` - native segwit addresses per [BIP-84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) +- ``sh(wpkh(@0/**))`` - nested segwit addresses as per [BIP-49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) +- ``tr(@0/**)`` - single Key P2TR as per [BIP-86](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki) -Note that the wallet policy is considered standard (and therefore usable for signing without prior registration) only if the signing paths (defined in the key origin information) adheres to the corresponding BIP. +Note that the wallet policy is considered standard (and therefore usable for signing without prior registration) only if the signing paths (defined in the key origin information) adhere to the corresponding BIP. Moreover, the BIP-44 `account` level must be at most `100`, and the `address index` at most `50000`. Larger values can still be used by registering the policy. diff --git a/glyphs/Bitcoin_64px.bmp b/glyphs/Bitcoin_64px.bmp new file mode 100644 index 000000000..db706c4cc Binary files /dev/null and b/glyphs/Bitcoin_64px.bmp differ diff --git a/glyphs/blue_badge_bitcoin.gif b/glyphs/blue_badge_bitcoin.gif deleted file mode 100644 index df0c7abb1..000000000 Binary files a/glyphs/blue_badge_bitcoin.gif and /dev/null differ diff --git a/glyphs/blue_badge_bitcoin_cash.gif b/glyphs/blue_badge_bitcoin_cash.gif deleted file mode 100644 index 9a63e6d80..000000000 Binary files a/glyphs/blue_badge_bitcoin_cash.gif and /dev/null differ diff --git a/glyphs/blue_badge_bitcoin_gold.gif b/glyphs/blue_badge_bitcoin_gold.gif deleted file mode 100644 index 9a63e6d80..000000000 Binary files a/glyphs/blue_badge_bitcoin_gold.gif and /dev/null differ diff --git a/glyphs/blue_badge_bitcoin_testnet.gif b/glyphs/blue_badge_bitcoin_testnet.gif deleted file mode 100644 index df0c7abb1..000000000 Binary files a/glyphs/blue_badge_bitcoin_testnet.gif and /dev/null differ diff --git a/glyphs/blue_badge_dash.gif b/glyphs/blue_badge_dash.gif deleted file mode 100644 index 74369f560..000000000 Binary files a/glyphs/blue_badge_dash.gif and /dev/null differ diff --git a/glyphs/blue_badge_digibyte.gif b/glyphs/blue_badge_digibyte.gif deleted file mode 100644 index c485d23c6..000000000 Binary files a/glyphs/blue_badge_digibyte.gif and /dev/null differ diff --git a/glyphs/blue_badge_dogecoin.gif b/glyphs/blue_badge_dogecoin.gif deleted file mode 100644 index 41769cc67..000000000 Binary files a/glyphs/blue_badge_dogecoin.gif and /dev/null differ diff --git a/glyphs/blue_badge_komodo.gif b/glyphs/blue_badge_komodo.gif deleted file mode 100644 index 41466b8a5..000000000 Binary files a/glyphs/blue_badge_komodo.gif and /dev/null differ diff --git a/glyphs/blue_badge_lbry.gif b/glyphs/blue_badge_lbry.gif deleted file mode 100644 index c20f93828..000000000 Binary files a/glyphs/blue_badge_lbry.gif and /dev/null differ diff --git a/glyphs/blue_badge_litecoin.gif b/glyphs/blue_badge_litecoin.gif deleted file mode 100644 index de0095b68..000000000 Binary files a/glyphs/blue_badge_litecoin.gif and /dev/null differ diff --git a/glyphs/blue_badge_peercoin.gif b/glyphs/blue_badge_peercoin.gif deleted file mode 100644 index 1c39643d7..000000000 Binary files a/glyphs/blue_badge_peercoin.gif and /dev/null differ diff --git a/glyphs/blue_badge_pivx.gif b/glyphs/blue_badge_pivx.gif deleted file mode 100644 index 62db7bdde..000000000 Binary files a/glyphs/blue_badge_pivx.gif and /dev/null differ diff --git a/glyphs/blue_badge_qtum.gif b/glyphs/blue_badge_qtum.gif deleted file mode 100644 index d48701d0b..000000000 Binary files a/glyphs/blue_badge_qtum.gif and /dev/null differ diff --git a/glyphs/blue_badge_ravencoin.gif b/glyphs/blue_badge_ravencoin.gif deleted file mode 100644 index e02647f6f..000000000 Binary files a/glyphs/blue_badge_ravencoin.gif and /dev/null differ diff --git a/glyphs/blue_badge_resistance.gif b/glyphs/blue_badge_resistance.gif deleted file mode 100644 index 0d0691015..000000000 Binary files a/glyphs/blue_badge_resistance.gif and /dev/null differ diff --git a/glyphs/blue_badge_stealth.gif b/glyphs/blue_badge_stealth.gif deleted file mode 100644 index c3b2324b5..000000000 Binary files a/glyphs/blue_badge_stealth.gif and /dev/null differ diff --git a/glyphs/blue_badge_stratis.gif b/glyphs/blue_badge_stratis.gif deleted file mode 100644 index a01878e4a..000000000 Binary files a/glyphs/blue_badge_stratis.gif and /dev/null differ diff --git a/glyphs/blue_badge_transaction.gif b/glyphs/blue_badge_transaction.gif deleted file mode 100644 index 131325d4a..000000000 Binary files a/glyphs/blue_badge_transaction.gif and /dev/null differ diff --git a/glyphs/blue_badge_vertcoin.gif b/glyphs/blue_badge_vertcoin.gif deleted file mode 100644 index 2f47fc15b..000000000 Binary files a/glyphs/blue_badge_vertcoin.gif and /dev/null differ diff --git a/glyphs/blue_badge_viacoin.gif b/glyphs/blue_badge_viacoin.gif deleted file mode 100644 index 1732c1127..000000000 Binary files a/glyphs/blue_badge_viacoin.gif and /dev/null differ diff --git a/glyphs/blue_badge_warning.gif b/glyphs/blue_badge_warning.gif deleted file mode 100644 index 37c7ed5be..000000000 Binary files a/glyphs/blue_badge_warning.gif and /dev/null differ diff --git a/glyphs/blue_badge_zcash.gif b/glyphs/blue_badge_zcash.gif deleted file mode 100644 index 0d0691015..000000000 Binary files a/glyphs/blue_badge_zcash.gif and /dev/null differ diff --git a/glyphs/blue_icon_toggle_reset.gif b/glyphs/blue_icon_toggle_reset.gif deleted file mode 100644 index 450bc869d..000000000 Binary files a/glyphs/blue_icon_toggle_reset.gif and /dev/null differ diff --git a/glyphs/blue_icon_toggle_set.gif b/glyphs/blue_icon_toggle_set.gif deleted file mode 100644 index 571264c79..000000000 Binary files a/glyphs/blue_icon_toggle_set.gif and /dev/null differ diff --git a/glyphs/nanos_badge_bitcoin_cash.gif b/glyphs/nanos_badge_bitcoin_cash.gif deleted file mode 100644 index d43e12f59..000000000 Binary files a/glyphs/nanos_badge_bitcoin_cash.gif and /dev/null differ diff --git a/glyphs/nanos_badge_bitcoin_gold.gif b/glyphs/nanos_badge_bitcoin_gold.gif deleted file mode 100644 index d43e12f59..000000000 Binary files a/glyphs/nanos_badge_bitcoin_gold.gif and /dev/null differ diff --git a/glyphs/nanos_badge_bitcoin_private.gif b/glyphs/nanos_badge_bitcoin_private.gif deleted file mode 100644 index 51d457525..000000000 Binary files a/glyphs/nanos_badge_bitcoin_private.gif and /dev/null differ diff --git a/glyphs/nanos_badge_dash.gif b/glyphs/nanos_badge_dash.gif deleted file mode 100644 index 9d309cdfc..000000000 Binary files a/glyphs/nanos_badge_dash.gif and /dev/null differ diff --git a/glyphs/nanos_badge_digibyte.gif b/glyphs/nanos_badge_digibyte.gif deleted file mode 100644 index f94460155..000000000 Binary files a/glyphs/nanos_badge_digibyte.gif and /dev/null differ diff --git a/glyphs/nanos_badge_dogecoin.gif b/glyphs/nanos_badge_dogecoin.gif deleted file mode 100644 index 4f97c60c7..000000000 Binary files a/glyphs/nanos_badge_dogecoin.gif and /dev/null differ diff --git a/glyphs/nanos_badge_komodo.gif b/glyphs/nanos_badge_komodo.gif deleted file mode 100644 index 2d22e51d1..000000000 Binary files a/glyphs/nanos_badge_komodo.gif and /dev/null differ diff --git a/glyphs/nanos_badge_lbry.gif b/glyphs/nanos_badge_lbry.gif deleted file mode 100644 index 01d3e10fd..000000000 Binary files a/glyphs/nanos_badge_lbry.gif and /dev/null differ diff --git a/glyphs/nanos_badge_litecoin.gif b/glyphs/nanos_badge_litecoin.gif deleted file mode 100644 index db32a5cef..000000000 Binary files a/glyphs/nanos_badge_litecoin.gif and /dev/null differ diff --git a/glyphs/nanos_badge_nix.gif b/glyphs/nanos_badge_nix.gif deleted file mode 100644 index a5918635f..000000000 Binary files a/glyphs/nanos_badge_nix.gif and /dev/null differ diff --git a/glyphs/nanos_badge_peercoin.gif b/glyphs/nanos_badge_peercoin.gif deleted file mode 100644 index 89ae64d7c..000000000 Binary files a/glyphs/nanos_badge_peercoin.gif and /dev/null differ diff --git a/glyphs/nanos_badge_pivx.gif b/glyphs/nanos_badge_pivx.gif deleted file mode 100644 index 0af1467dc..000000000 Binary files a/glyphs/nanos_badge_pivx.gif and /dev/null differ diff --git a/glyphs/nanos_badge_qtum.gif b/glyphs/nanos_badge_qtum.gif deleted file mode 100644 index 0559f39d1..000000000 Binary files a/glyphs/nanos_badge_qtum.gif and /dev/null differ diff --git a/glyphs/nanos_badge_ravencoin.gif b/glyphs/nanos_badge_ravencoin.gif deleted file mode 100644 index 5fd1e385d..000000000 Binary files a/glyphs/nanos_badge_ravencoin.gif and /dev/null differ diff --git a/glyphs/nanos_badge_resistance.gif b/glyphs/nanos_badge_resistance.gif deleted file mode 100644 index 36551ab04..000000000 Binary files a/glyphs/nanos_badge_resistance.gif and /dev/null differ diff --git a/glyphs/nanos_badge_stealth.gif b/glyphs/nanos_badge_stealth.gif deleted file mode 100644 index 6c75a7ca9..000000000 Binary files a/glyphs/nanos_badge_stealth.gif and /dev/null differ diff --git a/glyphs/nanos_badge_stratis.gif b/glyphs/nanos_badge_stratis.gif deleted file mode 100644 index bb0841bce..000000000 Binary files a/glyphs/nanos_badge_stratis.gif and /dev/null differ diff --git a/glyphs/nanos_badge_vertcoin.gif b/glyphs/nanos_badge_vertcoin.gif deleted file mode 100644 index eea49cccf..000000000 Binary files a/glyphs/nanos_badge_vertcoin.gif and /dev/null differ diff --git a/glyphs/nanos_badge_viacoin.gif b/glyphs/nanos_badge_viacoin.gif deleted file mode 100644 index 869a399cf..000000000 Binary files a/glyphs/nanos_badge_viacoin.gif and /dev/null differ diff --git a/glyphs/nanos_badge_xsn.gif b/glyphs/nanos_badge_xsn.gif deleted file mode 100644 index cd3ea5f09..000000000 Binary files a/glyphs/nanos_badge_xsn.gif and /dev/null differ diff --git a/glyphs/nanos_badge_zcash.gif b/glyphs/nanos_badge_zcash.gif deleted file mode 100644 index 36551ab04..000000000 Binary files a/glyphs/nanos_badge_zcash.gif and /dev/null differ diff --git a/glyphs/nanos_badge_zcoin.gif b/glyphs/nanos_badge_zcoin.gif deleted file mode 100644 index 0f1ec3ccb..000000000 Binary files a/glyphs/nanos_badge_zcoin.gif and /dev/null differ diff --git a/glyphs/nanos_badge_zencash.gif b/glyphs/nanos_badge_zencash.gif deleted file mode 100644 index d90503dc0..000000000 Binary files a/glyphs/nanos_badge_zencash.gif and /dev/null differ diff --git a/icons/bitcoin_cash.png b/icons/bitcoin_cash.png deleted file mode 100644 index c26f03e4b..000000000 Binary files a/icons/bitcoin_cash.png and /dev/null differ diff --git a/icons/bitcoin_gold.png b/icons/bitcoin_gold.png deleted file mode 100644 index 6f1d0fd83..000000000 Binary files a/icons/bitcoin_gold.png and /dev/null differ diff --git a/icons/bitcoin_private.png b/icons/bitcoin_private.png deleted file mode 100644 index e132ee6fa..000000000 Binary files a/icons/bitcoin_private.png and /dev/null differ diff --git a/icons/bitcoin_testnet.png b/icons/bitcoin_testnet.png deleted file mode 100644 index 32778bcd6..000000000 Binary files a/icons/bitcoin_testnet.png and /dev/null differ diff --git a/icons/blue_app_bitcoin.gif b/icons/blue_app_bitcoin.gif deleted file mode 100644 index beec02c64..000000000 Binary files a/icons/blue_app_bitcoin.gif and /dev/null differ diff --git a/icons/blue_app_bitcoin_cash.gif b/icons/blue_app_bitcoin_cash.gif deleted file mode 100644 index adcfbb63b..000000000 Binary files a/icons/blue_app_bitcoin_cash.gif and /dev/null differ diff --git a/icons/blue_app_bitcoin_gold.gif b/icons/blue_app_bitcoin_gold.gif deleted file mode 100644 index 00c6c385e..000000000 Binary files a/icons/blue_app_bitcoin_gold.gif and /dev/null differ diff --git a/icons/blue_app_bitcoin_private.gif b/icons/blue_app_bitcoin_private.gif deleted file mode 100644 index 3d528fc3d..000000000 Binary files a/icons/blue_app_bitcoin_private.gif and /dev/null differ diff --git a/icons/blue_app_bitcoin_testnet.gif b/icons/blue_app_bitcoin_testnet.gif deleted file mode 100644 index beec02c64..000000000 Binary files a/icons/blue_app_bitcoin_testnet.gif and /dev/null differ diff --git a/icons/blue_app_dash.gif b/icons/blue_app_dash.gif deleted file mode 100644 index abf0fd0e8..000000000 Binary files a/icons/blue_app_dash.gif and /dev/null differ diff --git a/icons/blue_app_digibyte.gif b/icons/blue_app_digibyte.gif deleted file mode 100644 index 2ec2174ee..000000000 Binary files a/icons/blue_app_digibyte.gif and /dev/null differ diff --git a/icons/blue_app_dogecoin.gif b/icons/blue_app_dogecoin.gif deleted file mode 100644 index 880ca163a..000000000 Binary files a/icons/blue_app_dogecoin.gif and /dev/null differ diff --git a/icons/blue_app_firo.gif b/icons/blue_app_firo.gif deleted file mode 100644 index 4d11cc865..000000000 Binary files a/icons/blue_app_firo.gif and /dev/null differ diff --git a/icons/blue_app_gamecredits.gif b/icons/blue_app_gamecredits.gif deleted file mode 100644 index 312e3844b..000000000 Binary files a/icons/blue_app_gamecredits.gif and /dev/null differ diff --git a/icons/blue_app_horizen.gif b/icons/blue_app_horizen.gif deleted file mode 100644 index 9828e14ba..000000000 Binary files a/icons/blue_app_horizen.gif and /dev/null differ diff --git a/icons/blue_app_komodo.gif b/icons/blue_app_komodo.gif deleted file mode 100644 index 57586bbf4..000000000 Binary files a/icons/blue_app_komodo.gif and /dev/null differ diff --git a/icons/blue_app_lbry.gif b/icons/blue_app_lbry.gif deleted file mode 100644 index 0360a35e1..000000000 Binary files a/icons/blue_app_lbry.gif and /dev/null differ diff --git a/icons/blue_app_litecoin.gif b/icons/blue_app_litecoin.gif deleted file mode 100644 index 62441b871..000000000 Binary files a/icons/blue_app_litecoin.gif and /dev/null differ diff --git a/icons/blue_app_nix.gif b/icons/blue_app_nix.gif deleted file mode 100644 index d258f2d23..000000000 Binary files a/icons/blue_app_nix.gif and /dev/null differ diff --git a/icons/blue_app_peercoin.gif b/icons/blue_app_peercoin.gif deleted file mode 100644 index 337538f9a..000000000 Binary files a/icons/blue_app_peercoin.gif and /dev/null differ diff --git a/icons/blue_app_pivx.gif b/icons/blue_app_pivx.gif deleted file mode 100644 index 3f3a94bb2..000000000 Binary files a/icons/blue_app_pivx.gif and /dev/null differ diff --git a/icons/blue_app_qtum.gif b/icons/blue_app_qtum.gif deleted file mode 100644 index 4c0194c37..000000000 Binary files a/icons/blue_app_qtum.gif and /dev/null differ diff --git a/icons/blue_app_ravencoin.gif b/icons/blue_app_ravencoin.gif deleted file mode 100644 index 3ff36c212..000000000 Binary files a/icons/blue_app_ravencoin.gif and /dev/null differ diff --git a/icons/blue_app_resistance.gif b/icons/blue_app_resistance.gif deleted file mode 100644 index 6b9609878..000000000 Binary files a/icons/blue_app_resistance.gif and /dev/null differ diff --git a/icons/blue_app_stealth.gif b/icons/blue_app_stealth.gif deleted file mode 100644 index 30e1733e3..000000000 Binary files a/icons/blue_app_stealth.gif and /dev/null differ diff --git a/icons/blue_app_stratis.gif b/icons/blue_app_stratis.gif deleted file mode 100644 index 3121749c6..000000000 Binary files a/icons/blue_app_stratis.gif and /dev/null differ diff --git a/icons/blue_app_vertcoin.gif b/icons/blue_app_vertcoin.gif deleted file mode 100644 index 03a45f0b5..000000000 Binary files a/icons/blue_app_vertcoin.gif and /dev/null differ diff --git a/icons/blue_app_viacoin.gif b/icons/blue_app_viacoin.gif deleted file mode 100644 index 26fa445f2..000000000 Binary files a/icons/blue_app_viacoin.gif and /dev/null differ diff --git a/icons/blue_app_xrhodium.gif b/icons/blue_app_xrhodium.gif deleted file mode 100644 index 766ab1610..000000000 Binary files a/icons/blue_app_xrhodium.gif and /dev/null differ diff --git a/icons/blue_app_xsn.gif b/icons/blue_app_xsn.gif deleted file mode 100644 index 68e9e43a6..000000000 Binary files a/icons/blue_app_xsn.gif and /dev/null differ diff --git a/icons/blue_app_zcash.gif b/icons/blue_app_zcash.gif deleted file mode 100644 index 980cded6b..000000000 Binary files a/icons/blue_app_zcash.gif and /dev/null differ diff --git a/icons/blue_app_zclassic.gif b/icons/blue_app_zclassic.gif deleted file mode 100644 index 950aad758..000000000 Binary files a/icons/blue_app_zclassic.gif and /dev/null differ diff --git a/icons/dash.png b/icons/dash.png deleted file mode 100644 index 779385d79..000000000 Binary files a/icons/dash.png and /dev/null differ diff --git a/icons/digibyte.png b/icons/digibyte.png deleted file mode 100644 index 0efa85de1..000000000 Binary files a/icons/digibyte.png and /dev/null differ diff --git a/icons/dogecoin.png b/icons/dogecoin.png deleted file mode 100644 index 8c26b2d55..000000000 Binary files a/icons/dogecoin.png and /dev/null differ diff --git a/icons/firo.png b/icons/firo.png deleted file mode 100644 index 0cc40eaba..000000000 Binary files a/icons/firo.png and /dev/null differ diff --git a/icons/gamecredits.png b/icons/gamecredits.png deleted file mode 100644 index a85a4648a..000000000 Binary files a/icons/gamecredits.png and /dev/null differ diff --git a/icons/horizen.png b/icons/horizen.png deleted file mode 100644 index 441648493..000000000 Binary files a/icons/horizen.png and /dev/null differ diff --git a/icons/komodo.png b/icons/komodo.png deleted file mode 100644 index c6e4f2045..000000000 Binary files a/icons/komodo.png and /dev/null differ diff --git a/icons/lbry.png b/icons/lbry.png deleted file mode 100644 index 3c5959e34..000000000 Binary files a/icons/lbry.png and /dev/null differ diff --git a/icons/litecoin.png b/icons/litecoin.png deleted file mode 100644 index c1ffffbb9..000000000 Binary files a/icons/litecoin.png and /dev/null differ diff --git a/icons/nanos_app_bitcoin_cash.gif b/icons/nanos_app_bitcoin_cash.gif deleted file mode 100644 index 9fa736b19..000000000 Binary files a/icons/nanos_app_bitcoin_cash.gif and /dev/null differ diff --git a/icons/nanos_app_bitcoin_gold.gif b/icons/nanos_app_bitcoin_gold.gif deleted file mode 100644 index 9fa736b19..000000000 Binary files a/icons/nanos_app_bitcoin_gold.gif and /dev/null differ diff --git a/icons/nanos_app_bitcoin_lite.gif b/icons/nanos_app_bitcoin_lite.gif deleted file mode 100644 index 9fa736b19..000000000 Binary files a/icons/nanos_app_bitcoin_lite.gif and /dev/null differ diff --git a/icons/nanos_app_bitcoin_private.gif b/icons/nanos_app_bitcoin_private.gif deleted file mode 100644 index 8eae073e5..000000000 Binary files a/icons/nanos_app_bitcoin_private.gif and /dev/null differ diff --git a/icons/nanos_app_bitcoin_testnet.gif b/icons/nanos_app_bitcoin_testnet.gif deleted file mode 100644 index 9fa736b19..000000000 Binary files a/icons/nanos_app_bitcoin_testnet.gif and /dev/null differ diff --git a/icons/nanos_app_bitcoin_testnet_lib.gif b/icons/nanos_app_bitcoin_testnet_lib.gif deleted file mode 100644 index 9fa736b19..000000000 Binary files a/icons/nanos_app_bitcoin_testnet_lib.gif and /dev/null differ diff --git a/icons/nanos_app_bitcoin_testnet_lite.gif b/icons/nanos_app_bitcoin_testnet_lite.gif deleted file mode 100644 index 9fa736b19..000000000 Binary files a/icons/nanos_app_bitcoin_testnet_lite.gif and /dev/null differ diff --git a/icons/nanos_app_dash.gif b/icons/nanos_app_dash.gif deleted file mode 100644 index 77813957f..000000000 Binary files a/icons/nanos_app_dash.gif and /dev/null differ diff --git a/icons/nanos_app_digibyte.gif b/icons/nanos_app_digibyte.gif deleted file mode 100644 index 71d41a09c..000000000 Binary files a/icons/nanos_app_digibyte.gif and /dev/null differ diff --git a/icons/nanos_app_dogecoin.gif b/icons/nanos_app_dogecoin.gif deleted file mode 100644 index 82596997a..000000000 Binary files a/icons/nanos_app_dogecoin.gif and /dev/null differ diff --git a/icons/nanos_app_firo.gif b/icons/nanos_app_firo.gif deleted file mode 100644 index f12e83e97..000000000 Binary files a/icons/nanos_app_firo.gif and /dev/null differ diff --git a/icons/nanos_app_gamecredits.gif b/icons/nanos_app_gamecredits.gif deleted file mode 100644 index ce780fc50..000000000 Binary files a/icons/nanos_app_gamecredits.gif and /dev/null differ diff --git a/icons/nanos_app_horizen.gif b/icons/nanos_app_horizen.gif deleted file mode 100644 index c8e2bd004..000000000 Binary files a/icons/nanos_app_horizen.gif and /dev/null differ diff --git a/icons/nanos_app_hydra.gif b/icons/nanos_app_hydra.gif deleted file mode 100644 index 5675f3999..000000000 Binary files a/icons/nanos_app_hydra.gif and /dev/null differ diff --git a/icons/nanos_app_hydra_testnet.gif b/icons/nanos_app_hydra_testnet.gif deleted file mode 100644 index 5675f3999..000000000 Binary files a/icons/nanos_app_hydra_testnet.gif and /dev/null differ diff --git a/icons/nanos_app_komodo.gif b/icons/nanos_app_komodo.gif deleted file mode 100644 index c0c2536f3..000000000 Binary files a/icons/nanos_app_komodo.gif and /dev/null differ diff --git a/icons/nanos_app_lbry.gif b/icons/nanos_app_lbry.gif deleted file mode 100644 index 64a44ae3a..000000000 Binary files a/icons/nanos_app_lbry.gif and /dev/null differ diff --git a/icons/nanos_app_liquid_headless.gif b/icons/nanos_app_liquid_headless.gif deleted file mode 100644 index 2f41595a2..000000000 Binary files a/icons/nanos_app_liquid_headless.gif and /dev/null differ diff --git a/icons/nanos_app_liquid_regtest.gif b/icons/nanos_app_liquid_regtest.gif deleted file mode 100644 index 2f41595a2..000000000 Binary files a/icons/nanos_app_liquid_regtest.gif and /dev/null differ diff --git a/icons/nanos_app_liquid_regtest_headless.gif b/icons/nanos_app_liquid_regtest_headless.gif deleted file mode 100644 index 2f41595a2..000000000 Binary files a/icons/nanos_app_liquid_regtest_headless.gif and /dev/null differ diff --git a/icons/nanos_app_litecoin.gif b/icons/nanos_app_litecoin.gif deleted file mode 100644 index 6b13d8117..000000000 Binary files a/icons/nanos_app_litecoin.gif and /dev/null differ diff --git a/icons/nanos_app_nix.gif b/icons/nanos_app_nix.gif deleted file mode 100644 index 12700c593..000000000 Binary files a/icons/nanos_app_nix.gif and /dev/null differ diff --git a/icons/nanos_app_peercoin.gif b/icons/nanos_app_peercoin.gif deleted file mode 100644 index 3c32b993e..000000000 Binary files a/icons/nanos_app_peercoin.gif and /dev/null differ diff --git a/icons/nanos_app_pivx.gif b/icons/nanos_app_pivx.gif deleted file mode 100644 index 30ef95a98..000000000 Binary files a/icons/nanos_app_pivx.gif and /dev/null differ diff --git a/icons/nanos_app_qtum.gif b/icons/nanos_app_qtum.gif deleted file mode 100644 index 7e2c908c3..000000000 Binary files a/icons/nanos_app_qtum.gif and /dev/null differ diff --git a/icons/nanos_app_ravencoin.gif b/icons/nanos_app_ravencoin.gif deleted file mode 100644 index 630d7c04b..000000000 Binary files a/icons/nanos_app_ravencoin.gif and /dev/null differ diff --git a/icons/nanos_app_resistance.gif b/icons/nanos_app_resistance.gif deleted file mode 100644 index e702f7793..000000000 Binary files a/icons/nanos_app_resistance.gif and /dev/null differ diff --git a/icons/nanos_app_stealth.gif b/icons/nanos_app_stealth.gif deleted file mode 100644 index 64106dbed..000000000 Binary files a/icons/nanos_app_stealth.gif and /dev/null differ diff --git a/icons/nanos_app_stratis.gif b/icons/nanos_app_stratis.gif deleted file mode 100644 index a1e85df6d..000000000 Binary files a/icons/nanos_app_stratis.gif and /dev/null differ diff --git a/icons/nanos_app_vertcoin.gif b/icons/nanos_app_vertcoin.gif deleted file mode 100644 index 7b6fa60d3..000000000 Binary files a/icons/nanos_app_vertcoin.gif and /dev/null differ diff --git a/icons/nanos_app_viacoin.gif b/icons/nanos_app_viacoin.gif deleted file mode 100644 index 015bb8bfd..000000000 Binary files a/icons/nanos_app_viacoin.gif and /dev/null differ diff --git a/icons/nanos_app_xrhodium.gif b/icons/nanos_app_xrhodium.gif deleted file mode 100644 index 8e9e2318b..000000000 Binary files a/icons/nanos_app_xrhodium.gif and /dev/null differ diff --git a/icons/nanos_app_xsn.gif b/icons/nanos_app_xsn.gif deleted file mode 100644 index 8819a7db1..000000000 Binary files a/icons/nanos_app_xsn.gif and /dev/null differ diff --git a/icons/nanos_app_zcash.gif b/icons/nanos_app_zcash.gif deleted file mode 100644 index 826a1baaf..000000000 Binary files a/icons/nanos_app_zcash.gif and /dev/null differ diff --git a/icons/nanos_app_zclassic.gif b/icons/nanos_app_zclassic.gif deleted file mode 100644 index 254c648d2..000000000 Binary files a/icons/nanos_app_zclassic.gif and /dev/null differ diff --git a/icons/nanox_app_bitcoin_cash.gif b/icons/nanox_app_bitcoin_cash.gif deleted file mode 100644 index 7a92ac33e..000000000 Binary files a/icons/nanox_app_bitcoin_cash.gif and /dev/null differ diff --git a/icons/nanox_app_bitcoin_gold.gif b/icons/nanox_app_bitcoin_gold.gif deleted file mode 100644 index 7a92ac33e..000000000 Binary files a/icons/nanox_app_bitcoin_gold.gif and /dev/null differ diff --git a/icons/nanox_app_bitcoin_private.gif b/icons/nanox_app_bitcoin_private.gif deleted file mode 100644 index 389957ca6..000000000 Binary files a/icons/nanox_app_bitcoin_private.gif and /dev/null differ diff --git a/icons/nanox_app_bitcoin_testnet.gif b/icons/nanox_app_bitcoin_testnet.gif deleted file mode 100644 index 7a92ac33e..000000000 Binary files a/icons/nanox_app_bitcoin_testnet.gif and /dev/null differ diff --git a/icons/nanox_app_bitcoin_testnet_lib.gif b/icons/nanox_app_bitcoin_testnet_lib.gif deleted file mode 100644 index 7a92ac33e..000000000 Binary files a/icons/nanox_app_bitcoin_testnet_lib.gif and /dev/null differ diff --git a/icons/nanox_app_dash.gif b/icons/nanox_app_dash.gif deleted file mode 100644 index f51615270..000000000 Binary files a/icons/nanox_app_dash.gif and /dev/null differ diff --git a/icons/nanox_app_digibyte.gif b/icons/nanox_app_digibyte.gif deleted file mode 100644 index e3e339d10..000000000 Binary files a/icons/nanox_app_digibyte.gif and /dev/null differ diff --git a/icons/nanox_app_dogecoin.gif b/icons/nanox_app_dogecoin.gif deleted file mode 100644 index 8bd529964..000000000 Binary files a/icons/nanox_app_dogecoin.gif and /dev/null differ diff --git a/icons/nanox_app_firo.gif b/icons/nanox_app_firo.gif deleted file mode 100644 index fa3c8a263..000000000 Binary files a/icons/nanox_app_firo.gif and /dev/null differ diff --git a/icons/nanox_app_gamecredits.gif b/icons/nanox_app_gamecredits.gif deleted file mode 100644 index c034ccec0..000000000 Binary files a/icons/nanox_app_gamecredits.gif and /dev/null differ diff --git a/icons/nanox_app_horizen.gif b/icons/nanox_app_horizen.gif deleted file mode 100644 index 5dd7b1da2..000000000 Binary files a/icons/nanox_app_horizen.gif and /dev/null differ diff --git a/icons/nanox_app_hydra.gif b/icons/nanox_app_hydra.gif deleted file mode 100644 index 9688696fc..000000000 Binary files a/icons/nanox_app_hydra.gif and /dev/null differ diff --git a/icons/nanox_app_hydra_testnet.gif b/icons/nanox_app_hydra_testnet.gif deleted file mode 100644 index 9688696fc..000000000 Binary files a/icons/nanox_app_hydra_testnet.gif and /dev/null differ diff --git a/icons/nanox_app_komodo.gif b/icons/nanox_app_komodo.gif deleted file mode 100644 index 347399434..000000000 Binary files a/icons/nanox_app_komodo.gif and /dev/null differ diff --git a/icons/nanox_app_lbry.gif b/icons/nanox_app_lbry.gif deleted file mode 100644 index 8d64a506b..000000000 Binary files a/icons/nanox_app_lbry.gif and /dev/null differ diff --git a/icons/nanox_app_liquid_headless.gif b/icons/nanox_app_liquid_headless.gif deleted file mode 100644 index 9fb840e57..000000000 Binary files a/icons/nanox_app_liquid_headless.gif and /dev/null differ diff --git a/icons/nanox_app_liquid_regtest.gif b/icons/nanox_app_liquid_regtest.gif deleted file mode 100644 index 9fb840e57..000000000 Binary files a/icons/nanox_app_liquid_regtest.gif and /dev/null differ diff --git a/icons/nanox_app_liquid_regtest_headless.gif b/icons/nanox_app_liquid_regtest_headless.gif deleted file mode 100644 index 9fb840e57..000000000 Binary files a/icons/nanox_app_liquid_regtest_headless.gif and /dev/null differ diff --git a/icons/nanox_app_litecoin.gif b/icons/nanox_app_litecoin.gif deleted file mode 100644 index ee77dfc2b..000000000 Binary files a/icons/nanox_app_litecoin.gif and /dev/null differ diff --git a/icons/nanox_app_nix.gif b/icons/nanox_app_nix.gif deleted file mode 100644 index 2c5c0e49f..000000000 Binary files a/icons/nanox_app_nix.gif and /dev/null differ diff --git a/icons/nanox_app_peercoin.gif b/icons/nanox_app_peercoin.gif deleted file mode 100644 index c8f62ff0b..000000000 Binary files a/icons/nanox_app_peercoin.gif and /dev/null differ diff --git a/icons/nanox_app_pivx.gif b/icons/nanox_app_pivx.gif deleted file mode 100644 index ff675527f..000000000 Binary files a/icons/nanox_app_pivx.gif and /dev/null differ diff --git a/icons/nanox_app_qtum.gif b/icons/nanox_app_qtum.gif deleted file mode 100644 index 225ef6f2b..000000000 Binary files a/icons/nanox_app_qtum.gif and /dev/null differ diff --git a/icons/nanox_app_ravencoin.gif b/icons/nanox_app_ravencoin.gif deleted file mode 100644 index 13146cffe..000000000 Binary files a/icons/nanox_app_ravencoin.gif and /dev/null differ diff --git a/icons/nanox_app_stealth.gif b/icons/nanox_app_stealth.gif deleted file mode 100644 index 3d72b11f3..000000000 Binary files a/icons/nanox_app_stealth.gif and /dev/null differ diff --git a/icons/nanox_app_stratis.gif b/icons/nanox_app_stratis.gif deleted file mode 100644 index ef31281fc..000000000 Binary files a/icons/nanox_app_stratis.gif and /dev/null differ diff --git a/icons/nanox_app_vertcoin.gif b/icons/nanox_app_vertcoin.gif deleted file mode 100644 index e8cc4e8df..000000000 Binary files a/icons/nanox_app_vertcoin.gif and /dev/null differ diff --git a/icons/nanox_app_viacoin.gif b/icons/nanox_app_viacoin.gif deleted file mode 100644 index 5a2c945cc..000000000 Binary files a/icons/nanox_app_viacoin.gif and /dev/null differ diff --git a/icons/nanox_app_xrhodium.gif b/icons/nanox_app_xrhodium.gif deleted file mode 100644 index 640287c0c..000000000 Binary files a/icons/nanox_app_xrhodium.gif and /dev/null differ diff --git a/icons/nanox_app_xsn.gif b/icons/nanox_app_xsn.gif deleted file mode 100644 index 92f4a6a28..000000000 Binary files a/icons/nanox_app_xsn.gif and /dev/null differ diff --git a/icons/nanox_app_zcash.gif b/icons/nanox_app_zcash.gif deleted file mode 100644 index 405aa9e55..000000000 Binary files a/icons/nanox_app_zcash.gif and /dev/null differ diff --git a/icons/nanox_app_zclassic.gif b/icons/nanox_app_zclassic.gif deleted file mode 100644 index 7806efa53..000000000 Binary files a/icons/nanox_app_zclassic.gif and /dev/null differ diff --git a/icons/nix.png b/icons/nix.png deleted file mode 100644 index 915c02430..000000000 Binary files a/icons/nix.png and /dev/null differ diff --git a/icons/peercoin.png b/icons/peercoin.png deleted file mode 100644 index 55d3c46cc..000000000 Binary files a/icons/peercoin.png and /dev/null differ diff --git a/icons/pivx.png b/icons/pivx.png deleted file mode 100644 index 87ad7254c..000000000 Binary files a/icons/pivx.png and /dev/null differ diff --git a/icons/qtum.png b/icons/qtum.png deleted file mode 100644 index 0f154f9bf..000000000 Binary files a/icons/qtum.png and /dev/null differ diff --git a/icons/ravencoin.png b/icons/ravencoin.png deleted file mode 100644 index a436acb2d..000000000 Binary files a/icons/ravencoin.png and /dev/null differ diff --git a/icons/resistance.png b/icons/resistance.png deleted file mode 100644 index 02b398830..000000000 Binary files a/icons/resistance.png and /dev/null differ diff --git a/icons/stax_app_bitcoin.gif b/icons/stax_app_bitcoin.gif new file mode 100644 index 000000000..99915d7c5 Binary files /dev/null and b/icons/stax_app_bitcoin.gif differ diff --git a/icons/stax_app_liquid.gif b/icons/stax_app_liquid.gif new file mode 100644 index 000000000..12edda648 Binary files /dev/null and b/icons/stax_app_liquid.gif differ diff --git a/icons/stealth.png b/icons/stealth.png deleted file mode 100644 index 6cac62b53..000000000 Binary files a/icons/stealth.png and /dev/null differ diff --git a/icons/stratis.png b/icons/stratis.png deleted file mode 100644 index 53afb78cb..000000000 Binary files a/icons/stratis.png and /dev/null differ diff --git a/icons/vertcoin.png b/icons/vertcoin.png deleted file mode 100644 index 8ed079358..000000000 Binary files a/icons/vertcoin.png and /dev/null differ diff --git a/icons/viacoin.png b/icons/viacoin.png deleted file mode 100644 index caae567c1..000000000 Binary files a/icons/viacoin.png and /dev/null differ diff --git a/icons/xrhodium.png b/icons/xrhodium.png deleted file mode 100644 index 7ebf60864..000000000 Binary files a/icons/xrhodium.png and /dev/null differ diff --git a/icons/xsn.png b/icons/xsn.png deleted file mode 100644 index f5c43ada5..000000000 Binary files a/icons/xsn.png and /dev/null differ diff --git a/icons/zcash.png b/icons/zcash.png deleted file mode 100644 index 015ec408d..000000000 Binary files a/icons/zcash.png and /dev/null differ diff --git a/icons/zclassic.png b/icons/zclassic.png deleted file mode 100644 index ea554440b..000000000 Binary files a/icons/zclassic.png and /dev/null differ diff --git a/ledger_app.toml b/ledger_app.toml new file mode 100644 index 000000000..e557cc8f9 --- /dev/null +++ b/ledger_app.toml @@ -0,0 +1,8 @@ +[app] +build_directory = "./" +sdk = "C" +devices = ["nanos", "nanox", "nanos+", "stax"] + +[tests] +unit_directory = "./unit-tests/" +pytest_directory = "./tests/" diff --git a/ragger_bitcoin/README.md b/ragger_bitcoin/README.md new file mode 100644 index 000000000..ae9c2f4c5 --- /dev/null +++ b/ragger_bitcoin/README.md @@ -0,0 +1 @@ +# Ragger wrapper for Ledger Bitcoin application client \ No newline at end of file diff --git a/ragger_bitcoin/__init__.py b/ragger_bitcoin/__init__.py new file mode 100644 index 000000000..516a775f7 --- /dev/null +++ b/ragger_bitcoin/__init__.py @@ -0,0 +1,5 @@ +"""Ragger wrapper for Ledger Nano Bitcoin client""" + +from .ragger_bitcoin import createRaggerClient, RaggerClient +from .ragger_instructions import Instructions +__version__ = '0.0.1' diff --git a/ragger_bitcoin/pyproject.toml b/ragger_bitcoin/pyproject.toml new file mode 100644 index 000000000..3324695e2 --- /dev/null +++ b/ragger_bitcoin/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = [ + "setuptools>=45", + "wheel" +] + +[tool.mypy] +ignore_missing_imports = true + +[tool.yapf] +based_on_style = "pep8" +column_limit = 100 + +[tool.coverage.report] +show_missing = true +exclude_lines = [ + "@abstractmethod", + "pragma: no cover" +] diff --git a/ragger_bitcoin/ragger_bitcoin.py b/ragger_bitcoin/ragger_bitcoin.py new file mode 100644 index 000000000..13d11b4b4 --- /dev/null +++ b/ragger_bitcoin/ragger_bitcoin.py @@ -0,0 +1,221 @@ +from typing import Tuple, List, Optional, Union +from pathlib import Path + +from ledger_bitcoin.common import Chain +from ledger_bitcoin.client_command import ClientCommandInterpreter +from ledger_bitcoin.client_base import TransportClient, PartialSignature +from ledger_bitcoin.wallet import WalletPolicy +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.client import NewClient +from ledger_bitcoin.client_base import print_response, print_apdu, ApduException + +from ragger.navigator import Navigator +from ragger_bitcoin.ragger_instructions import Instructions + +TESTS_ROOT_DIR = Path(__file__).parent + + +class RaggerClient(NewClient): + def __init__(self, comm_client: TransportClient, chain: Chain = Chain.MAIN, debug: bool = False, screenshot_dir: Path = TESTS_ROOT_DIR) -> None: + super().__init__(comm_client, chain, debug) + self.screenshot_dir = screenshot_dir + self.navigate = False + self.navigator = None + self.testname = "" + self.instructions = None + + def _apdu_exchange(self, apdu: dict, tick_timeout: int = 0) -> Tuple[int, bytes]: + try: + if self.debug: + print_apdu(apdu) + + response = self.transport_client.exchange( + **apdu, tick_timeout=tick_timeout) + if self.debug: + print_response(response.status, response.data) + + return response.status, response.data + except ApduException as e: + if self.debug: + print_response(e.sw, e.data) + + return e.sw, e.data + + def _make_request( + self, apdu: dict, client_intepreter: ClientCommandInterpreter = None + ) -> Tuple[int, bytes]: + + if self.navigate: + sw, response = self._make_request_with_navigation(navigator=self.navigator, + apdu=apdu, + client_intepreter=client_intepreter, + testname=self.testname, + instructions=self.instructions + ) + + else: + sw, response = NewClient._make_request( + self, apdu, client_intepreter) + return sw, response + + def last_async_response(self) -> Tuple[int, bytes]: + return self.transport_client.last_async_response.status, self.transport_client.last_async_response.data + + def ragger_navigate(self, navigator: Navigator, apdu: dict, instructions: Instructions, testname: str, index: int) -> Tuple[int, bytes, int]: + sub_index = 0 + + if instructions: + text = instructions.data['text'] + instruction_until_text = instructions.data['instruction_until_text'] + instruction_on_text = instructions.data['instruction_on_text'] + save_screenshot = instructions.data['save_screenshot'] + else: + text = [] + instruction_until_text = [] + instruction_on_text = [] + save_screenshot = [] + + try: + sw, response = self._apdu_exchange(apdu, tick_timeout=1) + except TimeoutError: + with self.transport_client.exchange_async(**apdu): + for t, instr_approve, instr_next, compare in zip(text[index], + instruction_on_text[index], + instruction_until_text[index], + save_screenshot[index]): + if compare: + navigator.navigate_until_text_and_compare(instr_next, + [instr_approve], + t, + self.screenshot_dir, + Path( + f"{testname}_{index}_{sub_index}"), + screen_change_after_last_instruction=False, + screen_change_before_first_instruction=True) + else: + navigator.navigate_until_text(instr_next, + [instr_approve], + t, + screen_change_after_last_instruction=False, + screen_change_before_first_instruction=True) + sub_index += 1 + + sw, response = self.last_async_response() + index += 1 + return sw, response, index + + def _make_request_with_navigation(self, navigator: Navigator, apdu: dict, client_intepreter: + ClientCommandInterpreter = None, + testname: str = "", instructions: Instructions = None) -> Tuple[int, bytes]: + + index = 0 + + sw, response, index = self.ragger_navigate( + navigator, apdu, instructions, testname, index) + + while sw == 0xE000: + if not client_intepreter: + raise RuntimeError( + "Unexpected SW_INTERRUPTED_EXECUTION received.") + + command_response = client_intepreter.execute(response) + apdu = self.builder.continue_interrupted(command_response) + + sw, response, index = self.ragger_navigate( + navigator, apdu, instructions, testname, index) + return sw, response + + def get_extended_pubkey(self, path: str, display: bool = False, navigator: Optional[Navigator] = None, + testname: str = "", + instructions: Instructions = None) -> str: + + if navigator: + self.navigate = True + self.navigator = navigator + self.testname = testname + self.instructions = instructions + + response = NewClient.get_extended_pubkey(self, path, display) + + self.navigate = False + + return response + + def register_wallet(self, wallet: WalletPolicy, navigator: Optional[Navigator] = None, + testname: str = "", instructions: Instructions = None) -> Tuple[bytes, bytes]: + + if navigator: + self.navigate = True + self.navigator = navigator + self.testname = testname + self.instructions = instructions + + wallet_id, wallet_hmac = NewClient.register_wallet(self, wallet) + + self.navigate = False + + return wallet_id, wallet_hmac + + def get_wallet_address( + self, + wallet: WalletPolicy, + wallet_hmac: Optional[bytes], + change: int, + address_index: int, + display: bool, + navigator: Optional[Navigator] = None, + instructions: Instructions = None, + testname: str = "" + ) -> str: + + if navigator: + self.navigate = True + self.navigator = navigator + self.testname = testname + self.instructions = instructions + + result = NewClient.get_wallet_address( + self, wallet, wallet_hmac, change, address_index, display) + + self.navigate = False + + return result + + def sign_psbt(self, psbt: Union[PSBT, bytes, str], wallet: WalletPolicy, wallet_hmac: + Optional[bytes], navigator: Optional[Navigator] = None, + testname: str = "", instructions: Instructions = None) -> List[Tuple[int, PartialSignature]]: + + if navigator: + self.navigate = True + self.navigator = navigator + self.testname = testname + self.instructions = instructions + + result = NewClient.sign_psbt(self, psbt, wallet, wallet_hmac) + + self.navigate = False + + return result + + def sign_message(self, message: Union[str, bytes], bip32_path: str, navigator: + Optional[Navigator] = None, + instructions: Instructions = None, + testname: str = "" + ) -> str: + + if navigator: + self.navigate = True + self.navigator = navigator + self.testname = testname + self.instructions = instructions + + response = NewClient.sign_message(self, message, bip32_path) + + self.navigate = False + + return response + + +def createRaggerClient(backend, chain: Chain = Chain.MAIN, debug: bool = False, screenshot_dir: + Path = TESTS_ROOT_DIR) -> RaggerClient: + return RaggerClient(backend, chain, debug, screenshot_dir) diff --git a/ragger_bitcoin/ragger_instructions.py b/ragger_bitcoin/ragger_instructions.py new file mode 100644 index 000000000..08e157d2b --- /dev/null +++ b/ragger_bitcoin/ragger_instructions.py @@ -0,0 +1,98 @@ +from ragger.navigator import NavInsID + + +class Instructions: + def __init__(self, model): + self.data = { + 'text': [], + 'instruction_until_text': [], + 'instruction_on_text': [], + 'save_screenshot': [] + } + + if not model: + raise Exception("Model must be specified") + + self.model = model + + def __str__(self): + return "Data: {0}\n\t".format(self.data) + + def same_request(self, text, instruction_until_text=NavInsID.RIGHT_CLICK, + instruction_on_text=NavInsID.BOTH_CLICK, save_screenshot=True): + + self.data['text'][-1].append(text) + self.data['instruction_until_text'][-1].append(instruction_until_text) + self.data['instruction_on_text'][-1].append(instruction_on_text) + self.data['save_screenshot'][-1].append(save_screenshot) + + def new_request(self, text, instruction_until_text=NavInsID.RIGHT_CLICK, + instruction_on_text=NavInsID.BOTH_CLICK, save_screenshot=True): + + self.data['text'].append([text]) + self.data['instruction_until_text'].append([instruction_until_text]) + self.data['instruction_on_text'].append([instruction_on_text]) + self.data['save_screenshot'].append([save_screenshot]) + + def nano_skip_screen(self, text, save_screenshot=True): + self.new_request(text, NavInsID.RIGHT_CLICK, NavInsID.RIGHT_CLICK, + save_screenshot=save_screenshot) + + def navigate_end_of_flow(self, save_screenshot=True): + self.new_request("Processing", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_TAP, + save_screenshot=save_screenshot) + + def confirm_transaction(self, save_screenshot=True): + self.new_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_CONFIRM, + save_screenshot=save_screenshot) + self.new_request("TRANSACTION", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, + save_screenshot=save_screenshot) + + def same_request_confirm_transaction(self, save_screenshot=True): + self.same_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_CONFIRM, + save_screenshot=save_screenshot) + self.new_request("TRANSACTION", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, + save_screenshot=save_screenshot) + + def confirm_message(self, save_screenshot=True): + self.new_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_REVIEW_CONFIRM, save_screenshot=save_screenshot) + self.new_request("MESSAGE", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, save_screenshot=save_screenshot) + + def confirm_wallet(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_CONFIRM, + save_screenshot=save_screenshot) + self.same_request("WALLET", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, save_screenshot=save_screenshot) + + def reject_message(self, save_screenshot=True): + self.new_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_REJECT, + save_screenshot=save_screenshot) + self.same_request("Reject", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_CONFIRM, + save_screenshot=save_screenshot) + self.new_request("MESSAGE", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_STATUS_DISMISS, + save_screenshot=save_screenshot) + + def warning_accept(self, save_screenshot=True): + self.new_request("Warning", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_CONFIRM, + save_screenshot=save_screenshot) + + def address_confirm(self, save_screenshot=True): + self.new_request("Confirm", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CONFIRM, + save_screenshot=save_screenshot) + + def choice_confirm(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_CONFIRM, + save_screenshot=save_screenshot) + + def choice_reject(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_REJECT, + save_screenshot=save_screenshot) + + def footer_cancel(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.CANCEL_FOOTER_TAP, + save_screenshot=save_screenshot) diff --git a/ragger_bitcoin/setup.cfg b/ragger_bitcoin/setup.cfg new file mode 100644 index 000000000..867b7b040 --- /dev/null +++ b/ragger_bitcoin/setup.cfg @@ -0,0 +1,26 @@ +[metadata] +name = ragger_bitcoin +version = 0.0.1 +author = Ledger +author_email = hello@ledger.fr +description = Ragger wrapper for Leger Bitcoin Client +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/LedgerHQ/app-bitcoin-new +project_urls = + Bug Tracker = https://github.com/LedgerHQ/app-bitcoin-new +classifiers = + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Operating System :: MacOS :: MacOS X + +[options] +packages = find: +include_package_data = True +python_requires = >=3.7 +install_requires= + ledger-bitcoin diff --git a/src/boilerplate/apdu_parser.c b/src/boilerplate/apdu_parser.c index b63c5b686..8304d1aac 100644 --- a/src/boilerplate/apdu_parser.c +++ b/src/boilerplate/apdu_parser.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/boilerplate/dispatcher.c b/src/boilerplate/dispatcher.c index c4da8cd4f..e6d1bd74c 100644 --- a/src/boilerplate/dispatcher.c +++ b/src/boilerplate/dispatcher.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,17 +39,10 @@ extern bool G_was_processing_screen_shown; // Private state that is not made accessible from the dispatcher context struct { void (*termination_cb)(void); - bool paused; uint16_t sw; bool had_ux_flow; // set to true if there was any UX flow during the APDU processing } G_dispatcher_state; -static void dispatcher_loop(); - -static void next(command_processor_t next_processor) { - G_dispatcher_context.machine_context_ptr->next_processor = next_processor; -} - static void add_to_response(const void *rdata, size_t rdata_len) { io_add_to_response(rdata, rdata_len); } @@ -63,35 +56,11 @@ static void send_response() { io_confirm_response(); } -static void pause() { - G_dispatcher_state.paused = true; - - // pause() is _always_ called for ux flows that wait for user input. - // No other flows should exist. +static void set_ui_dirty() { + // signals that the screen was changed while processing a command handler G_dispatcher_state.had_ux_flow = true; } -static void run() { - G_dispatcher_state.paused = false; - - io_start_processing_timeout(); - dispatcher_loop(); -} - -static void start_flow(command_processor_t first_processor, - machine_context_t *subcontext, - command_processor_t return_processor) { - // set the return_processor as the next processor for the current flow - G_dispatcher_context.machine_context_ptr->next_processor = return_processor; - - // initialize subcontext's parent context and initial processor - subcontext->parent_context = G_dispatcher_context.machine_context_ptr; - subcontext->next_processor = first_processor; - - // switch machine context to subcontext - G_dispatcher_context.machine_context_ptr = subcontext; -} - // TODO: refactor code in common with the main apdu loop static int process_interruption(dispatcher_context_t *dc) { command_t cmd; @@ -140,48 +109,31 @@ static int process_interruption(dispatcher_context_t *dc) { void apdu_dispatcher(command_descriptor_t const cmd_descriptors[], int n_descriptors, - machine_context_t *top_context, - size_t top_context_size, void (*termination_cb)(void), const command_t *cmd) { G_dispatcher_state.had_ux_flow = false; G_dispatcher_state.termination_cb = termination_cb; - G_dispatcher_state.paused = false; G_dispatcher_state.sw = 0; - G_dispatcher_context.next = next; G_dispatcher_context.add_to_response = add_to_response; G_dispatcher_context.finalize_response = finalize_response; G_dispatcher_context.send_response = send_response; - G_dispatcher_context.pause = pause; - G_dispatcher_context.run = run; - G_dispatcher_context.start_flow = start_flow; + G_dispatcher_context.set_ui_dirty = set_ui_dirty; G_dispatcher_context.process_interruption = process_interruption; G_dispatcher_context.read_buffer = buffer_create(cmd->data, cmd->lc); - if (cmd->cla == CLA_FRAMEWORK && cmd->ins == INS_CONTINUE) { - if (cmd->p1 != 0 || cmd->p2 != 0) { - io_send_sw(SW_WRONG_P1P2); - return; - } + if (cmd->p2 > CURRENT_PROTOCOL_VERSION) { + io_send_sw(SW_WRONG_P1P2); + return; + } - if (G_dispatcher_context.machine_context_ptr == NULL || - G_dispatcher_context.machine_context_ptr->next_processor == NULL) { - PRINTF("Unexpected INS_CONTINUE.\n"); - io_send_sw(SW_BAD_STATE); // received INS_CONTINUE, but no command was interrupted. - return; - } + if (cmd->cla == CLA_FRAMEWORK && cmd->ins == INS_CONTINUE) { + PRINTF("Unexpected INS_CONTINUE.\n"); + io_send_sw(SW_BAD_STATE); // received INS_CONTINUE, but no command was interrupted. + return; } else { - // If a previous command was interrupted but any command other than INS_CONTINUE is - // received, the interrupted command is discarded. - - G_dispatcher_context.machine_context_ptr = top_context; - - // Safety measure: reset to 0 the entire context before starting. - explicit_bzero(top_context, top_context_size); - bool cla_found = false, ins_found = false; command_handler_t handler; for (int i = 0; i < n_descriptors; i++) { @@ -203,54 +155,7 @@ void apdu_dispatcher(command_descriptor_t const cmd_descriptors[], } io_start_processing_timeout(); - handler(&G_dispatcher_context); - } - - dispatcher_loop(); -} - -static void dispatcher_loop() { - if (G_dispatcher_context.machine_context_ptr == NULL) { - PRINTF("dispatcher_loop called when the machine context is not set."); - return; - } - - while (true) { - if (G_dispatcher_state.paused) { - io_clear_processing_timeout(); - return; - } - - if (G_dispatcher_state.sw != 0) { - break; - } - - if (G_dispatcher_context.machine_context_ptr->next_processor) { - // there is a next processor, continue in the same context - - command_processor_t proc = G_dispatcher_context.machine_context_ptr->next_processor; - G_dispatcher_context.machine_context_ptr->next_processor = NULL; - - proc(&G_dispatcher_context); - - // if an interruption is sent, should exit the loop and persist the context for the next - // call in that case, there MUST be a next_processor - if (G_dispatcher_state.sw == SW_INTERRUPTED_EXECUTION) { - if (G_dispatcher_context.machine_context_ptr->next_processor == NULL) { - PRINTF("Interruption requested, but the next processor was not set.\n"); - } - - io_clear_processing_timeout(); - return; - } - } else if (G_dispatcher_context.machine_context_ptr->parent_context != NULL) { - // the current submachine ended, continue from parent's context - G_dispatcher_context.machine_context_ptr = - G_dispatcher_context.machine_context_ptr->parent_context; - continue; - } else { - break; // all done - } + handler(&G_dispatcher_context, cmd->p2); } // Here a response (either success or error) should have been send. @@ -266,6 +171,7 @@ static void dispatcher_loop() { bool is_ux_dirty = G_dispatcher_state.had_ux_flow || G_was_processing_screen_shown; if (G_dispatcher_state.termination_cb != NULL && is_ux_dirty) { G_dispatcher_state.termination_cb(); + G_was_processing_screen_shown = 0; } io_clear_processing_timeout(); @@ -274,21 +180,11 @@ static void dispatcher_loop() { #ifdef HAVE_LOG_PROCESSOR // Print current filename, line number and function name. // Indents according to the nesting depth for subprocessors. -void print_dispatcher_info(dispatcher_context_t *dc, - const char *file, +void print_dispatcher_info(const char *file, int line, const char *func) { - // prevent warnings when DEBUG is 0 - (void) file, (void) line, (void) func; - // PRINTF() replaced with low-level functions to reduce stack usage (~ 40 vs 500 bytes) - machine_context_t *ctx = dc->machine_context_ptr; - while (ctx->parent_context != NULL) { - debug_write("----"); - ctx = ctx->parent_context; - } - debug_write("->"); debug_write(file); debug_write(":"); diff --git a/src/boilerplate/dispatcher.h b/src/boilerplate/dispatcher.h index 2f2017161..8e01ecfa4 100644 --- a/src/boilerplate/dispatcher.h +++ b/src/boilerplate/dispatcher.h @@ -6,58 +6,22 @@ #include "common/buffer.h" -// TODO: continue brainstorming on a nice interface. -// A command descriptor should contain: -// - a command handler, that can access all the input and the global state -// - a command processor, that encodes the state machine (only for interruptible commands) -// For simple 1-round commands, the global state should not be used (or used only as temporary -// storage); there is no command processor. For interruptible commands, the command handler -// initializes the global state; it can return a status word and response, and no processor will be -// called in that case. Otherwise, the command processor is called, which implements the state -// machines, and must respect specific constraints in the way it's written. -// TODO: document this. - // Forward declaration struct dispatcher_context_s; typedef struct dispatcher_context_s dispatcher_context_t; -typedef void (*command_processor_t)(dispatcher_context_t *); - -typedef command_processor_t command_handler_t; - -typedef struct machine_context_s { - struct machine_context_s *parent_context; - command_processor_t next_processor; -} machine_context_t; - -typedef void (*dispatcher_callback_t)(machine_context_t *, buffer_t *); - -typedef struct { - void *state; - dispatcher_callback_t fn; -} dispatcher_callback_descriptor_t; - -static inline dispatcher_callback_descriptor_t make_callback(void *state, - dispatcher_callback_t fn) { - return (dispatcher_callback_descriptor_t){.state = state, .fn = fn}; -} +typedef void (*command_handler_t)(dispatcher_context_t *, uint8_t p2); /** * TODO: docs */ struct dispatcher_context_s { - machine_context_t *machine_context_ptr; buffer_t read_buffer; - void (*pause)(); - void (*run)(); - void (*next)(command_processor_t next_processor); + void (*set_ui_dirty)(); void (*add_to_response)(const void *rdata, size_t rdata_len); void (*finalize_response)(uint16_t sw); void (*send_response)(void); - void (*start_flow)(command_processor_t first_processor, - machine_context_t *subcontext, - command_processor_t return_processor); int (*process_interruption)(dispatcher_context_t *dispatcher_context); }; @@ -83,16 +47,6 @@ static inline void SEND_RESPONSE(struct dispatcher_context_s *dc, dc->send_response(); } -// TODO: instead of exposing a method like send_response, it might be more efficient to expose the -// response buffer, -// so that one could use the buffer_write_* methods directly. -// On the other hand, buth the read_buffer and the write buffer would point to the same shared -// global space (part of G_io_apdu_buffer). Therefore, one would have to make sure that no -// read happens after writes happen, and it would probably be better if the dispatcher -// enforces this, by making it impossible to accidentally read the read_buffer after writes -// happened. One way could be have a function get_output_buffer() in the dispatcher context, -// that returns the output buffer but it first zeroes the read_buffer. - /** * Describes a command that can be processed by the dispatcher. */ @@ -104,25 +58,33 @@ typedef struct { /** * Dispatch APDU command received to the right handler. - * @param[in] command_descriptors + * @param[in] cmd_descriptors * Array of command descriptors. * @param[in] n_descriptors * Length of the command_descriptors array. + * @param[in] termination_cb + * If not NULL, a callback that will be executed once the command handler is done. * @param[in] cmd * Structured APDU command (CLA, INS, P1, P2, Lc, Command data). - * - * TODO: update docs with new params - * */ void apdu_dispatcher(command_descriptor_t const cmd_descriptors[], int n_descriptors, - machine_context_t *top_context, - size_t top_context_size, void (*termination_cb)(void), const command_t *cmd); // Debug utilities +#if !defined(DEBUG) || DEBUG == 0 +#define LOG_PROCESSOR(file, line, func) +#elif defined(HAVE_CCMD_PRINTF) && defined(HAVE_LOG_PROCESSOR) +#define LOG_PROCESSOR(dc, file, line, func) ccmd_printf(dc, "->%s", func) +#elif defined(HAVE_LOG_PROCESSOR) + +void print_dispatcher_info(const char *file, int line, const char *func); + +#define LOG_PROCESSOR(file, line, func) print_dispatcher_info(file, line, func) +#endif + #ifdef HAVE_CCMD_PRINTF extern int ccmd_printf(dispatcher_context_t *dc, const char *format, ... ); #define CCMD_PRINTF ccmd_printf @@ -130,21 +92,6 @@ extern int ccmd_printf(dispatcher_context_t *dc, const char *format, ... ); #define CCMD_PRINTF(...) #endif -#if defined(HAVE_CCMD_PRINTF) && defined(HAVE_CCMD_PRINTF) -#define LOG_PROCESSOR(dc, file, line, func) ccmd_printf(dc, "->%s", func) -#elif defined(HAVE_LOG_PROCESSOR) -// Print current filename, line number and function name. -// Indents according to the nesting depth for subprocessors. -void print_dispatcher_info(dispatcher_context_t *dc, - const char *file, - int line, - const char *func); - -#define LOG_PROCESSOR(dc, file, line, func) print_dispatcher_info(dc, file, line, func) -#else -#define LOG_PROCESSOR(dc, file, line, func) -#endif - #ifdef HAVE_APDU_LOG extern void log_apdu(const command_t* cmd); #define LOG_APDU(cmd) log_apdu(cmd) diff --git a/src/boilerplate/io.c b/src/boilerplate/io.c index 2660b1cf4..4d4ad277e 100644 --- a/src/boilerplate/io.c +++ b/src/boilerplate/io.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,10 @@ #include "os.h" #include "ux.h" +#ifdef HAVE_NBGL +#include "nbgl_touch.h" +#include "nbgl_use_case.h" +#endif // HAVE_NBGL #include "util.h" #include "io.h" @@ -29,9 +33,7 @@ #include "common/write.h" #include "dispatcher.h" - -extern dispatcher_context_t G_dispatcher_context; -extern command_processor_t G_command_continuation; +#include "../swap/swap_globals.h" uint16_t G_output_len = 0; @@ -51,12 +53,14 @@ bool G_was_processing_screen_shown; uint16_t G_interruption_timeout_start_tick; uint16_t G_processing_timeout_start_tick; +#ifdef HAVE_BAGL UX_STEP_NOCB(ux_processing_flow_1_step, pn, {&C_icon_processing, "Processing..."}); UX_FLOW(ux_processing_flow, &ux_processing_flow_1_step); void io_seproxyhal_display(const bagl_element_t *element) { io_seproxyhal_display_default((bagl_element_t *) element); } +#endif // HAVE_BAGL void io_start_interruption_timeout() { G_interruption_timeout_start_tick = G_ticks; @@ -87,7 +91,9 @@ uint8_t io_event(uint8_t channel) { switch (G_io_seproxyhal_spi_buffer[0]) { case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: +#ifdef HAVE_BAGL UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); +#endif // HAVE_BAGL break; case SEPROXYHAL_TAG_STATUS_EVENT: if (G_io_apdu_media == IO_APDU_MEDIA_USB_HID && // @@ -98,8 +104,18 @@ uint8_t io_event(uint8_t channel) { /* fallthrough */ UTIL_FALLTHROUGH; case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: +#ifdef HAVE_BAGL UX_DISPLAYED_EVENT({}); +#endif // HAVE_BAGL +#ifdef HAVE_NBGL + UX_DEFAULT_EVENT(); +#endif // HAVE_NBGL break; +#ifdef HAVE_NBGL + case SEPROXYHAL_TAG_FINGER_EVENT: + UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); + break; +#endif // HAVE_NBGL case SEPROXYHAL_TAG_TICKER_EVENT: ++G_ticks; @@ -107,8 +123,18 @@ uint8_t io_event(uint8_t channel) { G_ticks - G_processing_timeout_start_tick >= PROCESSING_TIMEOUT_TICKS) { io_clear_processing_timeout(); - G_was_processing_screen_shown = true; - ux_flow_init(0, ux_processing_flow, NULL); + if (!G_was_processing_screen_shown) { + G_was_processing_screen_shown = true; +#ifdef HAVE_BAGL + ux_flow_init(0, ux_processing_flow, NULL); +#endif // HAVE_BAGL +#ifdef HAVE_NBGL + + if (!G_swap_state.called_from_swap) { + nbgl_useCaseSpinner("Processing"); + } +#endif // HAVE_NBGL + } } if (G_is_timeout_active.interruption && diff --git a/src/boilerplate/io.h b/src/boilerplate/io.h index 634c97026..4a6a3ea6e 100644 --- a/src/boilerplate/io.h +++ b/src/boilerplate/io.h @@ -7,7 +7,9 @@ #include "common/buffer.h" +#ifdef HAVE_BAGL void io_seproxyhal_display(const bagl_element_t *element); +#endif // HAVE_BAGL /** * IO callback called when an interrupt based channel has received diff --git a/src/boilerplate/sw.h b/src/boilerplate/sw.h index de384eb45..a61516caa 100644 --- a/src/boilerplate/sw.h +++ b/src/boilerplate/sw.h @@ -36,6 +36,11 @@ */ #define SW_WRONG_DATA_LENGTH 0x6A87 +/** + * Status word for fail in Swap + */ +#define SW_FAIL_SWAP 0x6B00 + /** * Status word for unknown command with this INS. */ diff --git a/src/commands.h b/src/commands.h index 179632523..f52758663 100644 --- a/src/commands.h +++ b/src/commands.h @@ -1,19 +1,5 @@ #pragma once -#include "boilerplate/dispatcher.h" -#include "constants.h" -#include "handler/get_master_fingerprint.h" -#include "handler/get_extended_pubkey.h" -#include "handler/get_wallet_address.h" -#include "handler/register_wallet.h" -#include "handler/sign_psbt.h" -#include "handler/liquid_sign_pset.h" -#include "handler/sign_message.h" -#ifdef HAVE_LIQUID -#include "handler/liquid_get_master_blinding_key.h" -#include "handler/liquid_get_blinding_key.h" -#endif // HAVE_LIQUID - /** * Enumeration with expected INS of APDU commands. */ @@ -29,29 +15,3 @@ typedef enum { LIQUID_GET_BLINDING_KEY = 0xE3, #endif // HAVE_LIQUID } command_e; - -/** - * Union of the global state for all the commands. - */ -typedef union { - get_master_fingerprint_t get_master_fingerprint; - get_extended_pubkey_state_t get_extended_pubkey_state; - register_wallet_state_t register_wallet_state; - get_wallet_address_state_t get_wallet_address_state; -#ifdef HAVE_LIQUID - sign_pset_state_t sign_pset_state; -#else - sign_psbt_state_t sign_psbt_state; -#endif - sign_message_state_t sign_message_state; -#ifdef HAVE_LIQUID - liquid_get_master_blinding_key_t liquid_get_master_blinding_key; - liquid_get_blinding_key_t liquid_get_blinding_key; -#endif // HAVE_LIQUID -} command_state_t; - -/** - * Since only one command can execute at the same time, we share the same global space - * for the command state of all the commands. - **/ -extern command_state_t G_command_state; diff --git a/src/common/base58.c b/src/common/base58.c index a9c3eb2b9..369f27c57 100644 --- a/src/common/base58.c +++ b/src/common/base58.c @@ -1,5 +1,5 @@ /***************************************************************************** - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/common/bip32.c b/src/common/bip32.c index 4fb783f5e..966b7bbf3 100644 --- a/src/common/bip32.c +++ b/src/common/bip32.c @@ -1,5 +1,5 @@ /***************************************************************************** - * (c) 2020 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,58 +141,3 @@ bool is_pubkey_path_standard(const uint32_t *bip32_path, return true; } - -bool is_address_path_standard(const uint32_t *bip32_path, - size_t bip32_path_len, - uint32_t expected_purpose, - const uint32_t expected_coin_types[], - size_t expected_coin_types_len, - int expected_change) { - if (bip32_path_len != 5) { - return false; - } - - if (!is_pubkey_path_standard(bip32_path, - 3, - expected_purpose, - expected_coin_types, - expected_coin_types_len)) { - return false; - } - - uint32_t change = bip32_path[BIP44_CHANGE_OFFSET]; - if (change != 0 && change != 1) { - return false; - } - - if (expected_change == 0 || expected_change == 1) { - // change should match the expected one - if (change != (uint32_t) expected_change) { - return false; - } - } else if (expected_change != -1) { - return false; // wrong expected_change parameter - } - - uint32_t address_index = bip32_path[BIP44_ADDRESS_INDEX_OFFSET]; - if (address_index > - MAX_BIP44_ADDRESS_INDEX_RECOMMENDED) { // should not be hardened, and not too large - return false; - } - return true; -} - -int get_bip44_purpose(int address_type) { - switch (address_type) { - case ADDRESS_TYPE_LEGACY: - return 44; // legacy - case ADDRESS_TYPE_WIT: - return 84; // native segwit - case ADDRESS_TYPE_SH_WIT: - return 49; // wrapped segwit - case ADDRESS_TYPE_TR: - return 86; // taproot - default: - return -1; - } -} diff --git a/src/common/bip32.h b/src/common/bip32.h index 1213f46d9..cd6707e14 100644 --- a/src/common/bip32.h +++ b/src/common/bip32.h @@ -5,10 +5,10 @@ #include // bool /** - * Maximum length of BIP32 path allowed. - * Note: BIP32 allows up to 256 derivation steps - but generally only 5 are used. + * Maximum length of BIP32 path supported. + * Note: BIP32 allows up to 256 derivation steps - but only 5 or 6 are used in most cases. */ -#define MAX_BIP32_PATH_STEPS 6 +#define MAX_BIP32_PATH_STEPS 8 /** * Maximum length of a string representing a BIP32 derivation path. @@ -114,52 +114,3 @@ bool is_pubkey_path_standard(const uint32_t *bip32_path, uint32_t expected_purpose, const uint32_t expected_coin_types[], size_t expected_coin_types_len); - -/** - * Verifies if a given path is standard according to the BIP44 or derived standards for the - * derivation path for an address. - * - * Returns false if any of the following conditions is not satisfied by the given bip32_path: - * - the bip32_path has exactly 5 elements; - * - the first 3 steps of the derivation are standard according to is_pubkey_path_standard; - * - change and address_index are not hardened; - * - change is 0 and is_change = false, or change is 1 and is_change = true; - * - address_index is at most MAX_BIP44_ADDRESS_INDEX_RECOMMENDED. - * - * @param[in] bip32_path - * Pointer to 32-bit integer input buffer. - * @param[in] bip32_path_len - * Maximum number of BIP32 paths in the input buffer. - * @param[in] expected_purpose - * The purpose that should be in the derivation (e.g. 44 for BIP44). - * @param[in] expected_coin_types - * Pointer to an array with the coin types that are considered acceptable. The - * elements of the array should be given as simple numbers (not their hardened version); - * for example, the coin type for Bitcoin is 0. - * Ignored if expected_coin_types_len is 0; in that case, it is only checked - * that the coin_type is hardened, as expected in the standard. - * @param[in] expected_coin_types_len - * The length of expected_coin_types. - * @param[in] expected_change - * It must be -1, 0 or 1. If -1, only checks that the provided change step is 0 or 1. If 0 or 1, - * the change step must equal `expected_change`. - * - * @return true if the given address is standard, false otherwise. - * - */ -bool is_address_path_standard(const uint32_t *bip32_path, - size_t bip32_path_len, - uint32_t expected_purpose, - const uint32_t expected_coin_types[], - size_t expected_coin_types_len, - int expected_change); - -/** - * Returns the appropriate value of the "purpose" step in a supported BIP44-compliant derivation. - * - * @param[in] address_type - * One of ADDRESS_TYPE_LEGACY, ADDRESS_TYPE_WIT, ADDRESS_TYPE_SH_WIT, ADDRESS_TYPE_TR. - * - * @return the correct BIP44 purpose, or -1 if the `address_type` parameter is wrong. - */ -int get_bip44_purpose(int address_type); diff --git a/src/common/buffer.c b/src/common/buffer.c index 7ccc6c791..470b2e91a 100644 --- a/src/common/buffer.c +++ b/src/common/buffer.c @@ -1,5 +1,5 @@ /***************************************************************************** - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,11 +74,15 @@ bool buffer_read_u8(buffer_t *buffer, uint8_t *value) { } bool buffer_peek(const buffer_t *buffer, uint8_t *value) { - if (!buffer_can_read(buffer, 1)) { + return buffer_peek_n(buffer, 0, value); +} + +bool buffer_peek_n(const buffer_t *buffer, size_t n, uint8_t *value) { + if (!buffer_can_read(buffer, n + 1)) { return false; } - *value = buffer->ptr[buffer->offset]; + *value = buffer->ptr[buffer->offset + n]; return true; } diff --git a/src/common/buffer.h b/src/common/buffer.h index ad931380e..1909e5884 100644 --- a/src/common/buffer.h +++ b/src/common/buffer.h @@ -118,7 +118,7 @@ bool buffer_read_u8(buffer_t *buffer, uint8_t *value); /** * Read 1 byte from buffer into uint8_t without advancing the current position in the buffer. - * Returns `true` on success, `false` if the buffer was empty; `value` is not change in case of + * Returns `true` on success, `false` if the buffer was empty; `value` is not changed in case of * failure. * * @param[in] buffer @@ -130,6 +130,22 @@ bool buffer_read_u8(buffer_t *buffer, uint8_t *value); */ bool buffer_peek(const buffer_t *buffer, uint8_t *value); +/** + * Read 1 byte at position `n` from buffer into uint8_t without advancing the current position in + * the buffer. Returns `true` on success, `false` if the buffer is not large enough; `value` is not + * changed in case of failure. + * + * @param[in] buffer + * Pointer to input buffer struct. + * @param[out] n + * Index of the byte to read, where the immediate next byte has index 0. + * @param[out] value + * Pointer to 8-bit unsigned integer read from buffer. + * + * @return true if success, false otherwise. + */ +bool buffer_peek_n(const buffer_t *buffer, size_t n, uint8_t *value); + /** * Read 2 bytes from buffer into uint16_t. * @@ -339,6 +355,17 @@ static inline buffer_t buffer_create(void *ptr, size_t size) { */ void *buffer_alloc(buffer_t *buffer, size_t size, bool aligned); +/** + * Checks if the current position in the buffer is aligned in memory to a 4-byte boundary. + * + * @param[in] buffer Pointer to a buffer struct. + * + * @return `true` if the current position in the buffer is aligned, `false` otherwise. + */ +static inline bool buffer_is_cur_aligned(const buffer_t *buffer) { + return (size_t) (buffer->ptr + buffer->offset) % 4 == 0; +} + /** * Saves a snapshot of the current position within the buffer. * diff --git a/src/common/format.c b/src/common/format.c index 61757e7c8..4c3e4e2f8 100644 --- a/src/common/format.c +++ b/src/common/format.c @@ -1,5 +1,5 @@ /***************************************************************************** - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/common/merkle.c b/src/common/merkle.c index 3e5c5344a..65f5e2baf 100644 --- a/src/common/merkle.c +++ b/src/common/merkle.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,53 +24,29 @@ #include "merkle.h" -#include "cx_ram.h" +#include "debug-helpers/debug.h" #include "ledger_assert.h" -void merkle_compute_element_hash(const uint8_t *in, size_t in_len, uint8_t out[static 32]) { - cx_sha256_t hash; - cx_sha256_init(&hash); - +void merkle_compute_element_hash(const uint8_t *in, + size_t in_len, + uint8_t out[static CX_SHA256_SIZE]) { // H(0x00 | in) - crypto_hash_update_u8(&hash.header, 0x00); - crypto_hash_update(&hash.header, in, in_len); - - crypto_hash_digest(&hash.header, out, 32); + uint8_t data = 0x00; + cx_iovec_t iovec[2] = {{.iov_base = &data, .iov_len = 1}, {.iov_base = in, .iov_len = in_len}}; + cx_sha256_hash_iovec(iovec, 2, out); } -// void merkle_combine_hashes(const uint8_t left[static 32], -// const uint8_t right[static 32], -// uint8_t out[static 32]) { -// PRINT_STACK_POINTER(); - -// cx_sha256_t hash; -// cx_sha256_init(&hash); - -// // H(0x01 | left | right) -// crypto_hash_update_u8(&hash.header, 0x01); -// crypto_hash_update(&hash.header, left, 32); -// crypto_hash_update(&hash.header, right, 32); - -// crypto_hash_digest(&hash.header, out, 32); -// } - -// implementation using the cxram section, in order to save ram -void merkle_combine_hashes(const uint8_t left[static 32], - const uint8_t right[static 32], - uint8_t out[static 32]) { +void merkle_combine_hashes(const uint8_t left[static CX_SHA256_SIZE], + const uint8_t right[static CX_SHA256_SIZE], + uint8_t out[static CX_SHA256_SIZE]) { PRINT_STACK_POINTER(); - cx_sha256_init_no_throw(&G_cx.sha256); - uint8_t prefix = 0x01; - LEDGER_ASSERT(cx_sha256_update(&G_cx.sha256, &prefix, 1) == CX_OK, "It never fails"); - - LEDGER_ASSERT(cx_sha256_update(&G_cx.sha256, left, 32) == CX_OK, "It never fails"); - LEDGER_ASSERT(cx_sha256_update(&G_cx.sha256, right, 32) == CX_OK, "It never fails"); - - cx_sha256_final(&G_cx.sha256, out); - explicit_bzero(&G_cx.sha256, sizeof(cx_sha256_t)); + cx_iovec_t iovec[3] = {{.iov_base = &prefix, .iov_len = 1}, + {.iov_base = left, .iov_len = CX_SHA256_SIZE}, + {.iov_base = right, .iov_len = CX_SHA256_SIZE}}; + cx_sha256_hash_iovec(iovec, 3, out); } // TODO: make this O(log n), or possibly O(1). Currently O(log^2 n). diff --git a/src/common/read.c b/src/common/read.c index 71e2594fe..6c467c1f6 100644 --- a/src/common/read.c +++ b/src/common/read.c @@ -1,5 +1,5 @@ /***************************************************************************** - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/common/script.c b/src/common/script.c index 5bbc690b4..cd701fe16 100644 --- a/src/common/script.c +++ b/src/common/script.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -13,6 +14,21 @@ #include "../crypto.h" #endif +size_t get_push_script_size(uint32_t n) { + if (n <= 16) + return 1; // OP_0 and OP_1 .. OP_16 + else if (n < 0x80) + return 2; // 01 nn + else if (n < 0x8000) + return 3; // 02 nnnn + else if (n < 0x800000) + return 4; // 03 nnnnnn + else if (n < 0x80000000) + return 5; // 04 nnnnnnnn + else + return 6; // 05 nnnnnnnnnn +} + int get_script_type(const uint8_t script[], size_t script_len) { if (script_len == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2] == 0x14 && script[23] == OP_EQUALVERIFY && script[24] == OP_CHECKSIG) { @@ -52,18 +68,14 @@ int get_script_type(const uint8_t script[], size_t script_len) { #ifndef SKIP_FOR_CMOCKA // TODO: add unit tests -int get_script_address(const uint8_t script[], - size_t script_len, - char *out, - size_t out_len) { +int get_script_address(const uint8_t script[], size_t script_len, char *out, size_t out_len) { int script_type = get_script_type(script, script_len); int addr_len; switch (script_type) { case SCRIPT_TYPE_P2PKH: case SCRIPT_TYPE_P2SH: { int offset = (script_type == SCRIPT_TYPE_P2PKH) ? 3 : 2; - int ver = (script_type == SCRIPT_TYPE_P2PKH) ? COIN_P2PKH_VERSION - : COIN_P2SH_VERSION; + int ver = (script_type == SCRIPT_TYPE_P2PKH) ? COIN_P2PKH_VERSION : COIN_P2SH_VERSION; addr_len = base58_encode_address(script + offset, ver, out, out_len - 1); if (addr_len < 0) { return -1; @@ -84,11 +96,8 @@ int get_script_address(const uint8_t script[], return -1; } - int ret = segwit_addr_encode(out, - COIN_NATIVE_SEGWIT_PREFIX, - version, - script + 2, - prog_len); + int ret = + segwit_addr_encode(out, COIN_NATIVE_SEGWIT_PREFIX, version, script + 2, prog_len); if (ret != 1) { return -1; // should never happen @@ -119,63 +128,99 @@ int format_opscript_script(const uint8_t script[], return -1; } - strlcpy(out, "OP_RETURN ", MAX_OPRETURN_OUTPUT_DESC_SIZE); + if (script_len > 83) { + // a script that is more than 83 bytes violates the "max 80 bytes total data" rule + // (+ 3 bytes of opcodes) and is therefore not standard in Bitcoin Core. + return -1; + } + + strncpy(out, "OP_RETURN ", MAX_OPRETURN_OUTPUT_DESC_SIZE); int out_ctr = 10; - uint8_t opcode = script[1]; // the push opcode - if (opcode > OP_16 || opcode == OP_RESERVED || opcode == OP_PUSHDATA2 || - opcode == OP_PUSHDATA4) { - return -1; // unsupported + // If the length of the script is 1 (just "OP_RETURN"), then it's not standard per bitcoin-core. + // However, signing such outputs is part of BIP-0322, and there's no danger in allowing them. + + if (script_len == 1) { + out[out_ctr - 1] = '\0'; // remove extra space + return out_ctr; } - int hex_offset = 1; - size_t hex_length = - 0; // if non-zero, `hex_length` bytes starting from script[hex_offset] must be hex-encoded - - if (opcode == OP_0) { - if (script_len != 1 + 1) return -1; - out[out_ctr++] = '0'; - } else if (opcode >= 1 && opcode <= 75) { - hex_offset += 1; - hex_length = opcode; - - if (script_len != 1 + 1 + hex_length) return -1; - } else if (opcode == OP_PUSHDATA1) { - // OP_RETURN OP_PUSHDATA1 - hex_offset += 2; - hex_length = script[2]; - - if (script_len != 1 + 1 + 1 + hex_length || hex_length > 80) return -1; - } else if (opcode == OP_1NEGATE) { - if (script_len != 1 + 1) return -1; - - out[out_ctr++] = '-'; - out[out_ctr++] = '1'; - } else if (opcode >= OP_1 && opcode <= OP_16) { - if (script_len != 1 + 1) return -1; - - // encode OP_1 to OP_16 as a decimal number - uint8_t num = opcode - 0x50; - if (num >= 10) { - out[out_ctr++] = '0' + (num / 10); + size_t offset = 1; // start after OP_RETURN + int num_pushes = 0; + const char hex[] = "0123456789abcdef"; + + while (offset < script_len && num_pushes < 5) { + uint8_t opcode = script[offset++]; + size_t hex_length = 0; // Data length to process + + if (opcode > OP_16 || opcode == OP_RESERVED || opcode == OP_PUSHDATA2 || + opcode == OP_PUSHDATA4) { + return -1; // unsupported + } + + if (opcode == OP_0) { + out[out_ctr++] = '0'; + } else if (opcode >= 1 && opcode <= 75) { + // opcodes between 1 and 75 indicate a data push of the corresponding length + hex_length = opcode; + } else if (opcode == OP_PUSHDATA1) { + // the next byte is the length + if (offset >= script_len) { + return -1; // out of bounds for length byte + } + hex_length = script[offset++]; + if (hex_length <= 75) { + return -1; // non-standard, should have used the minimal push opcode + } + } else if (opcode == OP_1NEGATE) { + out[out_ctr++] = '-'; + out[out_ctr++] = '1'; + } else if (opcode >= OP_1 && opcode <= OP_16) { + uint8_t num = opcode - 0x50; + // num is a number between 1 and 16 (included) + if (num >= 10) { + out[out_ctr++] = '1'; + num -= 10; + } + out[out_ctr++] = '0' + num; + } else { + // any other opcode is invalid or unsupported + return -1; } - out[out_ctr++] = '0' + (num % 10); - } else { - return -1; // can never happen - } - if (hex_length > 0) { - const char hex[] = "0123456789abcdef"; + if (offset + hex_length > script_len) { + // overflow, not enough bytes to read in the script + return -1; + } + + if (hex_length == 1) { + if (script[offset] == 0x81 || script[offset] <= 16) { + // non-standard, it should use OP_1NEGATE, or one of OP_0, ..., OP_16 + return -1; + } + } - out[out_ctr++] = '0'; - out[out_ctr++] = 'x'; - for (unsigned int i = 0; i < hex_length; i++) { - uint8_t data = script[hex_offset + i]; - out[out_ctr++] = hex[data / 16]; - out[out_ctr++] = hex[data % 16]; + if (hex_length > 0) { + out[out_ctr++] = '0'; + out[out_ctr++] = 'x'; + for (unsigned int i = 0; i < hex_length; i++) { + uint8_t data = script[offset + i]; + out[out_ctr++] = hex[data / 16]; + out[out_ctr++] = hex[data % 16]; + } + offset += hex_length; } + + num_pushes++; + out[out_ctr++] = ' '; } - out[out_ctr++] = '\0'; + if (offset < script_len) { + // if there are still more opcodes left, we do not support this script + // (for example: more than 5 push opcodes) + return -1; + } + + out[out_ctr - 1] = '\0'; return out_ctr; -} \ No newline at end of file +} diff --git a/src/common/script.h b/src/common/script.h index b443a761d..f0fa6e48f 100644 --- a/src/common/script.h +++ b/src/common/script.h @@ -126,8 +126,10 @@ enum opcodetype { // expansion OP_NOP1 = 0xb0, OP_CHECKLOCKTIMEVERIFY = 0xb1, + OP_CLTV = OP_CHECKLOCKTIMEVERIFY, OP_NOP2 = OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY = 0xb2, + OP_CSV = OP_CHECKSEQUENCEVERIFY, OP_NOP3 = OP_CHECKSEQUENCEVERIFY, OP_NOP4 = 0xb3, OP_NOP5 = 0xb4, @@ -168,6 +170,11 @@ static inline bool is_opreturn_burn(const uint8_t script[], size_t script_len) { return script_len == 1 && script[0] == OP_RETURN; } +/** + * Returns the size in bytes of the minimal push opcode for , where n a uint32_t. + */ +size_t get_push_script_size(uint32_t n); + /** * Returns a constant of type `script_type_e` indicating the type of known script type with an * address, or -1 for any invalid script, or valid script without an address. @@ -192,33 +199,40 @@ int get_script_type(const uint8_t script[], size_t script_len); * not have an associated address (e.g. OP_RETURN), or the resulting address is too long to fit in * out. */ -int get_script_address(const uint8_t script[], - size_t script_len, - char *out, - size_t out_len); +int get_script_address(const uint8_t script[], size_t script_len, char *out, size_t out_len); #endif -// the longest OP_RETURN description "OP_RETURN 0x" followed by 160 hexadecimal characters -#define MAX_OPRETURN_OUTPUT_DESC_SIZE (12 + 80 * 2 + 1) +// the longest OP_RETURN description is upper bounded by: +// - 9 bytes for "OP_RETURN" +// - 5 times 3 for the " 0x" +// - up to 2 * 80 = 160 hexadecimal bytes +// - the termination null character +#define MAX_OPRETURN_OUTPUT_DESC_SIZE (9 + 5 * 3 + 2 * 80 + 1) /** * Formats a valid OP_RETURN script for user verification. The resulting string is "OP_RETURN - * ", where is written according to the rules below. Only scripts with a single push - * opcode are supported, and OP_PUSHDATA2 and OP_PUSHDATA4 are not supported. OP_1NEGATE is + * ", where is written according to the rules below. Only scripts with up to 5 push + * opcodes are supported, and OP_PUSHDATA2 and OP_PUSHDATA4 are not supported. OP_1NEGATE is * represented as "-1", and OP_0, OP_1, ..., OP_16 are represented in decimal ("0", "1", ..., "16"). * For other push opcodes, the data is represented in hexadecimal, two characters per byte, with the * "0x" prefix. * + * As a best-effort measure, this function returns an error if the transaction is non-standard + * according to the default rules of Bitcoin Core (maximum 80 bytes of data, only push + * instructions). Such transactions, even if valid, would not easily be relayed in the default + * mempool. + * An exception is an output script with a single OP_RETURN is accepted despite being non-standard, + * as such an output is used in BIP-0322. + * * The string is written onto `out` and is 0-terminated. Its length is returned. * * @param script the script to parse and format. * @param script_len the length of the script. - * @param out the output array, that must be at least MAX_OPRETURN_OUTPUT_DESC_SIZE bytes long. The - * longest possible string is "OP_RETURN 0x" followed by 160 hexadecimal characters, plus the - * terminating null character, for a total of 173 characters. + * @param out the output array, that must be at least MAX_OPRETURN_OUTPUT_DESC_SIZE bytes long. * @return The length of the string written into `out` (including the terminating 0) on success; -1 - * on error. + * on error, or if such an output would make the transaction be non-standard per the default relay + * rules of Bitcoin Core. */ int format_opscript_script(const uint8_t script[], size_t script_len, diff --git a/src/common/varint.c b/src/common/varint.c index 29fc660be..943b5e70b 100644 --- a/src/common/varint.c +++ b/src/common/varint.c @@ -1,5 +1,5 @@ /***************************************************************************** - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/common/wallet.c b/src/common/wallet.c index 2f09494b1..ca45358f1 100644 --- a/src/common/wallet.c +++ b/src/common/wallet.c @@ -1,108 +1,66 @@ #include #include #include -#include +#include "../common/base58.h" #include "../common/bip32.h" #include "../common/buffer.h" +#include "../common/script.h" #include "../common/segwit_addr.h" #include "../common/wallet.h" -#include "../liquid/liquid.h" + +#include "../cxram_stash.h" #include "../boilerplate/sw.h" -#include "../crypto.h" -#include "base58.h" -#include "util.h" -#include "read.h" +#include "../debug-helpers/debug.h" -#ifdef SKIP_FOR_CMOCKA +#ifndef SKIP_FOR_CMOCKA +#include "../crypto.h" +#else // disable problematic macros when compiling unit tests with CMOCKA #define PRINTF(...) #define PIC(x) (x) -#endif +#endif // SKIP_FOR_CMOCKA -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcomment" -// The compiler doesn't like /** inside a block comment, so we disable this warning temporarily. - -/* -Currently supported policies for singlesig: - -- pkh(key/**) where `key` follows `BIP44` (legacy) -- wpkh(key/**) where `key` follows `BIP 84` (native segwit) -- sh(wpkh(key/**)) where `key` follows `BIP 49` (nested segwit) -- tr(key/**) where `key` follows `BIP 86` (single-key p2tr) - -Currently supported wallet policies for multisig: - - LEGACY - sh(multi(...))) - sh(sortedmulti(...))) - - NATIVE SEGWIT - wsh(multi(...)) - wsh(sortedmulti(...)) - - WRAPPED SEGWIT - sh(wsh(multi(...))) - sh(wsh(sortedmulti(...))) -*/ - -#pragma GCC diagnostic pop - -// TODO: add unit tests to this module - -/// Bits specifying used characters -typedef enum { - // BASIC CATEGORIES ///////////////////////////////////////////// - /// Numbers 0...9 - CHARSET_NUM = 1 << 0, - /// Lowercase latin letters a...f - CHARSET_ALPHA_AF_LOW = 1 << 1, - /// Lowercase latin letters g...z - CHARSET_ALPHA_GZ_LOW = 1 << 2, - /// Uppercase latin letters a...f - CHARSET_ALPHA_AF_UP = 1 << 3, - /// Uppercase latin letters g...z - CHARSET_ALPHA_GZ_UP = 1 << 4, - /// Brackets () - CHARSET_BRACKETS = 1 << 5, - /// Other characters - CHARSET_OTHER = 1 << 6, - - // COMBINATIONS OT TRAITS /////////////////////////////////////// - /// Lowercase hexadecimal numbers - CHARSET_HEX_LOW = (CHARSET_NUM|CHARSET_ALPHA_AF_LOW), - /// Lowercase latin letters - CHARSET_ALPHA_LOW = (CHARSET_ALPHA_AF_LOW|CHARSET_ALPHA_GZ_LOW), - /// Uppercase latin letters - CHARSET_ALPHA_UP = (CHARSET_ALPHA_AF_UP|CHARSET_ALPHA_GZ_UP), - /// Latin letters of any case - CHARSET_ALPHA = (CHARSET_ALPHA_LOW|CHARSET_ALPHA_UP), - /// Alphanumeric: numbers and latin letters of any case - CHARSET_ALPHANUM = (CHARSET_NUM|CHARSET_ALPHA), - /// Alphanumeric: numbers and lowercase latin letters - CHARSET_ALPHANUM_LOW = (CHARSET_NUM|CHARSET_ALPHA_LOW), - /// Alphanumeric: numbers and uppercase latin letters - CHARSET_ALPHANUM_UP = (CHARSET_NUM|CHARSET_ALPHA_UP), -} charset_t; - -/// Token descriptor typedef struct { - PolicyNodeType type; ///< Node type - const char *name; ///< Token name + PolicyNodeType type; + const char *name; } token_descriptor_t; -/// Table of token descriptors static const token_descriptor_t KNOWN_TOKENS[] = { {.type = TOKEN_SH, .name = "sh"}, {.type = TOKEN_WSH, .name = "wsh"}, {.type = TOKEN_PKH, .name = "pkh"}, {.type = TOKEN_WPKH, .name = "wpkh"}, {.type = TOKEN_MULTI, .name = "multi"}, + {.type = TOKEN_MULTI_A, .name = "multi_a"}, {.type = TOKEN_SORTEDMULTI, .name = "sortedmulti"}, + {.type = TOKEN_SORTEDMULTI_A, .name = "sortedmulti_a"}, {.type = TOKEN_TR, .name = "tr"}, + + // miniscript tokens (except wrappers) + {.type = TOKEN_0, .name = "0"}, + {.type = TOKEN_1, .name = "1"}, + {.type = TOKEN_PK, .name = "pk"}, + {.type = TOKEN_PK_K, .name = "pk_k"}, + {.type = TOKEN_PK_H, .name = "pk_h"}, + {.type = TOKEN_OLDER, .name = "older"}, + {.type = TOKEN_AFTER, .name = "after"}, + {.type = TOKEN_SHA256, .name = "sha256"}, + {.type = TOKEN_HASH256, .name = "hash256"}, + {.type = TOKEN_RIPEMD160, .name = "ripemd160"}, + {.type = TOKEN_HASH160, .name = "hash160"}, + {.type = TOKEN_ANDOR, .name = "andor"}, + {.type = TOKEN_AND_V, .name = "and_v"}, + {.type = TOKEN_AND_B, .name = "and_b"}, + {.type = TOKEN_AND_N, .name = "and_n"}, + {.type = TOKEN_OR_B, .name = "or_b"}, + {.type = TOKEN_OR_C, .name = "or_c"}, + {.type = TOKEN_OR_D, .name = "or_d"}, + {.type = TOKEN_OR_I, .name = "or_i"}, + {.type = TOKEN_THRESH, .name = "thresh"}, + #ifdef HAVE_LIQUID {.type = TOKEN_CT, .name = "ct"}, {.type = TOKEN_SH, .name = "elsh"}, @@ -111,83 +69,116 @@ static const token_descriptor_t KNOWN_TOKENS[] = { {.type = TOKEN_WPKH, .name = "elwpkh"}, {.type = TOKEN_MULTI, .name = "elmulti"}, {.type = TOKEN_SORTEDMULTI, .name = "elsortedmulti"}, + {.type = TOKEN_SORTEDMULTI_A, .name = "elsortedmulti_a"}, {.type = TOKEN_TR, .name = "eltr"}, #endif }; -/// Maximum length of blinding key returned token prefix in characters -#define TOKEN_PREFIX_LEN 7 +// lookup table for characters that represent a valid miniscript wrapper fragment +const bool is_valid_miniscript_wrapper[] = { + 1, // "a" + 0, // "b" + 1, // "c" + 1, // "d" + 0, // "e" + 0, // "f" + 0, // "g" + 0, // "h" + 0, // "i" + 1, // "j" + 0, // "k" + 1, // "l" + 0, // "m" + 1, // "n" + 0, // "o" + 0, // "p" + 0, // "q" + 0, // "r" + 1, // "s" + 1, // "t" + 1, // "u" + 1, // "v" + 0, // "w" + 0, // "x" + 0, // "y" + 0, // "z" +}; -/// Token scan result -typedef struct { - /// Detected token length - size_t token_len; - /// Charset detected, a combination of `charset_t` flags - uint32_t charset; - /// Token prefix string, null terminated. Containins up to TOKEN_PREFIX_LEN first - /// characters of the token. - char prefix[TOKEN_PREFIX_LEN + 1]; -} token_scan_result_t; +/** + * Length of the longest token in the policy wallet descriptor language (not including the + * terminating \0 byte). + */ #ifdef HAVE_LIQUID /** * Length of the longest token in the policy wallet descriptor language (not including the * terminating \0 byte). */ -#define MAX_TOKEN_LENGTH (sizeof("elsortedmulti") - 1) +#define MAX_TOKEN_LENGTH (sizeof("elsortedmulti_a") - 1) #else /** * Length of the longest token in the policy wallet descriptor language (not including the * terminating \0 byte). */ -#define MAX_TOKEN_LENGTH (sizeof("sortedmulti") - 1) +#define MAX_TOKEN_LENGTH (sizeof("sortedmulti_a") - 1) #endif -int read_policy_map_wallet(buffer_t *buffer, policy_map_wallet_header_t *header) { - if (!buffer_read_u8(buffer, &header->type)) { - return -1; +int read_wallet_policy_header(buffer_t *buffer, policy_map_wallet_header_t *header) { + if (!buffer_read_u8(buffer, &header->version)) { + return WITH_ERROR(-1, "Invalid wallet policy header"); } - if (header->type != WALLET_TYPE_POLICY_MAP) { - return -2; + if (header->version != WALLET_POLICY_VERSION_V1 && + header->version != WALLET_POLICY_VERSION_V2) { + return WITH_ERROR(-1, "Invalid wallet policy header"); } if (!buffer_read_u8(buffer, &header->name_len)) { - return -3; + return WITH_ERROR(-1, "Invalid wallet policy header"); } if (header->name_len > MAX_WALLET_NAME_LENGTH) { - return -4; + return WITH_ERROR(-1, "Invalid wallet policy header"); } if (!buffer_read_bytes(buffer, (uint8_t *) header->name, header->name_len)) { - return -5; + return WITH_ERROR(-1, "Invalid wallet policy header"); } header->name[header->name_len] = '\0'; - uint64_t policy_map_len; - if (!buffer_read_varint(buffer, &policy_map_len) || - policy_map_len > MAX_POLICY_MAP_STR_LENGTH) { - return -6; + uint64_t descriptor_template_len; + if (!buffer_read_varint(buffer, &descriptor_template_len)) { + return WITH_ERROR(-1, "Invalid wallet policy header"); } - header->policy_map_len = (uint16_t) policy_map_len; + header->descriptor_template_len = (uint16_t) descriptor_template_len; - if (header->policy_map_len > MAX_POLICY_MAP_STR_LENGTH) { - return -7; - } + if (header->version == WALLET_POLICY_VERSION_V1) { + if (descriptor_template_len > MAX_DESCRIPTOR_TEMPLATE_LENGTH_V1) { + return WITH_ERROR(-1, "Invalid wallet policy header: descriptor template too long"); + } + if (!buffer_read_bytes(buffer, + (uint8_t *) header->descriptor_template, + header->descriptor_template_len)) { + return WITH_ERROR(-1, "Invalid wallet policy header"); + } + } else { // WALLET_POLICY_VERSION_V2 + if (descriptor_template_len > MAX_DESCRIPTOR_TEMPLATE_LENGTH_V2) { + return WITH_ERROR(-1, "Invalid wallet policy header: descriptor template too long"); + } - if (!buffer_read_bytes(buffer, (uint8_t *) header->policy_map, header->policy_map_len)) { - return -8; + if (!buffer_read_bytes(buffer, (uint8_t *) header->descriptor_template_sha256, 32)) { + return WITH_ERROR(-1, "Invalid wallet policy header"); + } } uint64_t n_keys; if (!buffer_read_varint(buffer, &n_keys) || n_keys > 252) { - return -9; + return WITH_ERROR(-1, "Invalid wallet policy header"); } header->n_keys = (uint16_t) n_keys; if (!buffer_read_bytes(buffer, (uint8_t *) header->keys_info_merkle_root, 32)) { - return -10; + return WITH_ERROR(-1, "Invalid wallet policy header"); } return 0; @@ -253,32 +244,33 @@ static uint8_t lowercase_hex_to_int(char c) { return (uint8_t) (is_digit(c) ? c - '0' : c - 'a' + 10); } -// TODO: remove -#if 0 -/** - * Read up to out_len characters from buffer, until either: - * - the buffer is exhausted - * - out_len characters are read - * - the next character is _not_ in [a-zAZ] - */ -static size_t read_word(buffer_t *buffer, char *out, size_t out_len) { - size_t word_len = 0; - uint8_t c; - while (word_len < out_len && buffer_peek(buffer, &c) && is_alpha((char) c)) { - out[word_len++] = (char) c; - buffer_seek_cur(buffer, 1); +static bool consume_character(buffer_t *in_buf, char expected) { + char c; + if (!buffer_peek(in_buf, (uint8_t *) &c) || c != expected) { + return false; } - return word_len; + buffer_seek_cur(in_buf, 1); + return true; +} + +static bool consume_characters(buffer_t *in_buf, const char *expected, size_t len) { + char c; + for (size_t i = 0; i < len; i++) { + if (!buffer_peek_n(in_buf, i, (uint8_t *) &c) || c != expected[i]) { + return false; + } + } + buffer_seek_cur(in_buf, len); + return true; } -#endif // 0 /** - * Reads a single tag from the buffer. + * Reads a single token from the buffer. * * Read up to out_len characters from buffer, until either: - * - the buffer is exhausted - * - out_len characters are read - * - the next character is _not_ in [a-zAZ], [0-9] + * - the buffer is exhausted + * - out_len characters are read + * - the next character is _not_ in [a-zAZ0-9_] * * @param[in,out] buffer * Input buffer. @@ -289,144 +281,43 @@ static size_t read_word(buffer_t *buffer, char *out, size_t out_len) { * * @return length of outputted tag in bytes. */ -static size_t read_tag(buffer_t *buffer, char *out, size_t out_len) { - size_t tag_len = 0; - uint8_t c; - while (tag_len < out_len && buffer_peek(buffer, &c) && is_alphanumeric((char) c)) { - out[tag_len++] = (char) c; +static size_t read_token(buffer_t *buffer, char *out, size_t out_len) { + size_t word_len = 0; + char c; + while (word_len < out_len && buffer_peek(buffer, (uint8_t *) &c) && + (is_alphanumeric(c) || c == '_')) { + out[word_len++] = c; buffer_seek_cur(buffer, 1); } - return tag_len; -} - -/** - * Reads lowercase hexadecimal data bytes from buffer. - * - * @param[in,out] buffer - * Input buffer. - * @param[out] out - * Pointer to output buffer. It is the responsibility of the caller to make sure that the output - * buffer is not smaller than the value in variable pointed by `out_len`. - * @param[in] out_len - * Maximum number of bytes to read, must be no greater than INT_MAX. - * @param[in] terminator - * Terminating character used to stop reading input data. Set to -1 if this feature is not needed. - * - * @return length of outputted data in bytes or -1 in case of error - */ -static int read_lowercase_hex_data(buffer_t *buffer, - uint8_t *out, - size_t out_len, - int terminator) { - size_t out_idx = 0; - uint8_t c; - char num[2]; - - if (out_len > INT_MAX) { - return -1; - } - - while (buffer_peek(buffer, &c) && c != terminator && out_idx < out_len) { - if (!buffer_read_bytes(buffer, (uint8_t *) num, 2)) { - return -1; - } - if (!is_lowercase_hex(num[0]) || !is_lowercase_hex(num[1])) { - return -1; - } - out[out_idx++] = lowercase_hex_to_int(num[0]) << 4 | lowercase_hex_to_int(num[1]); - } - return (int)out_idx; + return word_len; } /** - * Reads a single tag from the buffer and finds corresponding type of policy node. - * - * @param[in,out] buffer - * Input buffer. - * - * @return type of policy node or -1 if not found. + * Read the next word from buffer (or up to MAX_TOKEN_LENGTH characters), and + * returns the index of this word in KNOWN_TOKENS if found; TOKEN_INVALID otherwise. */ -static int parse_token(buffer_t *buffer) { +static PolicyNodeType parse_token(buffer_t *buffer) { char word[MAX_TOKEN_LENGTH + 1]; - size_t word_len = read_tag(buffer, word, MAX_TOKEN_LENGTH); + size_t word_len = read_token(buffer, word, MAX_TOKEN_LENGTH); word[word_len] = '\0'; for (unsigned int i = 0; i < sizeof(KNOWN_TOKENS) / sizeof(KNOWN_TOKENS[0]); i++) { if (strncmp((const char *) PIC(KNOWN_TOKENS[i].name), word, MAX_TOKEN_LENGTH) == 0) { - return (int) PIC(KNOWN_TOKENS[i].type); - } - } - - return -1; -} - -/** - * Scans a single token in the buffer while keeping its position. - * - * @param[in] buffer - * Input buffer with a token to scan, position is preserved. - * @param[in] separator - * A separator character on which scan process is stopped. - * @param[out] result - * Pointer to structure instance receiving scan results. - * - * @return true if sucessfull, false in case of error - */ -static bool scan_token(buffer_t *buffer, - char separator, - token_scan_result_t *result) { - buffer_snapshot_t in_buf_snapshot = buffer_snapshot(buffer); - memset(result, 0, sizeof(token_scan_result_t)); - - char c; - while (buffer_peek(buffer, (uint8_t*)&c) && c != separator) { - if (++result->token_len < sizeof(result->prefix)) { - result->prefix[result->token_len - 1] = c; - } - - if (c >= '0' && c <= '9') { - result->charset |= CHARSET_NUM; - } else if (c >= 'a' && c <= 'f') { - result->charset |= CHARSET_ALPHA_AF_LOW; - } else if (c >= 'g' && c <= 'z') { - result->charset |= CHARSET_ALPHA_GZ_LOW; - } else if (c >= 'A' && c <= 'F') { - result->charset |= CHARSET_ALPHA_AF_UP; - } else if (c >= 'G' && c <= 'Z') { - result->charset |= CHARSET_ALPHA_GZ_UP; - } else if (c == '(' || c == ')') { - result->charset |= CHARSET_BRACKETS; - } else { - result->charset |= CHARSET_OTHER; + return ((const token_descriptor_t *) PIC(&KNOWN_TOKENS[i]))->type; } - buffer_seek_cur(buffer, 1); } - - buffer_restore(buffer, in_buf_snapshot); - return !!result->token_len; + return TOKEN_INVALID; } /** - * Parses an unsigned decimal number from buffer. - * - * Parsing stops when either the buffer ends, the next character is not a number, or the number is - * already too big. Leading zeros are not allowed. - * - * @param[in,out] buffer - * Input buffer. - * @param[out] out - * Pointer to variable receiving resulting integer. - * - * @return 0 on success, -1 on failure. + * Parses an unsigned decimal number from buffer, stopping when either the buffer ends, the next + * character is not a number, or the number is already too big. Leading zeros are not allowed. + * Returns a valid 0 on success, -1 on failure. + * The read number is saved into *out on success. */ -static int parse_unsigned_decimal(buffer_t *buffer, size_t *out) { +static int parse_unsigned_decimal(buffer_t *buffer, uint32_t *out) { uint8_t c; - if (!buffer_peek(buffer, &c) || !is_digit(c)) { - PRINTF("parse_unsigned_decimal: couldn't read byte, or not a digit: %d\n", c); - return -1; - } - size_t result = 0; int digits_read = 0; while (buffer_peek(buffer, &c) && is_digit(c)) { @@ -434,14 +325,11 @@ static int parse_unsigned_decimal(buffer_t *buffer, size_t *out) { uint8_t next_digit = c - '0'; if (digits_read == 2 && result == 0) { - // if the first digit was a 0, than it should be the only digit + // if the first digit was a 0, then it should be the only digit return -1; } if (10 * result + next_digit < result) { - PRINTF("parse_unsigned_decimal: overflow. Current: %d. Next digit: %d\n", - result, - next_digit); return -1; // overflow, integer too large } @@ -459,90 +347,57 @@ static int parse_unsigned_decimal(buffer_t *buffer, size_t *out) { } /** - * Reads a derivation step from buffer. - * - * Reads a derivation step expressed in decimal, with the symbol ' to mark if hardened (h is not - * supported). - * - * @param[in,out] buffer - * Input buffer. - * @param[out] out - * Output derivation step. - * - * @return 0 on success, -1 on error. + * Reads exactly 2*n lowercase hecadecimal characters, storing them in exactly n bytes in `out` (1 + * byte every two hex characters); returns -1 if any character is not hexadecimal, or if less than + * 2*n characters can be read. */ +static int buffer_read_hex_hash(buffer_t *buffer, uint8_t *out, size_t n) { + if (!buffer_can_read(buffer, 2 * n)) { + return -1; + } + + for (unsigned int i = 0; i < n; i++) { + uint8_t c1, c2; + buffer_read_u8(buffer, &c1); + buffer_read_u8(buffer, &c2); + + if (!is_lowercase_hex(c1) || !is_lowercase_hex(c2)) { + return -1; + } + + out[i] = 16 * lowercase_hex_to_int((char) c1) + lowercase_hex_to_int((char) c2); + } + return 0; +} + +// Reads a derivation step expressed in decimal, with the symbol ' to mark if hardened (h is not +// supported) Returns 0 on success, -1 on error. static int buffer_read_derivation_step(buffer_t *buffer, uint32_t *out) { - size_t der_step; + uint32_t der_step; if (parse_unsigned_decimal(buffer, &der_step) == -1 || der_step >= BIP32_FIRST_HARDENED_CHILD) { PRINTF("Failed reading derivation step\n"); return -1; } - *out = (uint32_t)der_step; + *out = der_step; // Check if hardened - uint8_t c; - if (buffer_peek(buffer, &c) && c == '\'') { + if (consume_character(buffer, '\'')) { *out |= BIP32_FIRST_HARDENED_CHILD; - buffer_seek_cur(buffer, 1); // skip the ' character } return 0; } -/// Wildcard signature -typedef struct { - /// Wildcard numeric identifier. - policy_map_key_wildcard_id_t id; - /// Wildcard represented as a text string. - const char *str; -} wildcard_signature_t; - -/// Table of wildcard signatures -const wildcard_signature_t WILDCARD_SIGNATURES[] = { - { .id = KEY_WILDCARD_NONE, .str = "" }, - { .id = KEY_WILDCARD_ANY, .str = "/**" }, - { .id = KEY_WILDCARD_STANDARD_CHAINS, .str = "/<0;1>/*" }, - { .id = KEY_WILDCARD_EXTERNAL_CHAIN, .str = "/0/*" }, - { .id = KEY_WILDCARD_INTERNAL_CHAIN, .str = "/1/*" } -}; -/// Number of records in the table of wildcard signatures -static const size_t N_WILDCARD_SIGNATURES = - sizeof(WILDCARD_SIGNATURES) / sizeof(WILDCARD_SIGNATURES[0]); - -/** - * Finds the numeric wildcard identifier corresponding to a given wildcard string. - * - * @param[in] wildcard_str - * Wildcard represented as a text string. - * - * @return a non-negative wildcard identifier or -1 if not found - */ -static int find_wildcard(const char *wildcard_str) { - for (size_t i = 0; i < N_WILDCARD_SIGNATURES; ++i) { - const char *curr_str = (const char *) PIC(WILDCARD_SIGNATURES[i].str); - if (0 == strncmp(curr_str, wildcard_str, MAX_POLICY_MAP_KEY_WILDCARD_LEN)) { - return (int) PIC(WILDCARD_SIGNATURES[i].id); - } +int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out, int version) { + if (version != WALLET_POLICY_VERSION_V1 && version != WALLET_POLICY_VERSION_V2) { + return WITH_ERROR(-1, "Invalid version"); } - return -1; -} -// TODO: we are currently enforcing that the master key fingerprint (if present) is in lowercase -// hexadecimal digits, -// and that the symbol for "hardened derivation" is "'". -// This implies descriptors should be normalized on the client side. -int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out) { memset(out, 0, sizeof(policy_map_key_info_t)); - uint8_t c; - if (!buffer_peek(buffer, &c)) { - return -1; - } - - if (c == '[') { + if (consume_character(buffer, '[')) { out->has_key_origin = 1; - buffer_seek_cur(buffer, 1); // skip 1 byte if (!buffer_can_read(buffer, 9)) { // at least 8 bytes + (closing parenthesis or '\') return -1; } @@ -558,10 +413,9 @@ int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out) { // read all the given derivation steps out->master_key_derivation_len = 0; - while (buffer_peek(buffer, &c) && c == '/') { - buffer_seek_cur(buffer, 1); // skip the '/' character + while (consume_character(buffer, '/')) { if (out->master_key_derivation_len > MAX_BIP32_PATH_STEPS) { - return -1; + return WITH_ERROR(-1, "Too many derivation steps"); } if (buffer_read_derivation_step( @@ -574,711 +428,2479 @@ int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out) { } // the next character must be ']' - if (!buffer_read_u8(buffer, &c) || c != ']') { - return -1; + if (!consume_character(buffer, ']')) { + return WITH_ERROR(-1, "Expected ']'"); } } // consume the rest of the buffer into the pubkey, except possibly the final "/**" unsigned int ext_pubkey_len = 0; + char ext_pubkey_str[MAX_SERIALIZED_PUBKEY_LENGTH]; + uint8_t c; while (ext_pubkey_len < MAX_SERIALIZED_PUBKEY_LENGTH && buffer_peek(buffer, &c) && is_alphanumeric(c)) { - out->ext_pubkey[ext_pubkey_len] = c; + ext_pubkey_str[ext_pubkey_len] = c; ++ext_pubkey_len; buffer_seek_cur(buffer, 1); } - out->ext_pubkey[ext_pubkey_len] = '\0'; + ext_pubkey_str[ext_pubkey_len] = '\0'; - // Read the final wildcard part of the public key. There should be no more characters remaining. - char wildcard_str[MAX_POLICY_MAP_KEY_WILDCARD_LEN + 1]; - size_t wildcard_len = buffer_remaining(buffer); - if (wildcard_len >= sizeof(wildcard_str)) { - return -1; - } - if (wildcard_len && !buffer_read_bytes(buffer, (uint8_t*)wildcard_str, wildcard_len) ) { - return -1; + if (ext_pubkey_len < 111 || ext_pubkey_len > 112) { + // loose sanity check; pubkeys in bitcoin can be 111 or 112 characters long + return WITH_ERROR(-1, "Invalid extended pubkey length"); } - wildcard_str[wildcard_len] = '\0'; - // Find the numeric wildcard identifier in the table - int wildcard_id = find_wildcard(wildcard_str); - if (wildcard_id < 0 || wildcard_id > UINT8_MAX) { - return -1; + serialized_extended_pubkey_check_t ext_pubkey_check; + if (base58_decode(ext_pubkey_str, + ext_pubkey_len, + (uint8_t *) &ext_pubkey_check, + sizeof(ext_pubkey_check)) < 0) { + return WITH_ERROR(-1, "Error decoding serialized extended pubkey"); } - out->wildcard_id = (uint8_t)wildcard_id; - return 0; -} - -bool validate_policy_map_extended_pubkey(const policy_map_key_info_t *key_info, - uint32_t bip32_pubkey_version) { - int status = validate_serialized_extended_pubkey( - key_info->ext_pubkey, - key_info->master_key_derivation, - key_info->has_key_origin ? key_info->master_key_derivation_len : -1, - bip32_pubkey_version - ); - - return EXTENDED_PUBKEY_VALID == status; -} - -/** - * Parses key index from the input buffer. - * - * @param[in,out] in_buf - * Input buffer. - * - * @return a non-negative integer key index or -1 if error. - */ -static size_t parse_key_index(buffer_t *in_buf) { - char c; - if (!buffer_read_u8(in_buf, (uint8_t *) &c) || c != '@') { - return -1; - } + // verify checksum + uint8_t checksum[4]; + crypto_get_checksum((uint8_t *) &ext_pubkey_check.serialized_extended_pubkey, + sizeof(ext_pubkey_check.serialized_extended_pubkey), + checksum); - size_t k; - if (parse_unsigned_decimal(in_buf, &k) == -1) { - return -1; + if (memcmp(&ext_pubkey_check.checksum, checksum, sizeof(checksum)) != 0) { + return WITH_ERROR(-1, "Wrong extended pubkey checksum"); } - return k; -} -/// Flag: current context is within sh() -#define CONTEXT_WITHIN_SH (1U << 0) -/// Flag: current context is within ct() -#define CONTEXT_WITHIN_CT (1U << 1) + out->ext_pubkey = ext_pubkey_check.serialized_extended_pubkey; -/// Script parser context -typedef struct { - /// Input buffer with a script expression to parse. - buffer_t *in_buf; - /// Output buffer which receives a tree-like structure of nodes - buffer_t *out_buf; - /// Version prefix to use for the public key. - uint32_t bip32_pubkey_version; - /// Version prefix to use for the private key. - uint32_t bip32_privkey_version; -} script_parser_ctx_t; + // either the string terminates now, or it has a final "/**" suffix for the wildcard. + if (!buffer_can_read(buffer, 1)) { + // no wildcard; this is an error in V1 + if (version == WALLET_POLICY_VERSION_V1) { + return WITH_ERROR( + -1, + "Invalid key expression; keys in V1 wallet policies must end with /**."); + } -#ifdef HAVE_LIQUID + return 0; + } -/** - * Prototype for function implementing blinding key parser. - * - * This function should parse a BLINDING_KEY expression enclosed in ct() tag as specified in - * ELIP: 150 and ELIP 151 from the `in_buf` buffer, aallocating the nodes and variables in - * `out_buf`. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] token_len - * Size of key token in characters. - * - * @return 0 if successful, a negative number on error. - */ -typedef int (*blinding_key_parser_t)(script_parser_ctx_t *ctx, size_t token_len); + // in V2, key expressions terminate with the key (no wildcards) + if (version == WALLET_POLICY_VERSION_V2) { + return WITH_ERROR(-1, "Invalid key expression; must terminate after the key/xpub"); + } -/** - * Parses slip77() expression within BLINDING_KEY context. - * - * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] token_len - * Size of key token in characters. - * - * @return 0 if successful, a negative number on error. - */ -static int parse_ct_slip77(script_parser_ctx_t *ctx, size_t token_len) { - UNUSED(token_len); + out->has_wildcard = 1; - policy_node_blinding_privkey_t *node = (policy_node_blinding_privkey_t *) - buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_privkey_t), true); - if (NULL == node) { + // Only the final "/**" suffix should be left + uint8_t wildcard[3]; + // Make sure that the buffer is indeed exhausted + if (!buffer_read_bytes(buffer, wildcard, 3) // should be able to read 3 characters + || buffer_can_read(buffer, 1) // but nothing more + || wildcard[0] != '/' // suffix should be exactly "/**" + || wildcard[1] != '*' || wildcard[2] != '*') { return -1; } - node->type = TOKEN_SLIP77; - - bool ok = buffer_skip_data(ctx->in_buf, (const uint8_t*) "slip77(", sizeof("slip77(") - 1); - ok = ok && sizeof(node->privkey) == - read_lowercase_hex_data(ctx->in_buf, node->privkey, sizeof(node->privkey), ')'); - ok = ok && buffer_skip_data(ctx->in_buf, (const uint8_t*) ")", 1); - return ok ? 0 : -1; + return 0; } -/** - * Parses hexadecimal public key expression within BLINDING_KEY context. - * - * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] token_len - * Size of key token in characters. - * - * @return 0 if successful, a negative number on error. - */ -static int parse_ct_hex_pubkey(script_parser_ctx_t *ctx, size_t token_len) { - UNUSED(token_len); - - policy_node_blinding_pubkey_t *node = (policy_node_blinding_pubkey_t *) - buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_pubkey_t), true); - if (NULL == node) { - return -1; +static int parse_placeholder(buffer_t *in_buf, int version, policy_node_key_placeholder_t *out) { + char c; + if (!buffer_read_u8(in_buf, (uint8_t *) &c) || c != '@') { + return WITH_ERROR(-1, "Expected key placeholder starting with '@'"); } - node->type = TOKEN_HEX_PUB; - bool ok = sizeof(node->pubkey) == - read_lowercase_hex_data(ctx->in_buf, node->pubkey, sizeof(node->pubkey), ','); - - return ok && (0x02 == node->pubkey[0] || 0x03 == node->pubkey[0]) ? 0 : -1; -} + uint32_t k; + if (parse_unsigned_decimal(in_buf, &k) == -1 || k > INT16_MAX) { + return WITH_ERROR(-1, "The key index in a placeholder must be at most 32767"); + } -/** - * Parses hexadecimal private key expression within BLINDING_KEY context. - * - * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] token_len - * Size of key token in characters. - * - * @return 0 if successful, a negative number on error. - */ -static int parse_ct_hex_privkey(script_parser_ctx_t *ctx, size_t token_len) { - UNUSED(token_len); + out->key_index = (int16_t) k; + + if (version == WALLET_POLICY_VERSION_V1) { + // default values for compatibility with the new code + out->num_first = 0; + out->num_second = 1; + } else if (version == WALLET_POLICY_VERSION_V2) { + // the key expression must be followed by / and **, or /<0;1>/* + uint8_t next_character; + if (!consume_character(in_buf, '/') // the next character is "/" + || !buffer_peek(in_buf, &next_character) // we must be able to read the next character + || !(next_character == '*' || next_character == '<') // and it must be '*' or '<' + ) { + return WITH_ERROR(-1, "Expected /** or //* in key placeholder"); + } - policy_node_blinding_privkey_t *node = (policy_node_blinding_privkey_t *) - buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_privkey_t), true); - if (NULL == node) { - return -1; - } - node->type = TOKEN_HEX_PRV; + if (next_character == '*') { + if (!consume_characters(in_buf, "**", 2)) { + return WITH_ERROR(-1, "Expected /** or //* in key placeholder"); + } + out->num_first = 0; + out->num_second = 1; + } else if (next_character == '<') { + buffer_seek_cur(in_buf, 1); // skip "<" + if (parse_unsigned_decimal(in_buf, &out->num_first) == -1 || + out->num_first > 0x80000000u) { + return WITH_ERROR( + -1, + "Expected /** or //* in key placeholder, with unhardened M and N"); + } - bool ok = sizeof(node->privkey) == - read_lowercase_hex_data(ctx->in_buf, node->privkey, sizeof(node->privkey), ','); + if (!consume_character(in_buf, ';')) { + return WITH_ERROR(-1, "Expected /** or //* in key placeholder"); + } - return ok ? 0 : -1; -} + if (parse_unsigned_decimal(in_buf, &out->num_second) == -1 || + out->num_second > 0x80000000u) { + return WITH_ERROR( + -1, + "Expected /** or //* in key placeholder, with unhardened M and N"); + } -/** - * Parses xpub expression within BLINDING_KEY context. - * - * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] token_len - * Size of key token in characters. - * - * @return 0 if successful, a negative number on error. - */ -static int parse_ct_xpub(script_parser_ctx_t *ctx, size_t token_len) { - serialized_extended_pubkey_check_t pubkey_check; - const serialized_extended_pubkey_t *pubkey = &pubkey_check.serialized_extended_pubkey; + if (out->num_first == out->num_second) { + return WITH_ERROR(-1, "M and N must be different in /*"); + } - if (!buffer_can_read(ctx->in_buf, token_len)) { - return -1; - } - if (sizeof(pubkey_check) != base58_decode((char*) buffer_get_cur(ctx->in_buf), - token_len, - (uint8_t *) &pubkey_check, - sizeof(pubkey_check))) { - return -1; + if (!consume_characters(in_buf, ">/*", 3)) { + return WITH_ERROR(-1, "Expected /** or //* in key placeholder"); + } + } + } else { + return WITH_ERROR(-1, "Invalid version number"); } - uint8_t checksum[4]; - crypto_get_checksum((uint8_t *)&pubkey_check.serialized_extended_pubkey, - sizeof(pubkey_check.serialized_extended_pubkey), - checksum); - if (!memeq(checksum, pubkey_check.checksum, sizeof(checksum))) { - return -1; - } - if (read_u32_be(pubkey->version, 0) != ctx->bip32_pubkey_version || - !(0x02 == pubkey->compressed_pubkey[0] || 0x03 == pubkey->compressed_pubkey[0])) { - return -1; - } + return 0; +} - policy_node_blinding_pubkey_t *node = (policy_node_blinding_pubkey_t *) - buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_pubkey_t), true); - if (NULL == node) { - return -1; - } - node->type = TOKEN_XPUB; - memcpy(node->pubkey, pubkey->compressed_pubkey, sizeof(node->pubkey)); +#define CONTEXT_WITHIN_SH 1 // parsing a direct child of SH +#define CONTEXT_WITHIN_WSH 2 // parsing a direct child of WSH +#define CONTEXT_WITHIN_TR 4 // parsing a child of TR (direct or not) + +// forward declaration +static int parse_script(buffer_t *in_buf, + buffer_t *out_buf, + int version, + size_t depth, + unsigned int context_flags); + +static int parse_child_scripts(buffer_t *in_buf, + buffer_t *out_buf, + size_t depth, + rptr_policy_node_t child_scripts[], + int n_children, + int version, + unsigned int context_flags) { + // the internal scripts are recursively parsed (if successful) in the current location + // of the output buffer + + for (int child_index = 0; child_index < n_children; child_index++) { + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + i_policy_node(&child_scripts[child_index], buffer_get_cur(out_buf)); + + if (0 > parse_script(in_buf, out_buf, version, depth + 1, context_flags)) { + // failed while parsing internal script + return -1; + } - return buffer_seek_cur(ctx->in_buf, token_len) ? 0 : -1; + // the next character must be a comma (except after the last child) + if (child_index <= n_children - 2 && !consume_character(in_buf, ',')) { + return WITH_ERROR(-1, "Expected ','"); + } + } + return 0; } +// forward-declaration, since it's used in parse_script +static int parse_tree(buffer_t *in_buf, buffer_t *out_buf, int version, size_t depth); + /** - * Parses xprv expression within BLINDING_KEY context. - * - * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] token_len - * Size of key token in characters. - * - * @return 0 if successful, a negative number on error. + * Parses a SCRIPT expression from the in_buf buffer, allocating the nodes and variables in out_buf. + * The initial pointer in out_buf will contain the root node of the SCRIPT. */ -static int parse_ct_xprv(script_parser_ctx_t *ctx, size_t token_len) { - serialized_extended_privkey_check_t privkey_check; - const serialized_extended_privkey_t *privkey = &privkey_check.serialized_extended_privkey; +static int parse_script(buffer_t *in_buf, + buffer_t *out_buf, + int version, + size_t depth, + unsigned int context_flags) { + int n_wrappers = 0; + + policy_node_t *outermost_node = (policy_node_t *) buffer_get_cur(out_buf); + policy_node_with_script_t *inner_wrapper = NULL; // pointer to the inner wrapper, if any + + // miniscript-related parsing only within top-level WSH, or within tr + bool parse_as_miniscript = + ((context_flags & CONTEXT_WITHIN_WSH) != 0 && (context_flags & CONTEXT_WITHIN_SH) == 0) || + (context_flags & CONTEXT_WITHIN_TR) != 0; + + if (parse_as_miniscript) { + // look ahead to finds out if the buffer starts with alphanumeric digits that could be + // wrappers, followed by a colon + char c; + bool can_read; + while (true) { + can_read = buffer_peek_n(in_buf, n_wrappers, (uint8_t *) &c); + if (can_read && 'a' <= c && c <= 'z' && is_valid_miniscript_wrapper[c - 'a']) { + ++n_wrappers; + } else { + break; + } + } - if (!buffer_can_read(ctx->in_buf, token_len)) { - return -1; - } - if (sizeof(privkey_check) != base58_decode((char*) buffer_get_cur(ctx->in_buf), - token_len, - (uint8_t *) &privkey_check, - sizeof(privkey_check))) { - return -1; - } + if (can_read && c == ':') { + // parse wrappers + for (int i = 0; i < n_wrappers; i++) { + policy_node_with_script_t *node = + (policy_node_with_script_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + buffer_read_u8(in_buf, (uint8_t *) &c); + switch (c) { + case 'a': + node->base.type = TOKEN_A; + break; + case 's': + node->base.type = TOKEN_S; + break; + case 'c': + node->base.type = TOKEN_C; + break; + case 't': + node->base.type = TOKEN_T; + break; + case 'd': + node->base.type = TOKEN_D; + break; + case 'v': + node->base.type = TOKEN_V; + break; + case 'j': + node->base.type = TOKEN_J; + break; + case 'n': + node->base.type = TOKEN_N; + break; + case 'l': + node->base.type = TOKEN_L; + break; + case 'u': + node->base.type = TOKEN_U; + break; + default: + PRINTF("Unexpected wrapper: %c\n", c); + return -1; + } - uint8_t checksum[4]; - crypto_get_checksum((uint8_t *)&privkey_check.serialized_extended_privkey, - sizeof(privkey_check.serialized_extended_privkey), - checksum); - if (!memeq(checksum, privkey_check.checksum, sizeof(checksum))) { - return -1; + if (inner_wrapper != NULL) { + i_policy_node(&inner_wrapper->script, node); + } + inner_wrapper = node; + } + buffer_seek_cur(in_buf, 1); // skip ":" + } else { + n_wrappers = 0; // it was not a wrapper + } } - if (read_u32_be(privkey->version, 0) != ctx->bip32_privkey_version || - 0 != privkey->null_prefix) { + + // We read the token, we'll do different parsing based on what token we find + PolicyNodeType token = parse_token(in_buf); + if (token == TOKEN_INVALID) { + PRINTF("Failed to parse token"); return -1; } - policy_node_blinding_privkey_t *node = (policy_node_blinding_privkey_t *) - buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_privkey_t), true); - if (NULL == node) { - return -1; + if (context_flags & CONTEXT_WITHIN_SH) { + // whitelist of allowed tokens within sh; in particular, no miniscript + switch (token) { + case TOKEN_PK: + case TOKEN_PKH: + case TOKEN_MULTI: + case TOKEN_SORTEDMULTI: + case TOKEN_WPKH: + case TOKEN_WSH: + break; + default: + return WITH_ERROR(-1, "Token not allowed within sh"); + } } - node->type = TOKEN_XPRV; - memcpy(node->privkey, privkey->privkey, sizeof(node->privkey)); - return buffer_seek_cur(ctx->in_buf, token_len) ? 0 : -1; -} + if ((context_flags & CONTEXT_WITHIN_SH) != 0 && (context_flags & CONTEXT_WITHIN_WSH) != 0 && + depth == 2) { + // whitelist of allowed tokens within sh(wsh()); only few simple wallet types are supported + switch (token) { + case TOKEN_PK: + case TOKEN_PKH: + case TOKEN_MULTI: + case TOKEN_SORTEDMULTI: + break; + default: + return WITH_ERROR(-1, "Token not allowed within sh(wsh())"); + } + } -/** - * Parses elip151 expression within BLINDING_KEY context. - * - * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] token_len - * Size of key token in characters. - * - * @return 0 if successful, a negative number on error. - */ -static int parse_ct_elip151(script_parser_ctx_t *ctx, size_t token_len) { - UNUSED(token_len); + // all tokens but '0' and '1' have opening and closing parentheses + bool has_parentheses = token != TOKEN_0 && token != TOKEN_1; - if (!buffer_skip_data(ctx->in_buf, (const uint8_t*) "elip151", sizeof("elip151") - 1)) { - return -1; + if (has_parentheses) { + // Opening '(' + if (!consume_character(in_buf, '(')) { + return WITH_ERROR(-1, "Expected '('"); + } } + policy_node_t *parsed_node; - policy_node_t *node = (policy_node_t *) - buffer_alloc(ctx->out_buf, sizeof(policy_node_t), true); + switch (token) { + case TOKEN_0: + case TOKEN_1: { + policy_node_constant_t *node = + (policy_node_constant_t *) buffer_alloc(out_buf, + sizeof(policy_node_constant_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } - if (node) { - node->type = TOKEN_ELIP151; - node->node_data = NULL; - return 0; - } - return -1; -} + parsed_node = (policy_node_t *) node; + + node->base.type = token; + if (token == TOKEN_0) { + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 1; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + } else { + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 1; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 0; + node->base.flags.miniscript_mod_u = 1; + } -/// Blinding key signature -typedef struct { - size_t min_len; ///< Minimum allowed length - size_t max_len; ///< Maximum allowed length - uint32_t charset; ///< Allowed charset - blinding_key_parser_t parser; ///< Pinter to a function parsing a BLINDING_KEY expression. - char prefix[TOKEN_PREFIX_LEN + 1]; ///< Token prefix -} blinding_key_signature_t; - -/// Table of known blinding key signatures -static const blinding_key_signature_t BLINDING_KEY_SIGNATURES[] = { - { - .prefix = "slip77", - .min_len = 72, - .max_len = 72, - .charset = CHARSET_ALPHANUM_LOW|CHARSET_BRACKETS, - .parser = parse_ct_slip77 - }, - { - .prefix = "xpub", - .min_len = 111, - .max_len = 112, - .charset = CHARSET_ALPHANUM, - .parser = parse_ct_xpub - }, - { - .prefix = "xprv", - .min_len = 111, - .max_len = 112, - .charset = CHARSET_ALPHANUM, - .parser = parse_ct_xprv - }, - { - .prefix = "elip151", - .min_len = 7, - .max_len = 7, - .charset = CHARSET_ALPHANUM_LOW, - .parser = parse_ct_elip151 - }, - { - .prefix = "", - .min_len = 64, - .max_len = 64, - .charset = CHARSET_HEX_LOW, - .parser = parse_ct_hex_privkey - }, - { - .prefix = "", - .min_len = 66, - .max_len = 66, - .charset = CHARSET_HEX_LOW, - .parser = parse_ct_hex_pubkey - } -}; -/// Number of records in the table of known blinding key signatures -static const size_t N_BLINDING_KEY_SIGNATURES = - sizeof(BLINDING_KEY_SIGNATURES) / sizeof(BLINDING_KEY_SIGNATURES[0]); - -/** - * Looks through the table of blinding key signatures and returns corresponding - * parsing function. - * - * @param[in] scan_result - * Results of token scan used to find blinding key type by its signature. - * - * @return pointer to function parsing identified type of blinding key or NULL if not found. - */ -blinding_key_parser_t find_blinding_key_parser(const token_scan_result_t *scan_result) { - for (size_t i = 0; i < N_BLINDING_KEY_SIGNATURES; ++i) { - uint32_t expected_charset = (uint32_t) PIC(BLINDING_KEY_SIGNATURES[i].charset); - const char *expected_prefix = (const char *) PIC(BLINDING_KEY_SIGNATURES[i].prefix); - if (0 == (scan_result->charset & ~expected_charset) && - scan_result->token_len >= (size_t) PIC(BLINDING_KEY_SIGNATURES[i].min_len) && - scan_result->token_len <= (size_t) PIC(BLINDING_KEY_SIGNATURES[i].max_len) && - 0 == strncmp(expected_prefix, - scan_result->prefix, - strnlen(expected_prefix, TOKEN_PREFIX_LEN))) { - return (blinding_key_parser_t) PIC(BLINDING_KEY_SIGNATURES[i].parser); + break; } - } - - return NULL; -} - -/** - * Internal function parsing blinding key script inside ct() descriptor. - * - * Parses a BLINDING_KEY expression as specified in ELIP: 150 from the in_buf - * buffer, allocating the node and variables in out_buf.The initial pointer in - * out_buf will contain the node of the BLINDING_KEY. - * - * @param[in,out] ctx - * Script parser context. - * - * @return 0 if successful, a negative number on error. - */ -static int parse_blinding_key_script(script_parser_ctx_t *ctx) { - token_scan_result_t scan_result; - if (!scan_token(ctx->in_buf, ',', &scan_result)) { - return -1; - } - - blinding_key_parser_t key_parser = find_blinding_key_parser(&scan_result); - if (key_parser) { - return (*key_parser)(ctx, scan_result.token_len); - } - return -1; -} - -#endif // HAVE_LIQUID - -/** - * Internal function recursively parsing a script expression from the input buffer. - * - * Parses a SCRIPT expression from the in_buf buffer, allocating the nodes and variables in out_buf. - * The initial pointer in out_buf will contain the root node of the SCRIPT. - * - * @param[in,out] ctx - * Script parser context. - * @param[in] depth - * Current depth of nested structure. - * - * @return 0 if successful, a negative number on error. - */ -static int parse_script(script_parser_ctx_t *ctx, size_t depth, unsigned int context_flags) { - // We read the token, we'll do different parsing based on what token we find - int token = parse_token(ctx->in_buf); - char c; - unsigned int inner_context_flags = context_flags; - - // Opening '(' - if (!buffer_read_u8(ctx->in_buf, (uint8_t *) &c) && c != '(') { - return -1; - } - - switch (token) { case TOKEN_SH: case TOKEN_WSH: { if (token == TOKEN_SH) { - if (depth != 0 && (context_flags & CONTEXT_WITHIN_CT) == 0) { - return -2; // can only be top-level or inside ct + if (depth != 0) { + return WITH_ERROR(-1, "sh can only be a top-level function"); } - } else if (token == TOKEN_WSH) { - if (depth != 0 && - (context_flags & (CONTEXT_WITHIN_SH|CONTEXT_WITHIN_CT)) == 0) { - return -3; // only top-level, inside sh or ct + if (depth != 0 && ((context_flags & CONTEXT_WITHIN_SH) == 0)) { + return WITH_ERROR(-1, "wsh can only be top-level or inside sh"); } } policy_node_with_script_t *node = - (policy_node_with_script_t *) buffer_alloc(ctx->out_buf, + (policy_node_with_script_t *) buffer_alloc(out_buf, sizeof(policy_node_with_script_t), true); if (node == NULL) { - return -4; + return WITH_ERROR(-1, "Out of memory"); } - node->type = token; + parsed_node = (policy_node_t *) node; - if (token == TOKEN_SH) { - inner_context_flags |= CONTEXT_WITHIN_SH; - } + node->base.type = token; + + node->base.flags.is_miniscript = 0; + + unsigned int inner_context_flags = context_flags; + inner_context_flags |= (token == TOKEN_SH) ? CONTEXT_WITHIN_SH : CONTEXT_WITHIN_WSH; + + // the internal script is recursively parsed (if successful) in the current location + // of the output buffer + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + i_policy_node(&node->script, buffer_get_cur(out_buf)); - // the internal script is recursively parsed (if successful) in the current location of - // the output buffer - int res2 = 0; - node->script = (policy_node_t *) buffer_get_cur_aligned(ctx->out_buf); - if (NULL == node->script || (res2 = parse_script(ctx, depth + 1, inner_context_flags)) < 0) { + if (0 > parse_script(in_buf, out_buf, version, depth + 1, inner_context_flags)) { // failed while parsing internal script - return res2 * 100 - 5; + return -1; } break; } - case TOKEN_PKH: - case TOKEN_WPKH: - case TOKEN_TR: // not currently supporting x-only keys - { - if (token == TOKEN_WPKH) { - if (depth != 0 && - (context_flags & (CONTEXT_WITHIN_SH|CONTEXT_WITHIN_CT)) == 0) { - return -6; // only top-level, inside sh or ct - } - } - policy_node_with_key_t *node = - (policy_node_with_key_t *) buffer_alloc(ctx->out_buf, - sizeof(policy_node_with_key_t), - true); + case TOKEN_SHA256: + case TOKEN_HASH256: { + policy_node_with_hash_256_t *node = + (policy_node_with_hash_256_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_hash_256_t), + true); if (node == NULL) { - return -7; + return WITH_ERROR(-1, "Out of memory"); } - node->type = token; + parsed_node = (policy_node_t *) node; - int key_index = parse_key_index(ctx->in_buf); - if (key_index == -1) { - return -8; + if (0 > buffer_read_hex_hash(in_buf, node->h, 32)) { + return WITH_ERROR(-1, "Failed to parse 32-byte hash image"); } - node->key_index = (size_t) key_index; + node->base.type = token; + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 1; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; break; } - case TOKEN_MULTI: - case TOKEN_SORTEDMULTI: { - policy_node_multisig_t *node = - (policy_node_multisig_t *) buffer_alloc(ctx->out_buf, - sizeof(policy_node_multisig_t), - true); + case TOKEN_RIPEMD160: + case TOKEN_HASH160: { + policy_node_with_hash_160_t *node = + (policy_node_with_hash_160_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_hash_160_t), + true); if (node == NULL) { - return -9; + return WITH_ERROR(-1, "Out of memory"); } - node->type = token; + parsed_node = (policy_node_t *) node; - if (parse_unsigned_decimal(ctx->in_buf, &node->k) == -1) { - PRINTF("Error parsing threshold\n"); - return -10; + if (0 > buffer_read_hex_hash(in_buf, node->h, 20)) { + return WITH_ERROR(-1, "Failed to parse 20-byte hash image"); } - // We allocate the array of key indices at the current position in the output buffer (on - // success) - node->key_indexes = (size_t *) buffer_get_cur_aligned(ctx->out_buf); - if (NULL == node->key_indexes) { - return -11; + node->base.type = token; + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 1; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + break; + } + + case TOKEN_ANDOR: { + policy_node_with_script3_t *node = + (policy_node_with_script3_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script3_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); } + parsed_node = (policy_node_t *) node; - node->n = 0; - while (true) { - // If the next character is a ')', we exit and leave it in the buffer - if (buffer_peek(ctx->in_buf, (uint8_t *) &c) && c == ')') { - break; - } + node->base.type = token; - // otherwise, there must be a comma - if (!buffer_read_u8(ctx->in_buf, (uint8_t *) &c) || c != ',') { - PRINTF("Unexpected char: %c. Was expecting: ,\n", c); - return -12; - } + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 3, + version, + context_flags)) { + return -1; + } - int key_index = parse_key_index(ctx->in_buf); - if (key_index == -1) { - return -13; + for (int i = 0; i < 3; i++) { + if (!r_policy_node(&node->scripts[i])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of andor must be miniscript"); } + } - size_t *key_index_out = (size_t *) buffer_alloc(ctx->out_buf, sizeof(size_t), true); - if (key_index_out == NULL) { - return -14; - } - *key_index_out = (size_t) key_index; + // andor(X, Y, Z) + // X is Bdu; Y and Z are both B, K, or V - ++node->n; + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Y = r_policy_node(&node->scripts[1]); + const policy_node_t *Z = r_policy_node(&node->scripts[2]); + + if (X->flags.miniscript_type != MINISCRIPT_TYPE_B || !X->flags.miniscript_mod_d || + !X->flags.miniscript_mod_u) { + return WITH_ERROR(-1, "invalid type"); } - // check integrity of k and n - if (!(1 <= node->k && node->k <= node->n && node->n <= MAX_POLICY_MAP_COSIGNERS)) { - return -15; + if (Y->flags.miniscript_type != Z->flags.miniscript_type) { + return WITH_ERROR(-1, "invalid type"); + } + + if (Y->flags.miniscript_type == MINISCRIPT_TYPE_W) { // must be one of the other three + return WITH_ERROR(-1, "invalid type"); } + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = Y->flags.miniscript_type; + node->base.flags.miniscript_mod_z = + X->flags.miniscript_mod_z & Y->flags.miniscript_mod_z & Z->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_o = + (X->flags.miniscript_mod_z & Y->flags.miniscript_mod_o & Z->flags.miniscript_mod_o) + | + (X->flags.miniscript_mod_o & Y->flags.miniscript_mod_z & Z->flags.miniscript_mod_z); + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = Z->flags.miniscript_mod_d; + node->base.flags.miniscript_mod_u = Y->flags.miniscript_mod_u & Z->flags.miniscript_mod_u; + // clang-format on + break; } -#ifdef HAVE_LIQUID - case TOKEN_CT: { - if (depth != 0) { - return -16; // can only be top-level + case TOKEN_AND_V: { + policy_node_with_script2_t *node = + (policy_node_with_script2_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script2_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); } + parsed_node = (policy_node_t *) node; - policy_node_ct_t *node = - (policy_node_ct_t *) buffer_alloc(ctx->out_buf, sizeof(policy_node_ct_t), true); + node->base.type = token; + + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 2, + version, + context_flags)) { + return -1; + } + + if (!r_policy_node(&node->scripts[0])->flags.is_miniscript || + !r_policy_node(&node->scripts[1])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of and_v must be miniscript"); + } + + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Y = r_policy_node(&node->scripts[1]); + + // and_v(X,Y) + // X is V; Y is B, K, or V + + if (X->flags.miniscript_type != MINISCRIPT_TYPE_V) { + return WITH_ERROR(-1, "invalid type"); + } + + if (Y->flags.miniscript_type == MINISCRIPT_TYPE_W) { // must be one of the other three + return WITH_ERROR(-1, "invalid type"); + } + + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = Y->flags.miniscript_type; + node->base.flags.miniscript_mod_z = X->flags.miniscript_mod_z & Y->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_o = + (X->flags.miniscript_mod_z & Y->flags.miniscript_mod_o) + | + (X->flags.miniscript_mod_o & Y->flags.miniscript_mod_z); + node->base.flags.miniscript_mod_n = + X->flags.miniscript_mod_n + | + (X->flags.miniscript_mod_z & Y->flags.miniscript_mod_n); + node->base.flags.miniscript_mod_d = 0; + node->base.flags.miniscript_mod_u = Y->flags.miniscript_mod_u; + // clang-format on + + break; + } + case TOKEN_AND_B: { + policy_node_with_script2_t *node = + (policy_node_with_script2_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script2_t), + true); if (node == NULL) { - return -17; + return WITH_ERROR(-1, "Out of memory"); } - node->type = token; + parsed_node = (policy_node_t *) node; - inner_context_flags |= CONTEXT_WITHIN_CT; + node->base.type = token; - // the master blinding key script is recursively parsed (if successful) in the current - // location of the output buffer - node->mbk_script = (policy_node_t *) buffer_get_cur_aligned(ctx->out_buf); - if (NULL == node->mbk_script || 0 > parse_blinding_key_script(ctx)) { - // failed while parsing internal script - return -18; + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 2, + version, + context_flags)) { + return -1; } - // scripts must be separated by comma - if (!buffer_read_u8(ctx->in_buf, (uint8_t *) &c) || c != ',') { - PRINTF("Unexpected char: %c. Was expecting: ,\n", c); - return -19; + if (!r_policy_node(&node->scripts[0])->flags.is_miniscript || + !r_policy_node(&node->scripts[1])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of and_b must be miniscript"); } - // the internal script is recursively parsed (if successful) in the current location of - // the output buffer - int res2 = 0; - node->script = (policy_node_t *) buffer_get_cur_aligned(ctx->out_buf); - if (NULL == node->script || (res2 = parse_script(ctx, depth + 1, inner_context_flags)) < 0) { - // failed while parsing internal script - return res2 * 100 - 20; + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Y = r_policy_node(&node->scripts[1]); + + // and_b(X,Y) + // X is B; Y is W + + if (X->flags.miniscript_type != MINISCRIPT_TYPE_B || + Y->flags.miniscript_type != MINISCRIPT_TYPE_W) { + return WITH_ERROR(-1, "invalid type"); } + + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = X->flags.miniscript_mod_z & Y->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_o = + (X->flags.miniscript_mod_z & Y->flags.miniscript_mod_o) + | + (X->flags.miniscript_mod_o & Y->flags.miniscript_mod_z); + node->base.flags.miniscript_mod_n = + X->flags.miniscript_mod_n + | + (X->flags.miniscript_mod_z & Y->flags.miniscript_mod_n); + node->base.flags.miniscript_mod_d = X->flags.miniscript_mod_d & Y->flags.miniscript_mod_d; + node->base.flags.miniscript_mod_u = 1; + // clang-format on + break; } -#endif // HAVE_LIQUID - default: - PRINTF("Unknown token\n"); - return -21; - } + case TOKEN_AND_N: { + policy_node_with_script2_t *node = + (policy_node_with_script2_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script2_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + parsed_node = (policy_node_t *) node; - if (!buffer_read_u8(ctx->in_buf, (uint8_t *) &c) && c != ')') { - return -22; - } + node->base.type = token; - if (depth == 0 && buffer_can_read(ctx->in_buf, 1)) { - PRINTF("Input buffer too long\n"); - return -23; - } + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 2, + version, + context_flags)) { + return -1; + } - return 0; -} + if (!r_policy_node(&node->scripts[0])->flags.is_miniscript || + !r_policy_node(&node->scripts[1])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of and_n must be miniscript"); + } -int parse_policy_map(buffer_t *in_buf, - void *out, - size_t out_len, - uint32_t bip32_pubkey_version, - uint32_t bip32_privkey_version) { - if ((uintptr_t) out % sizeof(void*) != 0) { - PRINTF("Unaligned pointer\n"); - return -1; - } + // and_n(X, Y) is equivalent to andor(X, Y, 0) + // X is Bdu; Y is B - buffer_t out_buf = buffer_create(out, out_len); + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Y = r_policy_node(&node->scripts[1]); - script_parser_ctx_t parser_ctx = { - .in_buf = in_buf, - .out_buf = &out_buf, - .bip32_pubkey_version = bip32_pubkey_version, - .bip32_privkey_version = bip32_privkey_version - }; + if (X->flags.miniscript_type != MINISCRIPT_TYPE_B || !X->flags.miniscript_mod_d || + !X->flags.miniscript_mod_u) { + return WITH_ERROR(-1, "invalid type"); + } - return parse_script(&parser_ctx, 0, 0); -} + if (Y->flags.miniscript_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "invalid type"); + } -bool policy_is_multisig(const policy_node_t *policy) { - const policy_node_t *node = policy; + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = + X->flags.miniscript_mod_z & Y->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_o = X->flags.miniscript_mod_o & Y->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = Y->flags.miniscript_mod_u; + // clang-format on - while(node != NULL) { - switch(node->type) - { - case TOKEN_CT: - node = ((policy_node_ct_t *)node)->script; break; + } + case TOKEN_OR_B: { + policy_node_with_script2_t *node = + (policy_node_with_script2_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script2_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + parsed_node = (policy_node_t *) node; + + node->base.type = token; + + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 2, + version, + context_flags)) { + return -1; + } + + if (!r_policy_node(&node->scripts[0])->flags.is_miniscript || + !r_policy_node(&node->scripts[1])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of or_b must be miniscript"); + } + + // or_b(X, Z) + // X is Bd; Z is Wd + + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Z = r_policy_node(&node->scripts[1]); + + if (X->flags.miniscript_type != MINISCRIPT_TYPE_B || !X->flags.miniscript_mod_d) { + return WITH_ERROR(-1, "invalid type"); + } + + if (Z->flags.miniscript_type != MINISCRIPT_TYPE_W || !Z->flags.miniscript_mod_d) { + return WITH_ERROR(-1, "invalid type"); + } + + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = X->flags.miniscript_mod_z & Z->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_o = + (X->flags.miniscript_mod_z & Z->flags.miniscript_mod_o) + | + (X->flags.miniscript_mod_o & Z->flags.miniscript_mod_z); + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + // clang-format on - case TOKEN_SH: - case TOKEN_WSH: - node = ((policy_node_with_script_t *)node)->script; break; + } + case TOKEN_OR_C: { + policy_node_with_script2_t *node = + (policy_node_with_script2_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script2_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + parsed_node = (policy_node_t *) node; - case TOKEN_MULTI: - case TOKEN_SORTEDMULTI: - return true; + node->base.type = token; - // TOKEN_PKH, TOKEN_WPKH, TOKEN_TR - // TODO: add Taproot multisig when it will be supported project-wise - default: - return false; + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 2, + version, + context_flags)) { + return -1; + } + + if (!r_policy_node(&node->scripts[0])->flags.is_miniscript || + !r_policy_node(&node->scripts[1])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of or_c must be miniscript"); + } + + // or_c(X, Z) + // X is Bdu; Z is V + + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Z = r_policy_node(&node->scripts[1]); + + if (X->flags.miniscript_type != MINISCRIPT_TYPE_B || !X->flags.miniscript_mod_d || + !X->flags.miniscript_mod_u) { + return WITH_ERROR(-1, "invalid type"); + } + + if (Z->flags.miniscript_type != MINISCRIPT_TYPE_V) { + return WITH_ERROR(-1, "invalid type"); + } + + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_V; + node->base.flags.miniscript_mod_z = X->flags.miniscript_mod_z & Z->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_o = X->flags.miniscript_mod_o & Z->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 0; + node->base.flags.miniscript_mod_u = 0; + // clang-format on + + break; } - } + case TOKEN_OR_D: { + policy_node_with_script2_t *node = + (policy_node_with_script2_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script2_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + parsed_node = (policy_node_t *) node; - return false; -} + node->base.type = token; -#ifndef SKIP_FOR_CMOCKA + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 2, + version, + context_flags)) { + return -1; + } -void get_policy_wallet_id(const policy_map_wallet_header_t *wallet_header, uint8_t out[static 32]) { - cx_sha256_t wallet_hash_context; - cx_sha256_init(&wallet_hash_context); + if (!r_policy_node(&node->scripts[0])->flags.is_miniscript || + !r_policy_node(&node->scripts[1])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of or_d must be miniscript"); + } - crypto_hash_update_u8(&wallet_hash_context.header, wallet_header->type); - crypto_hash_update_u8(&wallet_hash_context.header, wallet_header->name_len); - crypto_hash_update(&wallet_hash_context.header, wallet_header->name, wallet_header->name_len); + // or_d(X, Z) + // X is Bdu; Z is B - crypto_hash_update_varint(&wallet_hash_context.header, wallet_header->policy_map_len); - crypto_hash_update(&wallet_hash_context.header, - wallet_header->policy_map, - wallet_header->policy_map_len); + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Z = r_policy_node(&node->scripts[1]); - crypto_hash_update_varint(&wallet_hash_context.header, wallet_header->n_keys); + if (X->flags.miniscript_type != MINISCRIPT_TYPE_B || !X->flags.miniscript_mod_d || + !X->flags.miniscript_mod_u) { + return WITH_ERROR(-1, "invalid type"); + } - crypto_hash_update(&wallet_hash_context.header, wallet_header->keys_info_merkle_root, 32); + if (Z->flags.miniscript_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "invalid type"); + } - crypto_hash_digest(&wallet_hash_context.header, out, 32); -} + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = X->flags.miniscript_mod_z & Z->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_o = X->flags.miniscript_mod_o & Z->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = Z->flags.miniscript_mod_d; + node->base.flags.miniscript_mod_u = Z->flags.miniscript_mod_u; + // clang-format on -#endif + break; + } + case TOKEN_OR_I: { + policy_node_with_script2_t *node = + (policy_node_with_script2_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_script2_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + parsed_node = (policy_node_t *) node; + + node->base.type = token; + + if (0 > parse_child_scripts(in_buf, + out_buf, + depth, + node->scripts, + 2, + version, + context_flags)) { + return -1; + } + + if (!r_policy_node(&node->scripts[0])->flags.is_miniscript || + !r_policy_node(&node->scripts[1])->flags.is_miniscript) { + return WITH_ERROR(-1, "children of or_i must be miniscript"); + } + + // or_i(X, Z) + // both are B, K, or V + + const policy_node_t *X = r_policy_node(&node->scripts[0]); + const policy_node_t *Z = r_policy_node(&node->scripts[1]); + + if (X->flags.miniscript_type == MINISCRIPT_TYPE_W) { + return WITH_ERROR(-1, "invalid type"); // must be B, K or V + } + + if (X->flags.miniscript_type != Z->flags.miniscript_type) { + return WITH_ERROR(-1, "invalid type"); // children must be the same type + } + + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = X->flags.miniscript_type; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = X->flags.miniscript_mod_z & Z->flags.miniscript_mod_z; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = X->flags.miniscript_mod_d | Z->flags.miniscript_mod_d; + node->base.flags.miniscript_mod_u = X->flags.miniscript_mod_u & Z->flags.miniscript_mod_u; + // clang-format on + + break; + } + case TOKEN_THRESH: { + policy_node_thresh_t *node = + (policy_node_thresh_t *) buffer_alloc(out_buf, sizeof(policy_node_thresh_t), true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + parsed_node = (policy_node_t *) node; + node->base.type = token; + + // the internal scripts are recursively parsed (if successful) in the current location + // of the output buffer + + uint32_t k; + if (parse_unsigned_decimal(in_buf, &k) == -1 || k > INT16_MAX) { + return WITH_ERROR(-1, "Error parsing threshold"); + } + node->k = (int16_t) k; + + // the next character must be a comma + if (!consume_character(in_buf, ',')) { + return WITH_ERROR(-1, "Expected a comma"); + } + + if (node->k < 1) { + return WITH_ERROR(-1, "Threshold must be at least 1"); + } + + node->n = 0; + policy_node_scriptlist_t *scriptlist = + buffer_alloc(out_buf, sizeof(policy_node_scriptlist_t), true); + if (scriptlist == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + i_policy_node_scriptlist(&node->scriptlist, scriptlist); + + policy_node_scriptlist_t *cur = scriptlist; + + i_policy_node_scriptlist(&cur->next, NULL); + + int count_z = 0; + int count_o = 0; + while (true) { + ++node->n; + // parse a script into cur->script + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + i_policy_node(&cur->script, buffer_get_cur(out_buf)); + if (0 > parse_script(in_buf, out_buf, version, depth + 1, context_flags)) { + // failed while parsing internal script + return -1; + } + + if (!r_policy_node(&cur->script)->flags.is_miniscript) { + return WITH_ERROR(-1, "children of thresh must be miniscript"); + } + + if (node->n == 1) { + // the first child's type must be B + if (r_policy_node(&cur->script)->flags.miniscript_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "the first children of thresh must be of type B"); + } + } else { + // every other child's type must be W + if (r_policy_node(&cur->script)->flags.miniscript_type != MINISCRIPT_TYPE_W) { + return WITH_ERROR( + -1, + "each child of thresh (except the first) must be of type W"); + } + } + + // all children must have properties du + if (!r_policy_node(&cur->script)->flags.miniscript_mod_d || + !r_policy_node(&cur->script)->flags.miniscript_mod_u) { + return WITH_ERROR(-1, "each child of thresh must have properties d and u"); + } + + if (r_policy_node(&cur->script)->flags.miniscript_mod_z) { + ++count_z; + } + if (r_policy_node(&cur->script)->flags.miniscript_mod_o) { + ++count_o; + } + + // peek, if next character is ',', consume it and exit + if (consume_character(in_buf, ',')) { + policy_node_scriptlist_t *next = + (policy_node_scriptlist_t *) buffer_alloc(out_buf, + sizeof(policy_node_scriptlist_t), + true); + if (next == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + + i_policy_node_scriptlist(&cur->next, next); + + cur = next; + i_policy_node_scriptlist(&cur->next, NULL); + } else { + // no more scripts to parse + break; + } + } + + // thresh(k, X1, ..., Xn) + // X1 is Bdu; others are Wdu + + // clang-format off + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = (count_z == node->n) ? 1 : 0; + node->base.flags.miniscript_mod_o = (count_z == node->n - 1 && count_o == 1) ? 1 : 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + // clang-format on + + break; + } + case TOKEN_PK: + case TOKEN_PKH: + case TOKEN_PK_K: + case TOKEN_PK_H: + case TOKEN_WPKH: { + policy_node_with_key_t *node = + (policy_node_with_key_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_key_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + + policy_node_key_placeholder_t *key_placeholder = + buffer_alloc(out_buf, sizeof(policy_node_key_placeholder_t), true); + + if (key_placeholder == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + i_policy_node_key_placeholder(&node->key_placeholder, key_placeholder); + + if (token == TOKEN_WPKH) { + if (depth > 0 && ((context_flags & CONTEXT_WITHIN_SH) == 0)) { + return WITH_ERROR(-1, "wpkh can only be top-level or inside sh"); + } + } + + parsed_node = (policy_node_t *) node; + + node->base.type = token; + + if (0 > parse_placeholder(in_buf, version, key_placeholder)) { + return WITH_ERROR(-1, "Couldn't parse key placeholder"); + } + + if (token == TOKEN_WPKH) { + // not valid in miniscript + node->base.flags.is_miniscript = 0; + } else { + switch (token) { + case TOKEN_PK: // pk(key) == c:pk_k(key) + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 1; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + break; + case TOKEN_PKH: // pkh(key) == c:pk_h(key) + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + break; + case TOKEN_PK_K: + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_K; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 1; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + break; + case TOKEN_PK_H: + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_K; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + break; + default: + return WITH_ERROR(-1, "unreachable code reached"); + } + } + + break; + } + case TOKEN_TR: { // supporting only xpubs + if (depth > 1) { + return WITH_ERROR(-1, "tr can only be top-level"); + } + + policy_node_tr_t *node = + (policy_node_tr_t *) buffer_alloc(out_buf, sizeof(policy_node_tr_t), true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + + policy_node_key_placeholder_t *key_placeholder = + buffer_alloc(out_buf, sizeof(policy_node_key_placeholder_t), true); + if (key_placeholder == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + i_policy_node_key_placeholder(&node->key_placeholder, key_placeholder); + + if (0 > parse_placeholder(in_buf, version, key_placeholder)) { + return WITH_ERROR(-1, "Couldn't parse key placeholder"); + } + + uint8_t c; + if (!buffer_peek(in_buf, &c)) { + return WITH_ERROR(-1, "buffer exhausted too early while parsing tr"); + } + if (c == ',') { + // Parse a TREE node + buffer_seek_cur(in_buf, 1); // skip ',' + + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + policy_node_tree_t *tree = (policy_node_tree_t *) buffer_get_cur(out_buf); + if (0 > parse_tree(in_buf, out_buf, version, depth + 1)) { + return WITH_ERROR(-1, "Failed to parse TREE expression"); + } + i_policy_node_tree(&node->tree, tree); + } else { + // no TREE, only tr(KP) + if (c != ')') { + return WITH_ERROR(-1, "Failed to parse tr"); + } + i_policy_node_tree(&node->tree, NULL); + } + + parsed_node = (policy_node_t *) node; + + node->base.type = token; + + node->base.flags.is_miniscript = 0; + + break; + } + case TOKEN_OLDER: + case TOKEN_AFTER: { + policy_node_with_uint32_t *node = + (policy_node_with_uint32_t *) buffer_alloc(out_buf, + sizeof(policy_node_with_uint32_t), + true); + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + parsed_node = (policy_node_t *) node; + node->base.type = token; + + if (parse_unsigned_decimal(in_buf, &node->n) == -1) { + return WITH_ERROR(-1, "Error parsing number"); + } + + if (node->n < 1 || node->n >= (1u << 31)) { + return WITH_ERROR(-1, "n must satisfy 1 <= n < 2^31 in older/after"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 1; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 0; + node->base.flags.miniscript_mod_u = 0; + + break; + } + case TOKEN_MULTI: + case TOKEN_MULTI_A: + case TOKEN_SORTEDMULTI: + case TOKEN_SORTEDMULTI_A: { + policy_node_multisig_t *node = + (policy_node_multisig_t *) buffer_alloc(out_buf, + sizeof(policy_node_multisig_t), + true); + + if (node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + + if ((context_flags & CONTEXT_WITHIN_TR) != 0) { + if (token != TOKEN_MULTI_A && token != TOKEN_SORTEDMULTI_A) { + return WITH_ERROR( + -1, + "multi and sortedmulti can only be used in legacy or segwit scripts"); + } + } else { // legacy or segwit scripts + if (token != TOKEN_MULTI && token != TOKEN_SORTEDMULTI) { + return WITH_ERROR( + -1, + "multi_a and sortedmulti_a can only be used in taproot scripts"); + } + } + + if (token == TOKEN_SORTEDMULTI) { + size_t n_sh_wrappers = 0; + if (context_flags & CONTEXT_WITHIN_SH) ++n_sh_wrappers; + if (context_flags & CONTEXT_WITHIN_WSH) ++n_sh_wrappers; + + // sortedmulti can only be used bare, or directly under sh(), wsh() + if (depth != n_sh_wrappers) { + return WITH_ERROR(-1, + "sortedmulti can only be bare, or directly under sh or wsh"); + } + } + + parsed_node = (policy_node_t *) node; + node->base.type = token; + + uint32_t k; + if (parse_unsigned_decimal(in_buf, &k) == -1 || k > INT16_MAX) { + return WITH_ERROR(-1, "Error parsing threshold"); + } + node->k = (int16_t) k; + + // We allocate the array of key indices at the current position in the output buffer + // (on success) + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + i_policy_node_key_placeholder(&node->key_placeholders, buffer_get_cur(out_buf)); + + node->n = 0; + while (true) { + uint8_t c; + // If the next character is a ')', we exit and leave it in the buffer + if (buffer_peek(in_buf, &c) && c == ')') { + break; + } + + // otherwise, there must be a comma + if (!consume_character(in_buf, ',')) { + return WITH_ERROR(-1, "Expected ','"); + } + + policy_node_key_placeholder_t *key_placeholder = + (policy_node_key_placeholder_t *) buffer_alloc( + out_buf, + sizeof(policy_node_key_placeholder_t), + true); // we align this pointer, as there's padding in an array of + // structures + if (key_placeholder == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + + if (0 > parse_placeholder(in_buf, version, key_placeholder)) { + return WITH_ERROR(-1, "Error parsing key placeholder"); + } + + ++node->n; + } + + // check integrity of k and n + if (!(1 <= node->k && node->k <= node->n && node->n <= MAX_PUBKEYS_PER_MULTISIG)) { + return WITH_ERROR(-1, "Invalid k and/or n"); + } + + if (token == TOKEN_SORTEDMULTI || token == TOKEN_SORTEDMULTI_A) { + node->base.flags.is_miniscript = 0; + } else if (token == TOKEN_MULTI) { + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + } else if (token == TOKEN_MULTI_A) { + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = 1; + } + + break; + } + default: + PRINTF("Unknown token: %d\n", token); + return -1; + } + + if (has_parentheses) { + if (!consume_character(in_buf, ')')) { + return WITH_ERROR(-1, "Expected ')'"); + } + } + + if (depth == 0 && buffer_can_read(in_buf, 1)) { + return WITH_ERROR(-1, "Input buffer too long"); + } + + // if there was one or more wrappers, the script of the most internal node must point + // to the parsed node + if (inner_wrapper != NULL) { + i_policy_node(&inner_wrapper->script, parsed_node); + } + + // Validate and compute the flags (miniscript type and modifiers) for all the wrapper, if any + // We start from the most internal wrapper. + // Remark: This loop has quadratic complexity as we process a linked list in reverse order, but + // it does not matter as it is always a short list. + + for (int i = n_wrappers - 1; i >= 0; i--) { + // find the actual node by traversing the list + policy_node_with_script_t *node = (policy_node_with_script_t *) outermost_node; + for (int j = 0; j < i; j++) { + node = (policy_node_with_script_t *) r_policy_node(&node->script); + } + + if (!r_policy_node(&node->script)->flags.is_miniscript) { + return WITH_ERROR(-1, "wrappers can only be applied to miniscript"); + } + + const policy_node_t *X = r_policy_node(&node->script); + + uint8_t X_type = X->flags.miniscript_type; + + uint8_t X_z = X->flags.miniscript_mod_z; + uint8_t X_o = X->flags.miniscript_mod_o; + uint8_t X_n = X->flags.miniscript_mod_n; + uint8_t X_d = X->flags.miniscript_mod_d; + uint8_t X_u = X->flags.miniscript_mod_u; + + switch (node->base.type) { + case TOKEN_A: + if (X_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "'a' wrapper requires a B type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_W; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = X_d; + node->base.flags.miniscript_mod_u = X_u; + break; + case TOKEN_S: + if (X_type != MINISCRIPT_TYPE_B || !X_o) { + return WITH_ERROR(-1, "'s' wrapper requires a Bu type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_W; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 0; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = X_d; + node->base.flags.miniscript_mod_u = X_u; + break; + case TOKEN_C: + if (X_type != MINISCRIPT_TYPE_K) { + return WITH_ERROR(-1, "'c' wrapper requires a K type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = X_o; + node->base.flags.miniscript_mod_n = X_n; + node->base.flags.miniscript_mod_d = X_d; + node->base.flags.miniscript_mod_u = 1; + break; + case TOKEN_T: + // t:X == and_v(X,1) + + if (X_type != MINISCRIPT_TYPE_V) { + return WITH_ERROR(-1, "'t' wrapper requires a V type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = X_z; + node->base.flags.miniscript_mod_o = X_o; + node->base.flags.miniscript_mod_n = X_n; + node->base.flags.miniscript_mod_d = 0; + node->base.flags.miniscript_mod_u = 1; + break; + case TOKEN_D: + if (X_type != MINISCRIPT_TYPE_V || !X_z) { + return WITH_ERROR(-1, "'d' wrapper requires a Vz type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = 1; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = (context_flags & CONTEXT_WITHIN_TR) ? 1 : 0; + break; + case TOKEN_V: + if (X_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "'v' wrapper requires a B type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_V; + node->base.flags.miniscript_mod_z = X_z; + node->base.flags.miniscript_mod_o = X_o; + node->base.flags.miniscript_mod_n = X_n; + node->base.flags.miniscript_mod_d = 0; + node->base.flags.miniscript_mod_u = 0; + break; + case TOKEN_J: + if (X_type != MINISCRIPT_TYPE_B || !X_n) { + return WITH_ERROR(-1, "'j' wrapper requires a Bn type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = X_o; + node->base.flags.miniscript_mod_n = 1; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = X_u; + break; + case TOKEN_N: + if (X_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "'n' wrapper requires a B type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = X_z; + node->base.flags.miniscript_mod_o = X_o; + node->base.flags.miniscript_mod_n = X_n; + node->base.flags.miniscript_mod_d = X_d; + node->base.flags.miniscript_mod_u = 1; + break; + case TOKEN_L: + // l:X == or_i(0,X) + + if (X_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "'l' wrapper requires a B type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = X_z; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = X_u; + break; + case TOKEN_U: + // u:X == or_i(X,0) + + if (X_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "'u' wrapper requires a B type child"); + } + + node->base.flags.is_miniscript = 1; + node->base.flags.miniscript_type = MINISCRIPT_TYPE_B; + node->base.flags.miniscript_mod_z = 0; + node->base.flags.miniscript_mod_o = X_z; + node->base.flags.miniscript_mod_n = 0; + node->base.flags.miniscript_mod_d = 1; + node->base.flags.miniscript_mod_u = X_u; + break; + default: + return WITH_ERROR(-1, "unreachable code reached"); + } + } + + return 0; +} + +// Parses a TREE expression inside tr() +// `depth` here refers to the depth inside the policy, therefore it starts at 1 for the taptree +static int parse_tree(buffer_t *in_buf, buffer_t *out_buf, int version, size_t depth) { + // out_buf must be aligned before calling this function + + if (depth > MAX_TAPTREE_POLICY_DEPTH) { + return WITH_ERROR(-1, "Taptree policy depth limit exceeded"); + } + + if (!buffer_is_cur_aligned(out_buf)) { + return WITH_ERROR(-1, "out_buf not aligned"); + } + + policy_node_tree_t *tree_node = + (policy_node_tree_t *) buffer_alloc(out_buf, sizeof(policy_node_tree_t), true); + + if (tree_node == NULL) { + return WITH_ERROR(-1, "Out of memory"); + } + + uint8_t c; + + // the first character must be a '{' + if (!buffer_peek(in_buf, &c)) { + return WITH_ERROR(-1, "buffer ended too early"); + } + + if (c != '{') { + // parse a SCRIPT + tree_node->is_leaf = true; + + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + i_policy_node(&tree_node->script, buffer_get_cur(out_buf)); + if (0 > parse_script(in_buf, out_buf, version, depth + 1, CONTEXT_WITHIN_TR)) { + return -1; + } + } else { + // parse a {TREE,TREE} + tree_node->is_leaf = false; + buffer_seek_cur(in_buf, 1); // skip '{' + + // parse first TREE expression + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + i_policy_node_tree(&tree_node->left_tree, buffer_get_cur(out_buf)); + if (0 > parse_tree(in_buf, out_buf, version, depth + 1)) { + return -1; + } + + // the next character must be a comma + if (!consume_character(in_buf, ',')) { + return WITH_ERROR(-1, "Expected a comma"); + } + + // parse the second TREE expression + buffer_alloc(out_buf, 0, true); // ensure alignment of current pointer + i_policy_node_tree(&tree_node->right_tree, buffer_get_cur(out_buf)); + if (0 > parse_tree(in_buf, out_buf, version, depth + 1)) { + return -1; + } + + // the next character must be a '}' + if (!consume_character(in_buf, '}')) { + return WITH_ERROR(-1, "Expected a '}'"); + } + } + + return 0; +} + +int parse_descriptor_template(buffer_t *in_buf, void *out, size_t out_len, int version) { + if ((unsigned long) out % 4 != 0) { + return WITH_ERROR(-1, "Unaligned pointer"); + } + + if (version != WALLET_POLICY_VERSION_V1 && version != WALLET_POLICY_VERSION_V2) { + return WITH_ERROR(-1, "Unsupported wallet policy version"); + } + + buffer_t out_buf = buffer_create(out, out_len); + + int result = parse_script(in_buf, &out_buf, version, 0, 0); + if (result < 0) { + return result; + } + + // the offset of the buffer is the size of the parsed descriptor template + return (int) out_buf.offset; +} + +int get_policy_segwit_version(const policy_node_t *policy) { + if (policy->type == TOKEN_TR) { + return 1; + } else if (policy->type == TOKEN_SH) { + const policy_node_t *inner = + r_policy_node(&((const policy_node_with_script_t *) policy)->script); + if (inner->type == TOKEN_WPKH || inner->type == TOKEN_WSH) { + return 0; // wrapped segwit + } else { + return -1; // legacy + } + } else if (policy->type == TOKEN_WPKH || policy->type == TOKEN_WSH) { + return 0; // native segwit + } else { + return -1; // legacy + } +} + +/** + * Convenience function that returns a + b, except: + * - returns -1 if any of a and b is negative + * - returns INT16_MAX if the sum of a and b is bigger than INT16_MAX = 32767. + * Used to combine satisfaction/dissatisfaction for miniscript. + */ +static int16_t sumcheck(int16_t a, int16_t b) { + if (a < 0 || b < 0) + return -1; + else if ((uint32_t) a + (uint32_t) b > INT16_MAX) + return INT16_MAX; + else + return a + b; +} + +/** + * Convenience function that whichever of a and b is not negative; if both are positive, returns the + * largest. If both are negative, returns either of the two. Used to combine + * satisfaction/dissatisfaction for miniscript. + */ +static int16_t maxcheck(int16_t a, int16_t b) { + if (a < 0) + return b; + else + return a > b ? a : b; +} + +// Maximum supported value for n in a thresh miniscript operator (technical limitation) +#define MAX_N_IN_THRESH 128 + +// Separated from the main function as it is stack-intensive, therefore we allocate large buffers +// into the CXRAM section. There is some repeated work () +static int compute_thresh_ops(const policy_node_thresh_t *node, + miniscript_ops_t *out, + MiniscriptContext ctx) { +#ifdef USE_CXRAM_SECTION + // allocate buffers inside the cxram section; safe as there are no syscalls here + uint16_t *sats = (uint16_t *) get_cxram_buffer(); + uint16_t *next_sats = + (uint16_t *) (get_cxram_buffer() + sizeof(uint16_t) * (MAX_N_IN_THRESH + 1 + 1)); +#else + uint16_t sats[MAX_N_IN_THRESH + 1 + 1] = {0}; + uint16_t next_sats[MAX_N_IN_THRESH + 1 + 1] = {0}; // it temporarily uses an extra element +#endif + + if (node->n > MAX_N_IN_THRESH) return -1; + + policy_node_scriptlist_t *cur = r_policy_node_scriptlist(&node->scriptlist); + + out->count = 0; + + sats[0] = 0; + int sats_size = 1; + + while (cur != NULL) { + policy_node_ext_info_t t; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&cur->script), &t, ctx)) return -1; + + out->count += t.ops.count + 1; + + next_sats[0] = sumcheck(sats[0], t.ops.dsat); + for (int j = 1; j < sats_size; j++) { + next_sats[j] = + maxcheck(sumcheck(sats[j], t.ops.dsat), sumcheck(sats[j - 1], t.ops.sat)); + } + next_sats[sats_size] = sumcheck(sats[sats_size - 1], t.ops.sat); + + ++sats_size; + memmove(sats, next_sats, sats_size * sizeof(sats[0])); + + cur = r_policy_node_scriptlist(&cur->next); + } + + out->sat = sats[node->k]; + out->dsat = sats[0]; + return 0; +} + +// Separated from the main function as it is stack-intensive, therefore we allocate large buffers +// into the CXRAM section. There is some repeated work () +static int compute_thresh_stacksize(const policy_node_thresh_t *node, + miniscript_stacksize_t *out, + MiniscriptContext ctx) { +#ifdef USE_CXRAM_SECTION + // allocate buffers inside the cxram section; safe as there are no syscalls here + uint16_t *sats = (uint16_t *) get_cxram_buffer(); + uint16_t *next_sats = + (uint16_t *) (get_cxram_buffer() + sizeof(uint16_t) * (MAX_N_IN_THRESH + 1 + 1)); +#else + uint16_t sats[MAX_N_IN_THRESH + 1 + 1] = {0}; + uint16_t next_sats[MAX_N_IN_THRESH + 1 + 1] = {0}; // it temporarily uses an extra element +#endif + + if (node->n > MAX_N_IN_THRESH) return -1; + + policy_node_scriptlist_t *cur = r_policy_node_scriptlist(&node->scriptlist); + + sats[0] = 0; + int sats_size = 1; + + while (cur != NULL) { + policy_node_ext_info_t t; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&cur->script), &t, ctx)) return -1; + + next_sats[0] = sumcheck(sats[0], t.ss.dsat); + for (int j = 1; j < sats_size; j++) { + next_sats[j] = maxcheck(sumcheck(sats[j], t.ss.dsat), sumcheck(sats[j - 1], t.ss.sat)); + } + next_sats[sats_size] = sumcheck(sats[sats_size - 1], t.ss.sat); + + ++sats_size; + memmove(sats, next_sats, sats_size * sizeof(sats[0])); + + cur = r_policy_node_scriptlist(&cur->next); + } + + out->sat = sats[node->k]; + out->dsat = sats[0]; + return 0; +} + +int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, + policy_node_ext_info_t *out, + MiniscriptContext ctx) { + if (!policy_node->flags.is_miniscript) { + return WITH_ERROR(-1, "Not miniscript"); + } + + if (ctx != MINISCRIPT_CONTEXT_P2WSH && ctx != MINISCRIPT_CONTEXT_TAPSCRIPT) { + return WITH_ERROR(-1, "Unknown miniscript context"); + } + + memset(out, 0, sizeof(policy_node_ext_info_t)); + + // set flags that are 1 in most cases (they will be zeroed when appropriate) + out->m = 1; + out->k = 1; + out->x = 1; + + switch (policy_node->type) { + case TOKEN_0: + out->s = 1; + out->e = 1; + + out->script_size = 1; + + out->ops = (miniscript_ops_t){0, -1, 0}; + out->ss = (miniscript_stacksize_t){-1, 0}; + + return 0; + case TOKEN_1: + out->f = 1; + + out->script_size = 1; + + out->ops = (miniscript_ops_t){0, 0, -1}; + out->ss = (miniscript_stacksize_t){0, -1}; + + return 0; + case TOKEN_PK_K: + out->s = 1; + out->e = 1; + + out->script_size = (ctx == MINISCRIPT_CONTEXT_TAPSCRIPT ? 33 : 34); + + out->ops = (miniscript_ops_t){0, 0, 0}; + out->ss = (miniscript_stacksize_t){1, 1}; + + return 0; + case TOKEN_PK_H: + out->s = 1; + out->e = 1; + + out->script_size = 3 + 21; + + out->ops = (miniscript_ops_t){3, 0, 0}; + out->ss = (miniscript_stacksize_t){2, 2}; + + return 0; + case TOKEN_PK: // pk(key) = c:pk_k(key) + out->s = 1; + out->e = 1; + + out->x = 0; + + out->script_size = (ctx == MINISCRIPT_CONTEXT_TAPSCRIPT ? 34 : 35); + + out->ops = (miniscript_ops_t){1, 0, 0}; + out->ss = (miniscript_stacksize_t){1, 1}; + + return 0; + case TOKEN_PKH: // pkh(key) = c:pk_h(key) + out->s = 1; + out->e = 1; + + out->x = 0; + + out->script_size = 3 + 21 + 1; + + out->ops = (miniscript_ops_t){4, 0, 0}; + out->ss = (miniscript_stacksize_t){2, 2}; + + return 0; + case TOKEN_MULTI: { + const policy_node_multisig_t *node = (const policy_node_multisig_t *) policy_node; + + out->s = 1; + out->e = 1; + + out->x = 0; + + out->script_size = (uint16_t) (1 + get_push_script_size(node->k) + + get_push_script_size(node->n) + 34 * node->n); + + out->ops = (miniscript_ops_t){1, node->n, node->n}; + out->ss = (miniscript_stacksize_t){node->k + 1, node->k + 1}; + + return 0; + } + case TOKEN_MULTI_A: { + const policy_node_multisig_t *node = (const policy_node_multisig_t *) policy_node; + + out->s = 1; + out->e = 1; + + out->x = 0; + + out->script_size = (uint16_t) (1 + get_push_script_size(node->k) + 34 * node->n); + + out->ops = (miniscript_ops_t){node->n + 1, 0, 0}; + out->ss = (miniscript_stacksize_t){node->n, node->n}; + + return 0; + } + case TOKEN_OLDER: { + const policy_node_with_uint32_t *node = (const policy_node_with_uint32_t *) policy_node; + + out->f = 1; + + if (node->n & SEQUENCE_LOCKTIME_TYPE_FLAG) { + out->g = 1; + } else { + out->h = 1; + } + + out->script_size = (uint16_t) (1 + get_push_script_size(node->n)); + + out->ops = (miniscript_ops_t){1, 0, -1}; + out->ss = (miniscript_stacksize_t){0, -1}; + + return 0; + } + case TOKEN_AFTER: { + const policy_node_with_uint32_t *node = (const policy_node_with_uint32_t *) policy_node; + + out->f = 1; + + if (node->n >= LOCKTIME_THRESHOLD) { + out->i = 1; + } else { + out->j = 1; + } + + out->script_size = (uint16_t) (1 + get_push_script_size(node->n)); + + out->ops = (miniscript_ops_t){1, 0, -1}; + out->ss = (miniscript_stacksize_t){0, -1}; + + return 0; + } + case TOKEN_SHA256: + case TOKEN_HASH256: + out->x = 0; + + out->script_size = 4 + 2 + 33; + + out->ops = (miniscript_ops_t){4, 0, -1}; + out->ss = (miniscript_stacksize_t){1, -1}; + + return 0; + case TOKEN_RIPEMD160: + case TOKEN_HASH160: + out->x = 0; + + out->script_size = 4 + 2 + 21; + + out->ops = (miniscript_ops_t){4, 0, -1}; + out->ss = (miniscript_stacksize_t){1, -1}; + + return 0; + case TOKEN_ANDOR: { + const policy_node_with_script3_t *node = + (const policy_node_with_script3_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t y; + policy_node_ext_info_t z; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &y, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[2]), &z, ctx)) + return -1; + + out->s = z.s & (x.s | y.s); + out->f = z.f & (x.s | y.f); + out->e = z.e & (x.s | y.f); + + out->m = x.m & y.m & z.m & x.e & (x.s | y.s | z.s); + + out->g = x.g | y.g | z.g; + out->h = x.h | y.h | z.h; + out->i = x.i | y.i | z.i; + out->j = x.j | y.j | z.j; + + if (!(x.k & y.k & z.k) || (x.g & y.h) || (x.h & y.g) || (x.i & y.j) || (x.j & y.i)) { + out->k = 0; + } + + out->script_size = 3 + x.script_size + y.script_size + z.script_size; + + out->ops = (miniscript_ops_t){ + 3 + x.ops.count + y.ops.count + z.ops.count, + maxcheck(sumcheck(y.ops.sat, x.ops.sat), sumcheck(y.ops.dsat, z.ops.sat)), + sumcheck(x.ops.dsat, z.ops.dsat)}; + out->ss = (miniscript_stacksize_t){ + maxcheck(sumcheck(x.ss.sat, y.ss.sat), sumcheck(x.ss.dsat, z.ss.sat)), + sumcheck(x.ss.dsat, z.ss.dsat)}; + + return 0; + } + case TOKEN_AND_V: { + const policy_node_with_script2_t *node = + (const policy_node_with_script2_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t y; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &y, ctx)) + return -1; + + out->s = x.s | y.s; + out->f = x.s | y.f; + + out->m = x.m & y.m; + + out->g = x.g | y.g; + out->h = x.h | y.h; + out->i = x.i | y.i; + out->j = x.j | y.j; + + if (!(x.k & y.k) || (x.g & y.h) || (x.h & y.g) || (x.i & y.j) || (x.j & y.i)) { + out->k = 0; + } + + out->x = y.x; + + out->script_size = x.script_size + y.script_size; + + out->ops = + (miniscript_ops_t){x.ops.count + y.ops.count, sumcheck(x.ops.sat, y.ops.sat), -1}; + out->ss = (miniscript_stacksize_t){sumcheck(x.ss.sat, y.ss.sat), -1}; + return 0; + } + case TOKEN_AND_B: { + const policy_node_with_script2_t *node = + (const policy_node_with_script2_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t y; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &y, ctx)) + return -1; + + out->s = x.s | y.s; + out->f = (x.f & y.f) | (x.s & x.f) | (y.s & y.f); + out->e = x.e & y.e & x.s & y.s; + + out->m = x.m & y.m; + + out->g = x.g | y.g; + out->h = x.h | y.h; + out->i = x.i | y.i; + out->j = x.j | y.j; + + if (!(x.k & y.k) || (x.g & y.h) || (x.h & y.g) || (x.i & y.j) || (x.j & y.i)) { + out->k = 0; + } + + out->script_size = 1 + x.script_size + y.script_size; + + out->ops = (miniscript_ops_t){1 + x.ops.count + y.ops.count, + sumcheck(x.ops.sat, y.ops.sat), + sumcheck(x.ops.dsat, y.ops.dsat)}; + out->ss = (miniscript_stacksize_t){sumcheck(x.ss.sat, y.ss.sat), + sumcheck(x.ss.dsat, y.ss.dsat)}; + + return 0; + } + case TOKEN_AND_N: { // == andor(X,Y,0) + const policy_node_with_script2_t *node = + (const policy_node_with_script2_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t y; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &y, ctx)) + return -1; + + out->s = x.s | y.s; + out->e = x.s | y.f; + + out->m = x.m & y.m & x.e & (x.s | y.s); + + out->g = x.g | y.g; + out->h = x.h | y.h; + out->i = x.i | y.i; + out->j = x.j | y.j; + + if (!(x.k & y.k) || (x.g & y.h) || (x.h & y.g) || (x.i & y.j) || (x.j & y.i)) { + out->k = 0; + } + + out->script_size = 4 + x.script_size + y.script_size; + + out->ops = (miniscript_ops_t){3 + x.ops.count + y.ops.count, + maxcheck(sumcheck(y.ops.sat, x.ops.sat), y.ops.dsat), + x.ops.dsat}; + out->ss = (miniscript_stacksize_t){sumcheck(x.ss.sat, y.ss.sat), x.ss.dsat}; + return 0; + } + case TOKEN_OR_B: { + const policy_node_with_script2_t *node = + (const policy_node_with_script2_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t z; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &z, ctx)) + return -1; + + out->s = x.s & z.s; + out->e = 1; + + out->m = x.m & z.m & x.e & z.e & (x.s | z.s); + + out->g = x.g | z.g; + out->h = x.h | z.h; + out->i = x.i | z.i; + out->j = x.j | z.j; + + out->k = x.k & z.k; + + out->script_size = 1 + x.script_size + z.script_size; + + out->ops = (miniscript_ops_t){ + 1 + x.ops.count + z.ops.count, + maxcheck(sumcheck(x.ops.sat, z.ops.dsat), sumcheck(z.ops.sat, x.ops.dsat)), + sumcheck(x.ops.dsat, z.ops.dsat)}; + out->ss = (miniscript_stacksize_t){ + maxcheck(sumcheck(x.ss.dsat, z.ss.sat), sumcheck(x.ss.sat, z.ss.dsat)), + sumcheck(x.ss.dsat, z.ss.dsat)}; + return 0; + } + case TOKEN_OR_C: { + const policy_node_with_script2_t *node = + (const policy_node_with_script2_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t z; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &z, ctx)) + return -1; + + out->s = x.s & z.s; + out->f = 1; + + out->m = x.m & z.m & x.e & (x.s | z.s); + + out->g = x.g | z.g; + out->h = x.h | z.h; + out->i = x.i | z.i; + out->j = x.j | z.j; + + out->k = x.k & z.k; + + out->script_size = 2 + x.script_size + z.script_size; + + out->ops = (miniscript_ops_t){2 + x.ops.count + z.ops.count, + maxcheck(x.ops.sat, sumcheck(z.ops.sat, x.ops.dsat)), + -1}; + out->ss = + (miniscript_stacksize_t){maxcheck(x.ss.sat, sumcheck(x.ss.dsat, z.ss.sat)), -1}; + return 0; + } + case TOKEN_OR_D: { + const policy_node_with_script2_t *node = + (const policy_node_with_script2_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t z; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &z, ctx)) + return -1; + + out->s = x.s & z.s; + out->f = z.f; + out->e = z.e; + + out->m = x.m & z.m & x.e & (x.s | z.s); + + out->g = x.g | z.g; + out->h = x.h | z.h; + out->i = x.i | z.i; + out->j = x.j | z.j; + + out->k = x.k & z.k; + + out->script_size = 3 + x.script_size + z.script_size; + + out->ops = (miniscript_ops_t){3 + x.ops.count + z.ops.count, + maxcheck(x.ops.sat, sumcheck(z.ops.sat, x.ops.dsat)), + sumcheck(x.ops.dsat, z.ops.dsat)}; + out->ss = (miniscript_stacksize_t){maxcheck(x.ss.sat, sumcheck(x.ss.dsat, z.ss.sat)), + sumcheck(x.ss.dsat, z.ss.dsat)}; + return 0; + } + case TOKEN_OR_I: { + const policy_node_with_script2_t *node = + (const policy_node_with_script2_t *) policy_node; + policy_node_ext_info_t x; + policy_node_ext_info_t z; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[0]), &x, ctx)) + return -1; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->scripts[1]), &z, ctx)) + return -1; + + out->s = x.s & z.s; + out->f = x.f & z.f; + out->e = (x.e & z.f) | (z.e & x.f); + + out->m = x.m & z.m & (x.s | z.s); + + out->g = x.g | z.g; + out->h = x.h | z.h; + out->i = x.i | z.i; + out->j = x.j | z.j; + + out->k = x.k & z.k; + + out->script_size = 3 + x.script_size + z.script_size; + + out->ops = (miniscript_ops_t){3 + x.ops.count + z.ops.count, + maxcheck(x.ops.sat, z.ops.sat), + maxcheck(x.ops.dsat, z.ops.dsat)}; + out->ss = + (miniscript_stacksize_t){maxcheck(sumcheck(x.ss.sat, 1), sumcheck(z.ss.sat, 1)), + maxcheck(sumcheck(x.ss.dsat, 1), sumcheck(z.ss.dsat, 1))}; + + return 0; + } + case TOKEN_THRESH: { + const policy_node_thresh_t *node = (const policy_node_thresh_t *) policy_node; + + policy_node_scriptlist_t *cur = r_policy_node_scriptlist(&node->scriptlist); + + int count_s = 0; + int count_e = 0; + int count_m = 0; + size_t children_scriptsize = 0; + size_t n_children = 0; + while (cur != NULL) { + ++n_children; + + policy_node_ext_info_t t; + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&cur->script), &t, ctx)) + return -1; + + if (t.e) { + ++count_e; + } + if (t.s) { + ++count_s; + } + if (t.m) { + ++count_m; + } + cur = r_policy_node_scriptlist(&cur->next); + + out->g |= t.g; + out->h |= t.h; + out->i |= t.i; + out->j |= t.j; + + out->k &= t.k; // if any child doesn't have k, thresh doesn't have k + + // if any two children have mixed timelocks, thresh doesn't have k + if (node->k >= 2 && + ((t.g & out->h) || (t.h & out->g) || (t.i & out->j) || (t.j & out->i))) { + out->k = 0; + } + + children_scriptsize += t.script_size; + } + + int count_not_s = node->n - count_s; + + out->s = count_not_s <= node->k - 1 ? 1 : 0; + out->e = count_s == node->n ? 1 : 0; + + out->m = (count_e == node->n && count_not_s <= node->k) ? 1 : 0; + + out->x = 0; + + out->script_size = children_scriptsize + n_children + get_push_script_size(node->k); + + if (0 > compute_thresh_ops(node, &out->ops, ctx)) return -1; + if (0 > compute_thresh_stacksize(node, &out->ss, ctx)) return -1; + + return 0; + } + case TOKEN_A: { + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = x.s; + out->f = x.f; + out->e = x.e; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + out->script_size = x.script_size + 2; + + out->ops = (miniscript_ops_t){2 + x.ops.count, x.ops.sat, x.ops.dsat}; + out->ss = x.ss; + + return 0; + } + case TOKEN_S: + case TOKEN_N: { + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = x.s; + out->f = x.f; + out->e = x.e; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + if (policy_node->type == TOKEN_S) out->x = x.x; + + out->script_size = x.script_size + 1; + + out->ops = (miniscript_ops_t){1 + x.ops.count, x.ops.sat, x.ops.dsat}; + out->ss = x.ss; + + return 0; + } + case TOKEN_C: { + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = 1; + out->f = x.f; + out->e = x.e; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + out->x = x.x; + + out->script_size = x.script_size + 1; + + out->x = 0; + + out->ops = (miniscript_ops_t){1 + x.ops.count, x.ops.sat, x.ops.dsat}; + out->ss = x.ss; + + return 0; + } + case TOKEN_D: { + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = x.s; + out->e = 1; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + out->script_size = x.script_size + 3; + + out->ops = (miniscript_ops_t){3 + x.ops.count, x.ops.sat, 0}; + out->ss = (miniscript_stacksize_t){sumcheck(1, x.ss.sat), 1}; + + return 0; + } + case TOKEN_T: { // and_v(X,1) + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = x.s; + out->f = 1; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + out->script_size = x.script_size + 1; + + out->ops = (miniscript_ops_t){x.ops.count, x.ops.sat, -1}; + + out->ss = (miniscript_stacksize_t){x.ss.sat, -1}; + + return 0; + } + case TOKEN_V: { + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = x.s; + out->f = 1; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + out->script_size = x.script_size + x.x; + + out->ops = (miniscript_ops_t){x.ops.count + x.x, x.ops.sat, -1}; + out->ss = (miniscript_stacksize_t){x.ss.sat, -1}; + + return 0; + } + case TOKEN_J: { + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = x.s; + out->e = x.f; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + out->script_size = x.script_size + 4; + + out->ops = (miniscript_ops_t){4 + x.ops.count, x.ops.sat, 0}; + out->ss = (miniscript_stacksize_t){x.ss.sat, 1}; + + return 0; + } + case TOKEN_L: // or_i(0,X) + case TOKEN_U: { // or_i(X,0) + const policy_node_with_script_t *node = (const policy_node_with_script_t *) policy_node; + policy_node_ext_info_t x; + + if (0 > compute_miniscript_policy_ext_info(r_policy_node(&node->script), &x, ctx)) + return -1; + + out->s = x.s; + out->f = 0; + out->e = x.f; + + out->m = x.m; + + out->g = x.g; + out->h = x.h; + out->i = x.i; + out->j = x.j; + out->k = x.k; + + out->script_size = x.script_size + 4; + + out->ops = (miniscript_ops_t){3 + x.ops.count, x.ops.sat, x.ops.dsat}; + out->ss = (miniscript_stacksize_t){sumcheck(x.ss.sat, 1), + maxcheck(1, sumcheck(x.ss.dsat, 1))}; + + return 0; + } + case TOKEN_SORTEDMULTI: + case TOKEN_SORTEDMULTI_A: + case TOKEN_WPKH: + case TOKEN_SH: + case TOKEN_WSH: + case TOKEN_TR: + PRINTF("Not miniscript: %d\n", policy_node->type); + return -1; + default: + PRINTF("%s, %d\n", __FILE__, __LINE__); + PRINTF("Unknown token: %d\n", policy_node->type); + return -1; + } +} + +#ifndef SKIP_FOR_CMOCKA + +void get_policy_wallet_id(policy_map_wallet_header_t *wallet_header, uint8_t out[static 32]) { + cx_sha256_t wallet_hash_context; + cx_sha256_init(&wallet_hash_context); + + crypto_hash_update_u8(&wallet_hash_context.header, wallet_header->version); + crypto_hash_update_u8(&wallet_hash_context.header, wallet_header->name_len); + crypto_hash_update(&wallet_hash_context.header, wallet_header->name, wallet_header->name_len); + + crypto_hash_update_varint(&wallet_hash_context.header, wallet_header->descriptor_template_len); + + if (wallet_header->version == WALLET_POLICY_VERSION_V1) { + crypto_hash_update(&wallet_hash_context.header, + wallet_header->descriptor_template, + wallet_header->descriptor_template_len); + } else { // WALLET_POLICY_VERSION_V2 + crypto_hash_update(&wallet_hash_context.header, + wallet_header->descriptor_template_sha256, + 32); + } + + crypto_hash_update_varint(&wallet_hash_context.header, wallet_header->n_keys); + + crypto_hash_update(&wallet_hash_context.header, wallet_header->keys_info_merkle_root, 32); + + crypto_hash_digest(&wallet_hash_context.header, out, 32); +} + +#endif // SKIP_FOR_CMOCKA diff --git a/src/common/wallet.h b/src/common/wallet.h index a08c1562c..29fc0a1a1 100644 --- a/src/common/wallet.h +++ b/src/common/wallet.h @@ -1,102 +1,99 @@ #pragma once #include +#include #include "ledger_assert.h" #include "common/bip32.h" #include "common/buffer.h" -#include "common/wif.h" #include "../constants.h" +#include "../crypto.h" #ifndef SKIP_FOR_CMOCKA #include "os.h" #include "cx.h" #endif -/** - * Wallet types - */ -/// Wallet type: policy map -#define WALLET_TYPE_POLICY_MAP 1 - #ifdef HAVE_LIQUID -/// Maximum supported number of keys for a policy map. -#define MAX_POLICY_MAP_COSIGNERS 7 -#else -/// Maximum supported number of keys for a policy map. -#define MAX_POLICY_MAP_COSIGNERS 5 +#include "liquid_wallet.h" #endif -/// Maximum supported number of keys for a policy map. -#define MAX_POLICY_MAP_KEYS MAX_POLICY_MAP_COSIGNERS +// The maximum number of keys supported for CHECKMULTISIG{VERIFY} +// bitcoin-core supports up to 20, but we limit to 16 as bigger pushes require special handling. +#define MAX_PUBKEYS_PER_MULTISIG 16 -/// Maximum length of public key wildcard in characters -#define MAX_POLICY_MAP_KEY_WILDCARD_LEN (sizeof("/<0;1>/*") - 1) +#define WALLET_POLICY_VERSION_V1 1 // the legacy version of the first release +#define WALLET_POLICY_VERSION_V2 2 // the current full version // The string describing a pubkey can contain: // - (optional) the key origin info, which we limit to 46 bytes (2 + 8 + 3*12 = 46 bytes) // - the xpub itself (up to 113 characters) -// - optional, the wildcard suffix. +// - optional, the "/**" suffix. // Therefore, the total length of the key info string is at most 162 bytes. -/// Maximum length of key information string -#define MAX_POLICY_KEY_INFO_LEN (46 + MAX_SERIALIZED_PUBKEY_LENGTH + \ - MAX_POLICY_MAP_KEY_WILDCARD_LEN) - -#ifdef HAVE_LIQUID -/// Maximum length of blinding key descriptor (length of Base58-encoded xpub) -#define MAX_POLICY_MAP_BLINDING_KEY_LENGTH 112 -// Enough to store "ct(,sh(wsh(sortedmulti(15,@0,@1,...,@n))))" -/// Length of policy map string -#define MAX_POLICY_MAP_STR_LENGTH ( sizeof("ct(,sh(wsh(sortedmulti(15))))") - 1 + \ - MAX_POLICY_MAP_BLINDING_KEY_LENGTH + \ - 4 * MAX_POLICY_MAP_COSIGNERS ) +#define MAX_POLICY_KEY_INFO_LEN_V1 (46 + MAX_SERIALIZED_PUBKEY_LENGTH + 3) + +// In V1, there is no "/**" suffix, as that is no longer part of the key +#define MAX_POLICY_KEY_INFO_LEN_V2 (46 + MAX_SERIALIZED_PUBKEY_LENGTH) + +#define MAX_POLICY_KEY_INFO_LEN MAX(MAX_POLICY_KEY_INFO_LEN_V1, MAX_POLICY_KEY_INFO_LEN_V2) + +// longest supported policy in V1 is "sh(wsh(sortedmulti(5,@0,@1,@2,@3,@4)))", 38 bytes +#define MAX_DESCRIPTOR_TEMPLATE_LENGTH_V1 40 + +#ifdef TARGET_NANOS +// this amount should be enough for many useful policies +#define MAX_DESCRIPTOR_TEMPLATE_LENGTH_V2 192 +// As the in-memory representation of wallet policy is implementation-specific, we would like +// this limit not to be hit for descriptor templates below the maximum length +// MAX_DESCRIPTOR_TEMPLATE_LENGTH_V2. +// A policy requiring about 300 bytes after parsing was reported by developers working on the Liana +// miniscript wallet. 320 = 64*5, so that it is a multiple of the NVRAM page size and fits all known +// cases. +#define MAX_WALLET_POLICY_BYTES 320 #else -// Enough to store "sh(wsh(sortedmulti(15,@0,@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11,@12,@13,@14)))" -/// Length of policy map string -#define MAX_POLICY_MAP_STR_LENGTH 74 +// On larger devices, we can afford to reserve a lot more memory. +// We do not expect these limits to be reached in practice any time soon, and the value +// of MAX_WALLET_POLICY_BYTES is chosen so that MAX_DESCRIPTOR_TEMPLATE_LENGTH_V2 and +// MAX_WALLET_POLICY_BYTES are approximately in the same proportion as defined on NanoS. +#define MAX_DESCRIPTOR_TEMPLATE_LENGTH_V2 512 +#define MAX_WALLET_POLICY_BYTES 896 #endif -/// Maximum length of wallet policy -#define MAX_POLICY_MAP_NAME_LENGTH 16 +#define MAX_DESCRIPTOR_TEMPLATE_LENGTH \ + MAX(MAX_DESCRIPTOR_TEMPLATE_LENGTH_V1, MAX_DESCRIPTOR_TEMPLATE_LENGTH_V2) -// at most 126 bytes +// at most 92 bytes // wallet type (1 byte) // name length (1 byte) -// name (max MAX_POLICY_MAP_NAME_LENGTH bytes) +// name (max MAX_WALLET_NAME_LENGTH bytes) // policy length (1 byte) -// policy (max MAX_POLICY_MAP_STR_LENGTH bytes) +// policy (max MAX_DESCRIPTOR_TEMPLATE_LENGTH bytes) // n_keys (1 byte) // keys_merkle_root (32 bytes) -/// Maximum length of serialized wallet policy -#define MAX_POLICY_MAP_SERIALIZED_LENGTH \ - (1 + 1 + MAX_POLICY_MAP_NAME_LENGTH + 1 + MAX_POLICY_MAP_STR_LENGTH + 1 + 32) +#define MAX_WALLET_POLICY_SERIALIZED_LENGTH_V1 \ + (1 + 1 + MAX_WALLET_NAME_LENGTH + 1 + MAX_DESCRIPTOR_TEMPLATE_LENGTH_V1 + 1 + 32) -#ifdef HAVE_LIQUID -/// Maximum size of a parsed policy map in memory -#define MAX_POLICY_MAP_BYTES 208 -#else -/// Maximum size of a parsed policy map in memory -#define MAX_POLICY_MAP_BYTES 128 -#endif +// at most 100 bytes +// wallet type (1 byte) +// name length (1 byte) +// name (max MAX_WALLET_NAME_LENGTH bytes) +// policy length (varint, up to 9 bytes) +// policy hash 32 +// n_keys (varint, up to 9 bytes) +// keys_merkle_root (32 bytes) +#define MAX_WALLET_POLICY_SERIALIZED_LENGTH_V2 (1 + 1 + MAX_WALLET_NAME_LENGTH + 9 + 32 + 9 + 32) -// Currently only multisig is supported -/// Maximum length of wallet policy -#define MAX_POLICY_MAP_LEN MAX_MULTISIG_POLICY_MAP_LENGTH +#define MAX_WALLET_POLICY_SERIALIZED_LENGTH \ + MAX(MAX_WALLET_POLICY_SERIALIZED_LENGTH_V1, MAX_WALLET_POLICY_SERIALIZED_LENGTH_V2) -/// Public key wildcards defining the rules for child key derivation. -typedef enum { - /// No wildcard. - KEY_WILDCARD_NONE = 0, - /// Any derivation is allowed: `/**`. - KEY_WILDCARD_ANY = 1, - /// Internal or external chain with an arbitrary address index: `/<0;1>/*`. - KEY_WILDCARD_STANDARD_CHAINS, - /// External chain with an arbitrary address index: `/0/*`. - KEY_WILDCARD_EXTERNAL_CHAIN, - /// Internal chain (change) with an arbitrary address index: `/1/*`. - KEY_WILDCARD_INTERNAL_CHAIN, -} policy_map_key_wildcard_id_t; +// maximum depth of a taproot tree that we support +// (here depth 1 means only the root of the taptree) +#ifdef TARGET_NANOS +#define MAX_TAPTREE_POLICY_DEPTH 4 +#else +#define MAX_TAPTREE_POLICY_DEPTH 9 +#endif /// Public key information typedef struct { @@ -108,44 +105,51 @@ typedef struct { uint8_t master_key_derivation_len; /// Set to 1 if key has origin data: master key fingerprint and derivation path uint8_t has_key_origin; - /// Wildcard terminating the key string, one of policy_map_key_wildcard_id_t constants. - /// Nonzero if the key ends with a wildcard. - uint8_t wildcard_id; + /// Wildcard flag: true iff the keys ends with the wildcard (/ followed by **) + uint8_t has_wildcard; /// Serialized extended public key - char ext_pubkey[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; + serialized_extended_pubkey_t ext_pubkey; } policy_map_key_info_t; /// Wallet header typedef struct { - /// Wallet type - uint8_t type; // Currently the only supported value is WALLET_TYPE_POLICY_MAP + // Policy version, supported values: WALLET_POLICY_VERSION_V1 and WALLET_POLICY_VERSION_V2 + uint8_t version; /// Length of wallet name uint8_t name_len; + /// Length of descriptor template + uint16_t descriptor_template_len; /// Wallet name char name[MAX_WALLET_NAME_LENGTH + 1]; - /// Length of wallet policy map - uint16_t policy_map_len; - /// Wallet policy map as a string - char policy_map[MAX_POLICY_MAP_STR_LENGTH]; + union { + /// Descriptor template + char descriptor_template[MAX_DESCRIPTOR_TEMPLATE_LENGTH_V1]; // used in V1 + /// Hash of descriptor template + uint8_t descriptor_template_sha256[32]; // used in V2 + }; /// Number of public keys size_t n_keys; - /// /// Merkle root of the vector of key descriptors - uint8_t keys_info_merkle_root[32]; // root of the Merkle tree of the keys information + /// Root of the Merkle tree of the keys information + uint8_t keys_info_merkle_root[32]; } policy_map_wallet_header_t; /// Type of policy node typedef enum { - TOKEN_SH, ///< Pay to script hash - TOKEN_WSH, ///< Pay to witness script hash - // TOKEN_PK, // disabled, but it will be needed for taproot - TOKEN_PKH, ///< Pay to public key hash - TOKEN_WPKH, ///< Pay to witness public key hash - // TOKEN_COMBO // disabled, does not mix well with the script policy language - TOKEN_MULTI, ///< Multisig - TOKEN_SORTEDMULTI, ///< Multisig with sorted keys - TOKEN_TR, ///< Taproot - // TOKEN_ADDR, // unsupported - // TOKEN_RAW, // unsupported + TOKEN_SH, + TOKEN_WSH, + TOKEN_PK, + TOKEN_PKH, + TOKEN_WPKH, + // TOKEN_COMBO // disabled, does not mix well with the script policy language + TOKEN_MULTI, + TOKEN_MULTI_A, + TOKEN_SORTEDMULTI, + TOKEN_SORTEDMULTI_A, + TOKEN_TR, + // TOKEN_ADDR, // unsupported + // TOKEN_RAW, // unsupported + +#ifdef HAVE_LIQUID TOKEN_CT, ///< ELIP 150: confidential transaction top-level wrapper TOKEN_SLIP77, ///< ELIP 150: SLIP-0077-derived master private blinding key TOKEN_HEX_PUB, ///< ELIP 150: compressed public key, parsed from 66 hex string @@ -153,190 +157,377 @@ typedef enum { TOKEN_XPUB, ///< ELIP 150: compressed public key, parsed from xpub TOKEN_XPRV, ///< ELIP 150: private key, parsed from xprv TOKEN_ELIP151, ///< ELIP 151: elip151 tag indicating ELIP 151 blinding key derivation -} PolicyNodeType; +#endif -// TODO: the following structures are using size_t for all integers to avoid alignment problems; -// if memory is an issue, we could use a packed version instead, but care needs to be taken -// when accessing pointers, since they would be unaligned. + /* miniscript tokens */ + + TOKEN_0, + TOKEN_1, + TOKEN_PK_K, + TOKEN_PK_H, + TOKEN_OLDER, + TOKEN_AFTER, + TOKEN_SHA256, + TOKEN_HASH256, + TOKEN_RIPEMD160, + TOKEN_HASH160, + TOKEN_ANDOR, + TOKEN_AND_V, + TOKEN_AND_B, + TOKEN_AND_N, + TOKEN_OR_B, + TOKEN_OR_C, + TOKEN_OR_D, + TOKEN_OR_I, + TOKEN_THRESH, + // wrappers + TOKEN_A, + TOKEN_S, + TOKEN_C, + TOKEN_T, + TOKEN_D, + TOKEN_V, + TOKEN_J, + TOKEN_N, + TOKEN_L, + TOKEN_U, + + TOKEN_INVALID = -1 // used to mark invalid tokens +} PolicyNodeType; -/// Abstract type for all policy nodes -typedef struct { - /// Type of this policy node +typedef enum { + MINISCRIPT_CONTEXT_P2WSH, + MINISCRIPT_CONTEXT_TAPSCRIPT, +} MiniscriptContext; + +// miniscript basic types +#define MINISCRIPT_TYPE_B 0 +#define MINISCRIPT_TYPE_V 1 +#define MINISCRIPT_TYPE_K 2 +#define MINISCRIPT_TYPE_W 3 + +// The various structures used to represent the wallet policy abstract syntax tree contain a lot +// pointers; using a regular pointer would make each of them 4 bytes long, moreover causing +// additional loss of memory due to padding. Instead, we use a 2-bytes relative pointer to point to +// policy_nodes, representing a non-negative offset from the position of the structure itself. +// This reduces the memory utilization of those pointers, and moreover it allows to reduce padding +// in other structures, as they no longer contain 32-bit pointers. +// Moreover, avoiding all pointers makes sure that the structure can be copied to a different +// location if needed (making sure the destination is aligned due to the platform restrictions). +// The following macro defines the data structure and the helper methods for a relative pointer to a +// type. The code does not depend on the type, but this allows to keep strong types when dealing +// with relative pointers, which otherwise would require numerous type casts. + +// Defines a relative pointer type for name##t, and the conversion functions to/from a relative +// pointer and a pointer to name##_t. +// Relative pointers use an uint16_t to represent the offset; therefore, the offset must be +// non-negative and at most 65535. +// An offset of 0 corresponds to a NULL pointer in the conversion (and vice-versa). +#define DEFINE_REL_PTR(name, type) \ + /* \ + * Relative pointer structure for `type`. \ + * \ + * This structure holds an offset that is used to calculate the actual pointer \ + * to a `type` object. \ + */ \ + typedef struct rptr_##name##_s { \ + uint16_t offset; \ + } rptr_##name##_t; \ + \ + /* \ + * Resolve a relative pointer to a `type` object. \ + * \ + * @param ptr A pointer to the relative pointer structure. \ + * @return A pointer to the `type` object. \ + */ \ + static inline type *r_##name(const rptr_##name##_t *ptr) { \ + if (ptr->offset == 0) \ + return NULL; \ + else \ + return (type *) ((const uint8_t *) ptr + ptr->offset); \ + } \ + \ + /* \ + * Returns true when the offset of the relative pointer is 0 (equivalent to a NULL pointer). \ + * \ + * @param relative_ptr A relative pointer. \ + */ \ + static inline bool isnull_##name(const rptr_##name##_t *ptr) { \ + return ptr->offset == 0; \ + } \ + \ + /* \ + * Initialize a relative pointer to a `type` object. \ + * \ + * @param relative_ptr A pointer to the relative pointer structure to be initialized. \ + * @param obj A pointer to the `type` object. \ + */ \ + static inline void i_##name(rptr_##name##_t *relative_ptr, void *obj) { \ + if (obj == NULL) \ + relative_ptr->offset = 0; \ + else { \ + int offset = (uint8_t *) obj - (uint8_t *) relative_ptr; \ + LEDGER_ASSERT(offset >= 0 && offset < UINT16_MAX, \ + "Relative pointer's offset must be between 0 and 65535"); \ + relative_ptr->offset = (uint16_t) offset; \ + } \ + } + +// 2 bytes +typedef struct policy_node_s { PolicyNodeType type; - /// Data specific to subtype - void *node_data; // subtypes will redefine this + struct { + unsigned int is_miniscript : 1; + unsigned int miniscript_type : 2; // B, C, K or W + unsigned int miniscript_mod_z : 1; + unsigned int miniscript_mod_o : 1; + unsigned int miniscript_mod_n : 1; + unsigned int miniscript_mod_d : 1; + unsigned int miniscript_mod_u : 1; + } flags; // 1 byte } policy_node_t; -/// Policy node with script: sh(), wsh() +DEFINE_REL_PTR(policy_node, policy_node_t) + +typedef struct miniscript_ops_s { + uint16_t count; // non-push opcodes + int16_t sat; // number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to satisfy (-1 + // for "invalid") + int16_t dsat; // number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to dissatisfy + // (-1 for "invalid") +} miniscript_ops_t; + +typedef struct miniscript_stacksize_s { + int16_t sat; // Maximum stack size to satisfy + int16_t dsat; // Maximum stack size to dissatisfy +} miniscript_stacksize_t; + +typedef struct policy_node_ext_info_s { + miniscript_ops_t ops; + miniscript_stacksize_t ss; + uint16_t script_size; + + unsigned int s : 1; // has a signature + + unsigned int f : 1; // forced + unsigned int e : 1; + + unsigned int m : 1; // non-malleable property + + // flags related to timelocks + unsigned int g : 1; // older: contains relative time timelock (csv_time) + unsigned int h : 1; // older: contains relative height timelock (csv_height) + unsigned int i : 1; // after: contains time timelock (cltv_time) + unsigned int j : 1; // after: contains height timelock (cltv_height) + unsigned int k : 1; // does not contain a combination of height and time locks + + unsigned int x : 1; // the last opcode is not EQUAL, CHECKSIG, or CHECKMULTISIG +} policy_node_ext_info_t; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcomment" +// The compiler doesn't like /** inside a block comment, so we disable this warning temporarily. + +/** Structure representing a key placeholder. + * In V1, it's the index of a key expression in the key informations array, which includes the final + * / ** step. + * In V2, it's the index of a key expression in the key informations array, plus the two + * numbers a, b in the //* derivation steps; here, the xpubs in the key informations + * array don't have extra derivation steps. + */ +#pragma GCC diagnostic pop +// 12 bytes +typedef struct { + // the following fields are only used in V2 + uint32_t num_first; // NUM_a of //* + uint32_t num_second; // NUM_b of //* + + // common between V1 and V2 + int16_t key_index; // index of the key +} policy_node_key_placeholder_t; + +DEFINE_REL_PTR(policy_node_key_placeholder, policy_node_key_placeholder_t) + +// 4 bytes +typedef struct { + struct policy_node_s base; +} policy_node_constant_t; + +// 4 bytes typedef struct { - /// Type of this policy node - PolicyNodeType type; // == TOKEN_SH, == TOKEN_WSH - /// Innder script - policy_node_t *script; + struct policy_node_s base; + rptr_policy_node_t script; } policy_node_with_script_t; -/// Policy node with key: pk(), pkh(), wpkh() +// 6 bytes typedef struct { - /// Type of this policy node - PolicyNodeType type; // == TOKEN_PK, == TOKEN_PKH, == TOKEN_WPKH - /// Key index - size_t key_index; // index of the key + struct policy_node_s base; + rptr_policy_node_t scripts[2]; +} policy_node_with_script2_t; + +// 8 bytes +typedef struct { + struct policy_node_s base; + rptr_policy_node_t scripts[3]; +} policy_node_with_script3_t; + +// generic type with pointer for up to 3 (but constant) number of child scripts +typedef policy_node_with_script3_t policy_node_with_scripts_t; + +// 4 bytes +typedef struct { + struct policy_node_s base; + rptr_policy_node_key_placeholder_t key_placeholder; } policy_node_with_key_t; -/// Multisig policy node: multi(), sortedmulti() +// 8 bytes +typedef struct { + struct policy_node_s base; + uint32_t n; +} policy_node_with_uint32_t; + +// 12 bytes typedef struct { - /// Type of this policy node - PolicyNodeType type; // == TOKEN_MULTI, == TOKEN_SORTEDMULTI - /// Threshold - size_t k; - /// Number of keys - size_t n; - /// Pointer to array of exactly n key indexes - size_t *key_indexes; + struct policy_node_s base; // type is TOKEN_MULTI or TOKEN_SORTEDMULTI + int16_t k; // threshold + int16_t n; // number of keys + rptr_policy_node_key_placeholder_t + key_placeholders; // pointer to array of exactly n key placeholders } policy_node_multisig_t; -/// Policy node ct() +// 8 bytes +struct policy_node_scriptlist_s; // forward declaration, as the struct is recursive + +DEFINE_REL_PTR(policy_node_scriptlist, struct policy_node_scriptlist_s) + +typedef struct policy_node_scriptlist_s { + rptr_policy_node_scriptlist_t next; + rptr_policy_node_t script; +} policy_node_scriptlist_t; + +// 12 bytes, (+ 8 bytes for every script) typedef struct { - /// Type of this policy node - PolicyNodeType type; // == TOKEN_CT - /// Master blinding key script, typically slip77() - policy_node_t *mbk_script; - /// Inner script - policy_node_t *script; -} policy_node_ct_t; - -/// Policy node containing ELIP 150 blinding public key + struct policy_node_s base; // type is TOKEN_THRESH + int16_t k; // threshold + int16_t n; // number of child scripts + rptr_policy_node_scriptlist_t + scriptlist; // pointer to array of exactly n pointers to child scripts +} policy_node_thresh_t; + typedef struct { - /// Type of this policy node, one of: TOKEN_HEX_PUB, TOKEN_XPUB - PolicyNodeType type; - /// Compressed public key - uint8_t pubkey[33]; -} policy_node_blinding_pubkey_t; + struct policy_node_s base; // type is TOKEN_SHA160 or TOKEN_HASH160 + uint8_t h[20]; +} policy_node_with_hash_160_t; -/// Policy node containing ELIP 150 blinding private key typedef struct { - /// Type of this policy node, one of: TOKEN_SLIP77, TOKEN_HEX_PRV, TOKEN_XPRV - PolicyNodeType type; - /// Private key - uint8_t privkey[32]; -} policy_node_blinding_privkey_t; + struct policy_node_s base; // type is TOKEN_SHA256 or TOKEN_HASH256 + uint8_t h[32]; +} policy_node_with_hash_256_t; + +struct policy_node_tree_s; // forward declaration, as the struct is recursive +DEFINE_REL_PTR(policy_node_tree, struct policy_node_tree_s) + +// a TREE is either a script, or a {TREE,TREE} +typedef struct policy_node_tree_s { + bool is_leaf; // if this is a leaf, then it contains a pointer to a SCRIPT; + // otherwise, it contains two pointers to TREE expressions. + union { + rptr_policy_node_t script; // pointer to a policy_node_with_script_t + struct { + rptr_policy_node_tree_t left_tree; // pointer to a policy_node_tree_s + rptr_policy_node_tree_t right_tree; // pointer to a policy_node_tree_s + }; + }; +} policy_node_tree_t; -/** - * Checks if the policy node has a private (blinding) key. - * - * @param[in] node - * Pointer to policy node. - * - * @return true if given policy node has a private blinding key, false otherwise. - */ -static inline bool policy_node_has_private_key(const policy_node_t *node) { - return ( node->type == TOKEN_SLIP77 || - node->type == TOKEN_HEX_PRV || - node->type == TOKEN_XPRV ); -} +typedef struct { + struct policy_node_s base; + rptr_policy_node_key_placeholder_t key_placeholder; + rptr_policy_node_tree_t tree; // NULL if tr(KP) +} policy_node_tr_t; /** - * Checks if the policy node has a compressed public (blinding) key. + * Parses the string in the `buffer` as a serialized policy map into `header` * - * @param[in] node - * Pointer to policy node. - * - * @return true if given policy node has a private blinding key, false otherwise. + * @param buffer the pointer to the buffer_t to parse + * @param header the pointer to a `policy_map_wallet_header_t` structure + * @return a negative number on failure, 0 on success. */ -static inline bool policy_node_has_public_key(const policy_node_t *node) { - return ( node->type == TOKEN_HEX_PUB || - node->type == TOKEN_XPUB ); -} +int read_wallet_policy_header(buffer_t *buffer, policy_map_wallet_header_t *header); -/** - * Reads wallet policy from buffer and decodes wallet header. - * - * @param[in,out] buffer - * Pointer to buffer storing serialized wallet policy. - * @param[out] header - * Pointer to structure instance receiving decoded wallet header. - * - * @return 0 if successful, a negative number on error. - */ -int read_policy_map_wallet(buffer_t *buffer, policy_map_wallet_header_t *header); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcomment" +// The compiler doesn't like /** inside a block comment, so we disable this warning temporarily. /** * Parses a string representing the key information for a policy map wallet. - * * The string is compatible with the output descriptor format, except that the pubkey must _not_ * have derivation steps (the key origin info, if present, does have derivation steps from the * master key fingerprint). The serialized base58check-encoded pubkey is _not_ validated. * - * For example: + * For WALLET_POLICY_VERSION_V1, the final suffix /** must be present and is part of the key + * information. For WALLET_POLICY_VERSION_V2, parsing stops at the xpub. + + * Example (V1): + * "[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/**" + * Example (V2): * "[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" - * - * @param[in,out] buffer - * Pointer to buffer storing serialized wallet policy. - * @param[out] out - * Pointer to structure instance receiving parsed key information. - * - * @return 0 if successful, a negative number on error. */ -int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out); +int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out, int version); + +#pragma GCC diagnostic pop /** - * Parses a script expression from the input buffer. + * Parses `in_buf` as a wallet policy descriptor template, constructing the abstract syntax tree in + * the buffer `out` of size `out_len`. * - * During parsing this function allocates the nodes and variables in the output buffer. The initial - * pointer in output buffer will contain the root node of the script. - * - * @param[in,out] in_buf - * Input buffer with a script expression to parse. - * @param[out] out - * Output buffer which receives a tree-like structure of nodes. - * @param out_len - * Size of the output buffer. - * @param bip32_pubkey_version - * Version prefix to use for the public key. - * @param bip32_privkey_version - * Version prefix to use for the private key. - * - * @return 0 if successful, a negative number on error. + * When parsing descriptors containing miniscript, this fails if the miniscript is not correct, + * as defined by the miniscript type system. + * This does NOT check non-malleability of the miniscript. + * @param in_buf the buffer containing the policy map to parse + * @param out the pointer to the output buffer, which must be 4-byte aligned + * @param out_len the length of the output buffer + * @param version either WALLET_POLICY_VERSION_V1 or WALLET_POLICY_VERSION_V2 + * @return The memory size of the parsed descriptor template (that is, the number of bytes consumed + * in the output buffer) on success; -1 in case of parsing error, if the output buffer is unaligned, + * or if the output buffer is too small. */ -int parse_policy_map(buffer_t *in_buf, - void *out, - size_t out_len, - uint32_t bip32_pubkey_version, - uint32_t bip32_privkey_version); +int parse_descriptor_template(buffer_t *in_buf, void *out, size_t out_len, int version); /** - * Checks if the policy specifies a multisignature wallet. + * Given a valid policy that the bitcoin app is able to sign, returns the segwit version. + * The result is undefined for a node that is not a valid root of a wallet policy that the bitcoin + * app is able to sign. * - * @param[in] policy - * Pointer to wallet's top-level policy node. - * - * @return true if the wallet is multisig, false otherwise. + * @param policy the root node of the wallet policy + * @return -1 if it's a legacy policy, 0 if it is a policy for SegwitV0 (possibly nested), 1 for + * SegwitV1 (taproot). */ -bool policy_is_multisig(const policy_node_t *policy); - -#ifndef SKIP_FOR_CMOCKA +int get_policy_segwit_version(const policy_node_t *policy); /** - * Computes wallet identifier (sha256 of the serialization). + * Computes additional properties of the given miniscript, to detect malleability and other security + * properties to assess if the miniscript is sane. + * The stack size limits are only valid for miniscript within wsh. * - * @param[in] wallet_header - * Wallet header. - * @param[out] out - * Output buffer receiving a computed 32-byte wallet identifier. + * @param policy_node a pointer to a miniscript policy node + * @param out pointer to the output policy_node_ext_info_t + * @param ctx either MINISCRIPT_CONTEXT_P2WSH or MINISCRIPT_CONTEXT_TAPSCRIPT + * @return a negative number on error; 0 on success. */ -void get_policy_wallet_id(const policy_map_wallet_header_t *wallet_header, uint8_t out[static 32]); +int compute_miniscript_policy_ext_info(const policy_node_t *policy_node, + policy_node_ext_info_t *out, + MiniscriptContext ctx); + +#ifndef SKIP_FOR_CMOCKA /** - * Validates the public key stored in key information for a policy map wallet. - * - * @param[in] key_info - * Key information. - * @param bip32_pubkey_version - * Version prefix to use for the public key. + * Computes the id of the policy map wallet (commitment to header + policy map + keys_info), as per + * specifications. * - * @return true if key is valid, false otherwise. + * @param wallet_header + * @param out a pointer to a 32-byte array for the output */ -bool validate_policy_map_extended_pubkey(const policy_map_key_info_t *key_info, - uint32_t bip32_pubkey_version); +void get_policy_wallet_id(policy_map_wallet_header_t *wallet_header, uint8_t out[static 32]); #endif diff --git a/src/common/write.c b/src/common/write.c index 34d78ae98..824e69e9a 100644 --- a/src/common/write.c +++ b/src/common/write.c @@ -1,5 +1,5 @@ /***************************************************************************** - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/constants.h b/src/constants.h index 222b16fac..6482f0b21 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,29 +1,14 @@ #pragma once -/** - * Instruction class of the Bitcoin application. - */ -#define CLA_APP_LEGACY 0xE0 - /** * Instruction class of the Bitcoin application. */ #define CLA_APP 0xE1 /** - * Length of APPNAME variable in the Makefile. - */ -#define APPNAME_LEN (sizeof(APPNAME) - 1) - -/** - * Maximum length of MAJOR_VERSION || MINOR_VERSION || PATCH_VERSION. - */ -#define APPVERSION_LEN 3 - -/** - * Maximum length of application name. + * Encodes the protocol version, which is passed in the p2 field of APDUs. */ -#define MAX_APPNAME_LEN 64 +#define CURRENT_PROTOCOL_VERSION 1 /** * Maximum length of SegWit prefix in characters @@ -41,13 +26,7 @@ * Maximum length of a serialized address (in characters). * Segwit addresses can reach 74 characters; 76 on regtest because of the longer "bcrt" prefix. */ -#ifdef COIN_NATIVE_SEGWIT_PREFIX #define MAX_ADDRESS_LENGTH_STR (72 + sizeof(COIN_NATIVE_SEGWIT_PREFIX)) -#else -// To be removed once altcoins are moved to a separate repo, as COIN_NATIVE_SEGWIT_PREFIX -// will always be defined -#define MAX_ADDRESS_LENGTH_STR 74 -#endif #endif // HAVE_LIQUID /** @@ -60,11 +39,6 @@ */ #define MAX_DER_SIG_LEN 72 -/** - * Exponent used to convert mBTC to BTC unit (N BTC = N * 10^3 mBTC). - */ -#define EXPONENT_SMALLEST_UNIT 3 - /** * Maximum scriptPubKey length for an input that we can sign. */ @@ -78,14 +52,20 @@ /** * Maximum length of a wallet registered into the device (characters), excluding terminating NULL. */ -#define MAX_WALLET_NAME_LENGTH 16 +#define MAX_WALLET_NAME_LENGTH 64 /** * Maximum length (characters) of a base58check-encoded serialized extended pubkey. */ #define MAX_SERIALIZED_PUBKEY_LENGTH 113 +/** + * Maximum number of inputs supported while signing a transaction. + */ +#define MAX_N_INPUTS_CAN_SIGN 512 + // SIGHASH flags +#define SIGHASH_DEFAULT 0x00000000 #define SIGHASH_ALL 0x00000001 #define SIGHASH_NONE 0x00000002 #define SIGHASH_SINGLE 0x00000003 @@ -119,3 +99,10 @@ * Currently supported types: 'transaction', 'issuance', 'reissuance', 'burn' */ #define MAX_TRANSACTION_TYPE_LEN 15 + +#define SEQUENCE_LOCKTIME_TYPE_FLAG (1 << 22) +#define LOCKTIME_THRESHOLD 500000000 + +#define MAX_STANDARD_P2WSH_STACK_ITEMS 100U +#define MAX_STANDARD_P2WSH_SCRIPT_SIZE 3600U +#define MAX_OPS_PER_SCRIPT 201U \ No newline at end of file diff --git a/src/crypto.c b/src/crypto.c index 21b00d124..d28dcf264 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,10 @@ #include "cx_stubs.h" #include "cx_ecfp.h" #include "ox_ec.h" +#include "cx_ram.h" +#include "lcx_ripemd160.h" +#include "cx_ripemd160.h" +#include "lib_standard_app/crypto_helpers.h" #endif #include "common/base58.h" @@ -33,19 +37,13 @@ #include "common/read.h" #include "common/write.h" -#include "crypto.h" +#include "cxram_stash.h" +#include "debug-helpers/debug.h" -#ifndef SKIP_FOR_CMOCKA -#include "cx_ram.h" -#include "lcx_ripemd160.h" -#include "cx_ripemd160.h" -#include "../../cxram_stash.h" -#endif +#include "crypto.h" #include "util.h" -#ifndef SKIP_FOR_CMOCKA - /** * Generator for secp256k1, value 'g' defined in "Standards for Efficient Cryptography" * (SEC2) 2.7.1. @@ -88,6 +86,9 @@ const uint8_t secp256k1_sqr_exponent[] = { static const uint8_t BIP0341_taptweak_tag[] = {'T', 'a', 'p', 'T', 'w', 'e', 'a', 'k'}; #endif +static const uint8_t BIP0341_tapbranch_tag[] = {'T', 'a', 'p', 'B', 'r', 'a', 'n', 'c', 'h'}; +static const uint8_t BIP0341_tapleaf_tag[] = {'T', 'a', 'p', 'L', 'e', 'a', 'f'}; + static int secp256k1_point(const uint8_t scalar[static 32], uint8_t out[static 65]); /** @@ -206,32 +207,19 @@ void crypto_hash_update_zeros(cx_hash_t *hash_context, size_t n_zeros) { } } -#ifndef _NR_cx_hash_ripemd160 -/** Missing in some SDKs, we implement it using the cxram section if needed. */ -static size_t cx_hash_ripemd160(const uint8_t *in, size_t in_len, uint8_t *out, size_t out_len) { - PRINT_STACK_POINTER(); - - if (out_len < CX_RIPEMD160_SIZE) { - return 0; - } - LEDGER_ASSERT(cx_ripemd160_init_no_throw((cx_ripemd160_t *) &G_cx) == CX_OK, "It never fails"); - LEDGER_ASSERT(cx_ripemd160_update((cx_ripemd160_t *) &G_cx, in, in_len) == CX_OK, - "It never fails"); - LEDGER_ASSERT(cx_ripemd160_final((cx_ripemd160_t *) &G_cx, out) == CX_OK, "It never fails"); - explicit_bzero((cx_ripemd160_t *) &G_cx, sizeof(cx_sha256_t)); - return CX_RIPEMD160_SIZE; -} -#endif // _NR_cx_hash_ripemd160 - void crypto_ripemd160(const uint8_t *in, uint16_t inlen, uint8_t out[static 20]) { - cx_hash_ripemd160(in, inlen, out, 20); + int res = cx_ripemd160_hash(in, inlen, out); + LEDGER_ASSERT(res == CX_OK, "Unexpected error in ripemd160 computation. Returned: %d", res); } void crypto_hash160(const uint8_t *in, uint16_t inlen, uint8_t out[static 20]) { PRINT_STACK_POINTER(); uint8_t buffer[32]; - cx_hash_sha256(in, inlen, buffer, 32); + int res = cx_hash_sha256(in, inlen, buffer, 32); + LEDGER_ASSERT(res == CX_SHA256_SIZE, + "Unexpected error in sha256 computation. Returned: %d", + res); crypto_ripemd160(buffer, 32, out); } @@ -278,37 +266,22 @@ bool crypto_get_compressed_pubkey_at_path(const uint32_t bip32_path[], uint8_t bip32_path_len, uint8_t pubkey[static 33], uint8_t chain_code[]) { - struct { - uint8_t prefix; - uint8_t raw_public_key[64]; - uint8_t chain_code[32]; - } keydata; - - cx_ecfp_private_key_t private_key = {0}; - cx_ecfp_public_key_t public_key; + uint8_t raw_public_key[65]; - bool ok = true; - - keydata.prefix = 0x04; // uncompressed public keys always start with 04 - // derive private key according to BIP32 path - ok = ok && 0 == crypto_derive_private_key(&private_key, keydata.chain_code, bip32_path, bip32_path_len); - - if (ok && chain_code != NULL) { - memmove(chain_code, keydata.chain_code, 32); + if (bip32_derive_get_pubkey_256(CX_CURVE_256K1, + bip32_path, + bip32_path_len, + raw_public_key, + chain_code, + CX_SHA512) != CX_OK) { + return false; } - // generate corresponding public key - ok = ok && CX_OK == cx_ecfp_generate_pair_no_throw(CX_CURVE_256K1, &public_key, &private_key, 1); - memmove(keydata.raw_public_key, public_key.W + 1, sizeof(keydata.raw_public_key)); - - // compute compressed public key - ok = ok && 0 == crypto_get_compressed_pubkey((uint8_t *) &keydata, pubkey); - - // delete sensitive data - explicit_bzero(keydata.chain_code, 32); - explicit_bzero(&private_key, sizeof(private_key)); + if (crypto_get_compressed_pubkey(raw_public_key, pubkey) < 0) { + return false; + } - return ok; + return true; } uint32_t crypto_get_key_fingerprint(const uint8_t pub_key[static 33]) { @@ -332,7 +305,7 @@ bool crypto_derive_symmetric_key(const char *label, size_t label_len, uint8_t ke // is not aligned. uint8_t label_copy[32] __attribute__((aligned(4))); - memcpy(label_copy, label, MIN(label_len, sizeof(label_copy))); + memcpy(label_copy, label, label_len); uint8_t raw_privkey[64]; // Needed because in the new SDK derived key could be up to 64 bytes long bool res = CX_OK == os_derive_bip32_with_seed_no_throw(HDW_SLIP21, @@ -350,13 +323,10 @@ bool crypto_derive_symmetric_key(const char *label, size_t label_len, uint8_t ke return res; } -// TODO: Split serialization from key derivation? -// It might be difficult to have a clean API without wasting memory, as the checksum -// needs to be concatenated to the data before base58 serialization. -int get_serialized_extended_pubkey_at_path(const uint32_t bip32_path[], - uint8_t bip32_path_len, - uint32_t bip32_pubkey_version, - char out[static MAX_SERIALIZED_PUBKEY_LENGTH + 1]) { +int get_extended_pubkey_at_path(const uint32_t bip32_path[], + uint8_t bip32_path_len, + uint32_t bip32_pubkey_version, + serialized_extended_pubkey_t *out_pubkey) { // find parent key's fingerprint and child number uint32_t parent_fingerprint = 0; uint32_t child_number = 0; @@ -365,7 +335,10 @@ int get_serialized_extended_pubkey_at_path(const uint32_t bip32_path[], // for the response, in order to save memory uint8_t parent_pubkey[33]; - if (!crypto_get_compressed_pubkey_at_path(bip32_path, bip32_path_len - 1, parent_pubkey, NULL)) { + if (!crypto_get_compressed_pubkey_at_path(bip32_path, + bip32_path_len - 1, + parent_pubkey, + NULL)) { return -1; } @@ -373,33 +346,19 @@ int get_serialized_extended_pubkey_at_path(const uint32_t bip32_path[], child_number = bip32_path[bip32_path_len - 1]; } - struct { - serialized_extended_pubkey_t ext_pubkey; - uint8_t checksum[4]; - } ext_pubkey_check; // extended pubkey and checksum - - serialized_extended_pubkey_t *ext_pubkey = &ext_pubkey_check.ext_pubkey; - - write_u32_be(ext_pubkey->version, 0, bip32_pubkey_version); - ext_pubkey->depth = bip32_path_len; - write_u32_be(ext_pubkey->parent_fingerprint, 0, parent_fingerprint); - write_u32_be(ext_pubkey->child_number, 0, child_number); + write_u32_be(out_pubkey->version, 0, bip32_pubkey_version); + out_pubkey->depth = bip32_path_len; + write_u32_be(out_pubkey->parent_fingerprint, 0, parent_fingerprint); + write_u32_be(out_pubkey->child_number, 0, child_number); if (!crypto_get_compressed_pubkey_at_path(bip32_path, bip32_path_len, - ext_pubkey->compressed_pubkey, - ext_pubkey->chain_code)) { + out_pubkey->compressed_pubkey, + out_pubkey->chain_code)) { return -1; } - crypto_get_checksum((uint8_t *) ext_pubkey, 78, ext_pubkey_check.checksum); - int serialized_pubkey_len = - base58_encode((uint8_t *) &ext_pubkey_check, 78 + 4, out, MAX_SERIALIZED_PUBKEY_LENGTH); - - if (serialized_pubkey_len > 0) { - out[serialized_pubkey_len] = '\0'; - } - return serialized_pubkey_len; + return 0; } int base58_encode_address(const uint8_t in[20], uint32_t version, char *out, size_t out_len) { @@ -423,29 +382,56 @@ int base58_encode_address(const uint8_t in[20], uint32_t version, char *out, siz } int crypto_ecdsa_sign_sha256_hash_with_key(const uint32_t bip32_path[], - size_t bip32_path_len, + uint8_t bip32_path_len, const uint8_t hash[static 32], + uint8_t *pubkey, uint8_t out[static MAX_DER_SIG_LEN], uint32_t *info) { cx_ecfp_private_key_t private_key = {0}; - uint8_t chain_code[32] = {0}; + cx_ecfp_public_key_t public_key; uint32_t info_internal = 0; size_t sig_len = MAX_DER_SIG_LEN; + bool error = true; + + if (bip32_derive_init_privkey_256(CX_CURVE_256K1, + bip32_path, + bip32_path_len, + &private_key, + NULL) != CX_OK) { + goto end; + } + + if (cx_ecdsa_sign_no_throw(&private_key, + CX_RND_RFC6979, + CX_SHA256, + hash, + 32, + out, + &sig_len, + &info_internal) != CX_OK) { + goto end; + } + + if (pubkey != NULL) { + // Generate associated pubkey + if (cx_ecfp_generate_pair_no_throw(CX_CURVE_256K1, &public_key, &private_key, true) != + CX_OK) { + goto end; + } + + // compute compressed public key + if (crypto_get_compressed_pubkey(public_key.W, pubkey) < 0) { + goto end; + } + } - bool ok = 0 == crypto_derive_private_key(&private_key, chain_code, bip32_path, bip32_path_len); - ok = ok && CX_OK == cx_ecdsa_sign_no_throw(&private_key, - CX_RND_RFC6979, - CX_SHA256, - hash, - 32, - out, - &sig_len, - &info_internal); + error = false; +end: explicit_bzero(&private_key, sizeof(private_key)); - if (!ok) { + if (error) { // unexpected error when signing return -1; } @@ -454,7 +440,7 @@ int crypto_ecdsa_sign_sha256_hash_with_key(const uint32_t bip32_path[], *info = info_internal; } - return (int)sig_len; + return sig_len; } void crypto_tr_tagged_hash_init(cx_sha256_t *hash_context, const uint8_t *tag, uint16_t tag_len) { @@ -470,18 +456,8 @@ void crypto_tr_tagged_hash_init(cx_sha256_t *hash_context, const uint8_t *tag, u crypto_hash_update(&hash_context->header, hashtag, sizeof(hashtag)); } -static void crypto_tr_tagged_hash(const uint8_t *tag, - uint16_t tag_len, - const uint8_t *data, - uint16_t data_len, - uint8_t out[static 32]) { - cx_sha256_t hash_context; - cx_sha256_init(&hash_context); - - crypto_tr_tagged_hash_init(&hash_context, tag, tag_len); - - crypto_hash_update(&hash_context.header, data, data_len); - crypto_hash_digest(&hash_context.header, out, 32); +void crypto_tr_tapleaf_hash_init(cx_sha256_t *hash_context) { + crypto_tr_tagged_hash_init(hash_context, BIP0341_tapleaf_tag, sizeof(BIP0341_tapleaf_tag)); } static int crypto_tr_lift_x(const uint8_t x[static 32], uint8_t out[static 65]) { @@ -493,29 +469,25 @@ static int crypto_tr_lift_x(const uint8_t x[static 32], uint8_t out[static 65]) // c = x^3 (mod p) uint8_t e = 3; if (CX_OK != cx_math_powm_no_throw(c, x, &e, 1, secp256k1_p, 32)) { - return -1; + return -1; // c = x^3 (mod p) } - - // c = x^3 + 7 (mod p) uint8_t scalar[32] = {0}; scalar[31] = 7; if (CX_OK != cx_math_addm_no_throw(c, c, scalar, secp256k1_p, 32)) { - return -1; + return -1; // c = x^3 + 7 (mod p) } - // y = sqrt(x^3 + 7) (mod p) if (CX_OK != cx_math_powm_no_throw(y, c, secp256k1_sqr_exponent, 32, secp256k1_p, 32)) { - return -1; + return -1; // y = sqrt(x^3 + 7) (mod p) } // sanity check: fail if y * y % p != x^3 + 7 uint8_t y_2[32]; e = 2; - if (CX_OK != cx_math_powm_no_throw(y_2, y, &e, 1, secp256k1_p, 32)) { // y^2 (mod p) - return -1; + if (CX_OK != cx_math_powm_no_throw(y_2, y, &e, 1, secp256k1_p, 32)) { + return -1; // y^2 (mod p) } - - int diff = 1; + int diff; if (CX_OK != cx_math_cmp_no_throw(y_2, c, 32, &diff) || diff != 0) { return -1; } @@ -534,12 +506,66 @@ static int crypto_tr_lift_x(const uint8_t x[static 32], uint8_t out[static 65]) return 0; } -// Like taproot_tweak_pubkey of BIP0341, with empty string h -// TODO: should it recycle pubkey also for the output (like crypto_tr_tweak_seckey below)? -int crypto_tr_tweak_pubkey(uint8_t pubkey[static 32], uint8_t *y_parity, uint8_t out[static 32]) { +// Computes a tagged hash according to BIP-340. +// If data2_len > 0, then data2 must be non-NULL and the `data` and `data2` arrays are concatenated. +static void crypto_tr_tagged_hash(const uint8_t *tag, + uint16_t tag_len, + const uint8_t *data, + uint16_t data_len, + const uint8_t *data2, + uint16_t data2_len, + uint8_t out[static CX_SHA256_SIZE]) { + // First compute hashtag, reuse out buffer for that + cx_sha256_hash(tag, tag_len, out); + + cx_iovec_t iovec[4] = {{.iov_base = out, .iov_len = CX_SHA256_SIZE}, + {.iov_base = out, .iov_len = CX_SHA256_SIZE}, + {.iov_base = data, .iov_len = data_len}, + {.iov_base = data2, .iov_len = data2_len}}; + if (data2_len > 0) { + cx_sha256_hash_iovec(iovec, 4, out); + } else { + cx_sha256_hash_iovec(iovec, 3, out); + } +} + +void crypto_tr_combine_taptree_hashes(const uint8_t left_h[static 32], + const uint8_t right_h[static 32], + uint8_t out[static 32]) { + if (memcmp(left_h, right_h, 32) < 0) { + crypto_tr_tagged_hash(BIP0341_tapbranch_tag, + sizeof(BIP0341_tapbranch_tag), + left_h, + 32, + right_h, + 32, + out); + } else { + crypto_tr_tagged_hash(BIP0341_tapbranch_tag, + sizeof(BIP0341_tapbranch_tag), + right_h, + 32, + left_h, + 32, + out); + } +} + +// Like taproot_tweak_pubkey of BIP0341 +int crypto_tr_tweak_pubkey(const uint8_t pubkey[static 32], + const uint8_t *h, + size_t h_len, + uint8_t *y_parity, + uint8_t out[static 32]) { uint8_t t[32]; - crypto_tr_tagged_hash(BIP0341_taptweak_tag, sizeof(BIP0341_taptweak_tag), pubkey, 32, t); + crypto_tr_tagged_hash(BIP0341_taptweak_tag, + sizeof(BIP0341_taptweak_tag), + pubkey, + 32, + h, + h_len, + t); // fail if t is not smaller than the curve order int diff = 1; @@ -568,67 +594,55 @@ int crypto_tr_tweak_pubkey(uint8_t pubkey[static 32], uint8_t *y_parity, uint8_t return 0; } -// Like taproot_tweak_seckey of BIP0341, with empty string h -int crypto_tr_tweak_seckey(uint8_t seckey[static 32]) { +// Like taproot_tweak_seckey of BIP0341 +int crypto_tr_tweak_seckey(const uint8_t seckey[static 32], + const uint8_t *h, + size_t h_len, + uint8_t out[static 32]) { uint8_t P[65]; - bool ok = 0 == secp256k1_point(seckey, P); - - if (P[64] & 1) { - // odd y, negate the secret key - ok = ok && CX_OK == cx_math_sub_no_throw(seckey, secp256k1_n, seckey, 32); - } - - uint8_t t[32]; - crypto_tr_tagged_hash(BIP0341_taptweak_tag, - sizeof(BIP0341_taptweak_tag), - &P[1], // P[1:33] is x(P) - 32, - t); - - // fail if t is not smaller than the curve order - int diff = 1; - ok = ok && CX_OK == cx_math_cmp_no_throw(t, secp256k1_n, 32, &diff); - ok = ok && diff < 0; - - ok = ok && CX_OK == cx_math_addm_no_throw(seckey, seckey, t, secp256k1_n, 32); - explicit_bzero(&P, sizeof(P)); + int ret = -1; + do { // loop to break out in case of error + if (0 > secp256k1_point(seckey, P)) { + break; + } - return ok ? 0 : -1; -} + memmove(out, seckey, 32); -bool crypto_generate_compressed_pubkey_pair(const uint8_t privkey[static 32], - uint8_t pubkey[static 33]) { - cx_ecfp_private_key_t privkey_inst; - cx_ecfp_public_key_t pubkey_inst; + if (P[64] & 1) { + // odd y, negate the secret key + if (CX_OK != cx_math_sub_no_throw(out, secp256k1_n, out, 32)) { + break; + } + } - // New private key instance from private key - bool ok = CX_OK == cx_ecfp_init_private_key_no_throw(CX_CURVE_256K1, - privkey, - 32, - &privkey_inst); + uint8_t t[32]; + crypto_tr_tagged_hash(BIP0341_taptweak_tag, + sizeof(BIP0341_taptweak_tag), + &P[1], // P[1:33] is x(P) + 32, + h, + h_len, + t); + + // fail if t is not smaller than the curve order + int diff; + if (CX_OK != cx_math_cmp_no_throw(t, secp256k1_n, 32, &diff) || diff >= 0) { + break; + } - // Generate corresponding public key - ok = ok && CX_OK == cx_ecfp_generate_pair_no_throw(CX_CURVE_256K1, - &pubkey_inst, - &privkey_inst, - 1); + if (CX_OK != cx_math_addm_no_throw(out, out, t, secp256k1_n, 32)) { + break; + } - // Save produced public key in compressed format - if (ok) { - pubkey[0] = ((pubkey_inst.W[64] & 1) ? 0x03 : 0x02); - memcpy(pubkey + 1, pubkey_inst.W + 1, 32); - } + ret = 0; + } while (0); - // Zeroize sensitive data - explicit_bzero(&privkey_inst, sizeof(privkey_inst)); - explicit_bzero(&pubkey_inst, sizeof(pubkey_inst)); + explicit_bzero(&P, sizeof(P)); - return ok; + return ret; } -#endif // SKIP_FOR_CMOCKA - /***************************************************************************** * FUNTIONS COVERED BY CMOCKA UNIT TESTS *****************************************************************************/ @@ -636,8 +650,15 @@ bool crypto_generate_compressed_pubkey_pair(const uint8_t privkey[static 32], // TODO: missing unit tests void crypto_get_checksum(const uint8_t *in, uint16_t in_len, uint8_t out[static 4]) { uint8_t buffer[32]; - cx_hash_sha256(in, in_len, buffer, 32); - cx_hash_sha256(buffer, 32, buffer, 32); + size_t res; + res = cx_hash_sha256(in, in_len, buffer, 32); + LEDGER_ASSERT(res == CX_SHA256_SIZE, + "Unexpected error in sha256 computation. Returned: %d", + res); + res = cx_hash_sha256(buffer, 32, buffer, 32); + LEDGER_ASSERT(res == CX_SHA256_SIZE, + "Unexpected error in sha256 computation. Returned: %d", + res); memmove(out, buffer, 4); } diff --git a/src/crypto.h b/src/crypto.h index 23b8862fa..d0d1bc088 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -104,7 +104,8 @@ WARN_UNUSED_RESULT int crypto_derive_private_key(cx_ecfp_private_key_t *private_ uint8_t bip32_path_len); /** - * Initialize public key given private key. + * Generates the child extended public key, from a parent extended public key and non-hardened + * index. * * @param[in] parent * Pointer to the extended serialized pubkey of the parent. @@ -122,7 +123,7 @@ WARN_UNUSED_RESULT int bip32_CKDpub(const serialized_extended_pubkey_t *parent, serialized_extended_pubkey_t *child); /** - * Convenience wrapper for cx_hash to add some data to an initialized hash context. + * Convenience wrapper for cx_hash_no_throw to add some data to an initialized hash context. * * @param[in] hash_context * The context of the hash, which must already be initialized. @@ -132,12 +133,12 @@ WARN_UNUSED_RESULT int bip32_CKDpub(const serialized_extended_pubkey_t *parent, * Size of the passed data. */ static inline void crypto_hash_update(cx_hash_t *hash_context, const void *in, size_t in_len) { - int ret = cx_hash_no_throw(hash_context, 0, in, in_len, NULL, 0); - LEDGER_ASSERT(CX_OK == ret, "It should not fail"); + int res = cx_hash_no_throw(hash_context, 0, in, in_len, NULL, 0); + LEDGER_ASSERT(res == CX_OK, "Unexpected error in sha256 computation. Returned: %d", res); } /** - * Convenience wrapper for cx_hash to compute the final hash, without adding any extra data + * Convenience wrapper for cx_hash_no_throw to compute the final hash, without adding any extra data * to the hash context. * * @param[in] hash_context @@ -148,8 +149,8 @@ static inline void crypto_hash_update(cx_hash_t *hash_context, const void *in, s * Size of output buffer, which must be large enough to contain the result. */ static inline void crypto_hash_digest(cx_hash_t *hash_context, uint8_t *out, size_t out_len) { - int ret = cx_hash_no_throw(hash_context, CX_LAST, NULL, 0, out, out_len); - LEDGER_ASSERT(CX_OK == ret, "It should not fail"); + int res = cx_hash_no_throw(hash_context, CX_LAST, NULL, 0, out, out_len); + LEDGER_ASSERT(res == CX_OK, "Unexpected error in sha256 computation. Returned: %d", res); } /** @@ -339,7 +340,7 @@ uint32_t crypto_get_key_fingerprint(const uint8_t pub_key[static 33]); uint32_t crypto_get_master_key_fingerprint(void); /** - * Computes the base58check-encoded extended pubkey at a given path. + * Computes extended pubkey at a given path, serialized as per BIP32. * * @param[in] bip32_path * Pointer to 32-bit array of BIP-32 derivation steps. @@ -347,20 +348,18 @@ uint32_t crypto_get_master_key_fingerprint(void); * Number of steps in the BIP32 derivation. * @param[in] bip32_pubkey_version * Version prefix to use for the pubkey. - * @param[out] out - * Pointer to the output buffer, which must be long enough to contain the result (including the - * terminating null). + * @param[out] out_pubkey + * A pointer to a serialized_extended_pubkey_t. * - * @return the length of the output pubkey (not including the null character), or -1 on error. + * @return 0 on success, or -1 on error. */ -WARN_UNUSED_RESULT int get_serialized_extended_pubkey_at_path(const uint32_t bip32_path[], - uint8_t bip32_path_len, - uint32_t bip32_pubkey_version, - char out[static MAX_SERIALIZED_PUBKEY_LENGTH + 1]); +WARN_UNUSED_RESULT int get_extended_pubkey_at_path(const uint32_t bip32_path[], + uint8_t bip32_path_len, + uint32_t bip32_pubkey_version, + serialized_extended_pubkey_t *out_pubkey); /** * Derives the level-1 symmetric key at the given label using SLIP-0021. - * Must be wrapped in a TRY/FINALLY block to make sure that the output key is wiped after using it. * * @param[in] label * Pointer to the label. The first byte of the label must be 0x00 to comply with SLIP-0021. @@ -404,6 +403,9 @@ WARN_UNUSED_RESULT int base58_encode_address(const uint8_t in[20], uint32_t vers * Number of steps in the BIP32 derivation. * @param[in] hash * Pointer to a 32-byte SHA-256 hash digest. + * @param[out] pubkey + * Either NULL, or a pointer to a 33-bytes array that will receive the compressed pubkey + * corresponding to the private key used for signing. * @param[out] out * The pointer to the output array to contain the signature, that must be of length * `MAX_DER_SIG_LEN`. @@ -413,8 +415,9 @@ WARN_UNUSED_RESULT int base58_encode_address(const uint8_t in[20], uint32_t vers * @return the length of the signature on success, or -1 in case of error. */ WARN_UNUSED_RESULT int crypto_ecdsa_sign_sha256_hash_with_key(const uint32_t bip32_path[], - size_t bip32_path_len, + uint8_t bip32_path_len, const uint8_t hash[static 32], + uint8_t *pubkey, uint8_t out[static MAX_DER_SIG_LEN], uint32_t *info); @@ -422,7 +425,7 @@ WARN_UNUSED_RESULT int crypto_ecdsa_sign_sha256_hash_with_key(const uint32_t bip * Initializes the "tagged" SHA256 hash with the given tag, as defined by BIP-0340. * * @param[out] hash_context - * Pointer to 32-bit array of BIP-32 derivation steps. + * Pointer to a sha256 hash context. * @param[in] tag * Pointer to an array containing the tag of the tagged hash. * @param[in] tag_len @@ -431,11 +434,37 @@ WARN_UNUSED_RESULT int crypto_ecdsa_sign_sha256_hash_with_key(const uint32_t bip void crypto_tr_tagged_hash_init(cx_sha256_t *hash_context, const uint8_t *tag, uint16_t tag_len); /** - * Builds a tweaked public key from a BIP340 public key array. + * Initializes the "tagged" SHA256 hash with tag "TapLeaf", used for tapscript leaves. + * + * @param[out] hash_context + * Pointer to a sha256 hash context. + */ +void crypto_tr_tapleaf_hash_init(cx_sha256_t *hash_context); + +/** + * Computes the tagged hash with tagged hash of a tapbranch, given the hashes for the children. + * + * @param[in] left_h + * The hash of the left tapbranch/tapleaf. + * @param[in] right_h + * The hash of the right tapbranch/tapleaf. + * @param[out] out + * The combined hash for the tapbranch. + */ +void crypto_tr_combine_taptree_hashes(const uint8_t left_h[static 32], + const uint8_t right_h[static 32], + uint8_t out[static 32]); + +/** + * Computes the tweaked public key from a BIP340 public key array. * Implementation of taproot_tweak_pubkey of BIP341 with `h` set to the empty byte string. * * @param[in] pubkey * Pointer to the 32-byte to be used as public key. + * @param[in] h + * Pointer to the tweaking data. + * @param[in] h_len + * Length of `h`. * @param[out] y_parity * Pointer to a variable that will be set to 0/1 according to the parity of th y-coordinate of the * final tweaked pubkey. @@ -444,19 +473,31 @@ void crypto_tr_tagged_hash_init(cx_sha256_t *hash_context, const uint8_t *tag, u * * @return 0 on success, or -1 in case of error. */ -WARN_UNUSED_RESULT int crypto_tr_tweak_pubkey(uint8_t pubkey[static 32], uint8_t *y_parity, uint8_t out[static 32]); +WARN_UNUSED_RESULT int crypto_tr_tweak_pubkey(const uint8_t pubkey[static 32], + const uint8_t *h, + size_t h_len, + uint8_t *y_parity, + uint8_t out[static 32]); /** - * Builds a tweaked public key from a BIP340 public key array. + * Computes the tweaked secret key from a BIP340 secret key. * Implementation of taproot_tweak_seckey of BIP341 with `h` set to the empty byte string. * - * @param[in|out] seckey - * Pointer to the 32-byte containing the secret key; it will contain the output tweaked secret - * key. + * @param[in] seckey + * Pointer to the 32-byte containing the secret key. + * @param[out] h + * Pointer to the tweaking data. + * @param[out] h_len + * Length of `h`. + * @param[out] out + * Pointer to the a 32-byte array that will contain the tweaked secret key. * * @return 0 on success, or -1 in case of error. */ -WARN_UNUSED_RESULT int crypto_tr_tweak_seckey(uint8_t seckey[static 32]); +WARN_UNUSED_RESULT int crypto_tr_tweak_seckey(const uint8_t seckey[static 32], + const uint8_t *h, + size_t h_len, + uint8_t out[static 32]); /** * Validates the Base58Check-encoded extended pubkey at a given path. diff --git a/src/cxram_stash.c b/src/cxram_stash.c index bd61a7aa7..1f3d819b3 100644 --- a/src/cxram_stash.c +++ b/src/cxram_stash.c @@ -3,14 +3,13 @@ #include "cxram_stash.h" #include "cx_ram.h" +#ifdef USE_CXRAM_SECTION #ifndef G_cx // The G_cx symbol is only defined in the sdk if compiled with certain libs are included. // This makes sure that the symbol exists nonetheless. union cx_u G_cx; #endif -#ifdef USE_CXRAM_SECTION - uint8_t *get_cxram_buffer() { return (uint8_t *) &G_cx; } diff --git a/src/debug-helpers/debug.c b/src/debug-helpers/debug.c index c8b92d5b3..127636f5e 100644 --- a/src/debug-helpers/debug.c +++ b/src/debug-helpers/debug.c @@ -72,13 +72,12 @@ int semihosted_printf(const char *format, ...) { } // Returns the current stack pointer -static unsigned int __attribute__((noinline, unused)) get_stack_pointer() { - int stack_top = 0; - // Returning an address on the stack is unusual, so we disable the warning -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wreturn-stack-address" - return (unsigned int) &stack_top; -#pragma GCC diagnostic pop +static unsigned int __attribute__((noinline)) get_stack_pointer() { + unsigned int stack_top = 0; + + __asm__ __volatile__("mov %0, sp" : "=r"(stack_top) : :); + + return stack_top; } #ifdef HAVE_BOLOS_APP_STACK_CANARY diff --git a/src/debug-helpers/debug.h b/src/debug-helpers/debug.h index 1ded1253a..217e4c1f7 100644 --- a/src/debug-helpers/debug.h +++ b/src/debug-helpers/debug.h @@ -1,5 +1,15 @@ #pragma once +#include "os.h" + +// Workaround +#ifdef HAVE_SEMIHOSTED_PRINTF + #ifdef PRINTF + #undef PRINTF + #endif + #define PRINTF semihosted_printf +#endif // HAVE_SEMIHOSTED_PRINTF + void debug_write(const char *buf); void debug_write_hex(unsigned int word, unsigned int bytes); void debug_write_dec(unsigned int word); @@ -38,3 +48,18 @@ void print_strn(const char *msg, const char *str, int len); #define PRINT_STR(msg, str) #define PRINT_STRN(msg, str, len) #endif // HAVE_PRINTF + + +static inline int print_error_info(const char *error_msg, + const char *filename, + int line, + int retval) { + (void) error_msg; + (void) filename; + (void) line; + + PRINTF("ERR (%s::%d): %s\n", filename, line, error_msg); + return retval; +} + +#define WITH_ERROR(retval, error_msg) print_error_info(error_msg, __FILE__, __LINE__, retval) \ No newline at end of file diff --git a/src/globals.h b/src/globals.h index 152d737e3..ede4e836c 100644 --- a/src/globals.h +++ b/src/globals.h @@ -27,8 +27,3 @@ extern ux_state_t G_ux; * Global structure with the parameters to exchange with the BOLOS UX application. */ extern bolos_ux_params_t G_ux_params; - -/** - * State of the current APDU interaction, if any. - */ -extern command_state_t G_command_state; diff --git a/src/handler/get_extended_pubkey.c b/src/handler/get_extended_pubkey.c index 34bace015..536d247ea 100644 --- a/src/handler/get_extended_pubkey.c +++ b/src/handler/get_extended_pubkey.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,38 +20,24 @@ #include "boilerplate/io.h" #include "boilerplate/dispatcher.h" #include "boilerplate/sw.h" +#include "../common/base58.h" +#include "../common/bip32.h" #include "../commands.h" #include "../constants.h" #include "../crypto.h" #include "../ui/display.h" #include "../ui/menu.h" -/** - * Sends response APDU. - * - * @param[in,out] dc - * Dispatcher context. - */ -static void send_response(dispatcher_context_t *dc); +#define H 0x80000000ul + +static bool is_path_safe_for_pubkey_export(const uint32_t bip32_path[], size_t bip32_path_len) { + // Exception for Electrum: it historically used "m/4541509h/1112098098h" + // to derive encryption keys, so we whitelist it. + if (bip32_path_len == 2 && bip32_path[0] == (4541509 ^ H) && + bip32_path[1] == (1112098098 ^ H)) { + return true; + } -/** - * Checks if requested path is safe to export derived public key. - * - * @param bip32_path - * Derivation path. - * @param bip32_path_len - * Length of derivation path (number of 32-bit elements). - * @param coin_types - * List of coin types this application supports. - * @param coin_types_length - * Number of elements in the coin type list. - * - * @return true if export allowed, false otherwise. - */ -static bool is_path_safe_for_pubkey_export(const uint32_t bip32_path[], - size_t bip32_path_len, - const uint32_t coin_types[], - size_t coin_types_length) { if (bip32_path_len < 3) { return false; } @@ -66,6 +52,11 @@ static bool is_path_safe_for_pubkey_export(const uint32_t bip32_path[], case 86: hardened_der_len = 3; break; + case 45: + // BIP-45 prescribes simply length 1, but we instead support existing deployed + // use cases with path "m/45'/coin_type'/account' + hardened_der_len = 3; + break; case 48: hardened_der_len = 4; break; @@ -73,9 +64,9 @@ static bool is_path_safe_for_pubkey_export(const uint32_t bip32_path[], return false; } - // bip32_path_len should be either hardened_der_len, or just two more - // for change and address_index - if (bip32_path_len != hardened_der_len && bip32_path_len != hardened_der_len + 2) { + // bip32_path_len should be at least the hardened_der_len + // (but it could have additional unhardened derivation steps) + if (bip32_path_len < hardened_der_len) { return false; } @@ -92,14 +83,7 @@ static bool is_path_safe_for_pubkey_export(const uint32_t bip32_path[], } uint32_t coin_type = bip32_path[1] & 0x7FFFFFFF; - bool coin_type_found = false; - for (unsigned int i = 0; i < coin_types_length; i++) { - if (coin_type == coin_types[i]) { - coin_type_found = true; - } - } - - if (!coin_type_found) { + if (coin_type != BIP44_COIN_TYPE) { return false; } @@ -110,19 +94,6 @@ static bool is_path_safe_for_pubkey_export(const uint32_t bip32_path[], return false; } - if (bip32_path_len > hardened_der_len) { - uint32_t change = bip32_path[bip32_path_len - 2]; - uint32_t address_index = bip32_path[bip32_path_len - 1]; - - if (change != 0 && change != 1) { - return false; - } - - if (address_index > MAX_BIP44_ADDRESS_INDEX_RECOMMENDED) { - return false; - } - } - // For BIP48, there is also the script type, with only standardized values 1' and 2' if (purpose == 48) { uint32_t script_type = bip32_path[3] & 0x7FFFFFFF; @@ -134,16 +105,10 @@ static bool is_path_safe_for_pubkey_export(const uint32_t bip32_path[], return true; } -void handler_get_extended_pubkey(dispatcher_context_t *dc) { - get_extended_pubkey_state_t *state = (get_extended_pubkey_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); +void handler_get_extended_pubkey(dispatcher_context_t *dc, uint8_t protocol_version) { + (void) protocol_version; - // Device must be unlocked - if (os_global_pin_is_validated() != BOLOS_UX_OK) { - SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); - return; - } + LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t display; uint8_t bip32_path_len; @@ -164,41 +129,48 @@ void handler_get_extended_pubkey(dispatcher_context_t *dc) { return; } - uint32_t coin_types[2] = {BIP44_COIN_TYPE, BIP44_COIN_TYPE_2}; - bool is_safe = is_path_safe_for_pubkey_export(bip32_path, bip32_path_len, coin_types, 2); + bool is_safe = is_path_safe_for_pubkey_export(bip32_path, bip32_path_len); if (!is_safe && !display) { SEND_SW(dc, SW_NOT_SUPPORTED); return; } - int serialized_pubkey_len = - get_serialized_extended_pubkey_at_path(bip32_path, - bip32_path_len, - BIP32_PUBKEY_VERSION, - state->serialized_pubkey_str); - if (serialized_pubkey_len == -1) { + serialized_extended_pubkey_check_t pubkey_check; + if (0 > get_extended_pubkey_at_path(bip32_path, + bip32_path_len, + BIP32_PUBKEY_VERSION, + &pubkey_check.serialized_extended_pubkey)) { + PRINTF("Failed getting bip32 pubkey\n"); SEND_SW(dc, SW_BAD_STATE); return; } + crypto_get_checksum((uint8_t *) &pubkey_check.serialized_extended_pubkey, + sizeof(pubkey_check.serialized_extended_pubkey), + pubkey_check.checksum); + + char pubkey_str[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; + int pubkey_str_len = base58_encode((uint8_t *) &pubkey_check, + sizeof(pubkey_check), + pubkey_str, + sizeof(pubkey_str)); + if (pubkey_str_len != 111 && pubkey_str_len != 112) { + PRINTF("Failed encoding base58 pubkey\n"); + SEND_SW(dc, SW_BAD_STATE); + return; + } + pubkey_str[pubkey_str_len] = 0; + char path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1] = "(Master key)"; if (bip32_path_len > 0) { bip32_path_format(bip32_path, bip32_path_len, path_str, sizeof(path_str)); } - if (display) { - ui_display_pubkey(dc, path_str, !is_safe, state->serialized_pubkey_str, send_response); - } else { - dc->next(send_response); + if (display && !ui_display_pubkey(dc, path_str, !is_safe, pubkey_str)) { + SEND_SW(dc, SW_DENY); + return; } -} - -static void send_response(dispatcher_context_t *dc) { - get_extended_pubkey_state_t *state = (get_extended_pubkey_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - size_t pubkey_len = strnlen(state->serialized_pubkey_str, sizeof(state->serialized_pubkey_str) - 1); - SEND_RESPONSE(dc, state->serialized_pubkey_str, pubkey_len, SW_OK); + SEND_RESPONSE(dc, pubkey_str, pubkey_str_len, SW_OK); } diff --git a/src/handler/get_extended_pubkey.h b/src/handler/get_extended_pubkey.h deleted file mode 100644 index 9f5ab230d..000000000 --- a/src/handler/get_extended_pubkey.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "../common/bip32.h" -#include "../boilerplate/dispatcher.h" - -/// State of GET_EXTENDED_PUBKEY handler -typedef struct { - /// Machine context for command dispatcher - machine_context_t ctx; - /// Serialized public key - char serialized_pubkey_str[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; -} get_extended_pubkey_state_t; - -/** - * Handles GET_EXTENDED_PUBKEY command. - * - * @param[in,out] dispatcher_context - * Dispatcher context. - */ -void handler_get_extended_pubkey(dispatcher_context_t *dispatcher_context); diff --git a/src/handler/get_master_fingerprint.c b/src/handler/get_master_fingerprint.c index 0af716640..e1993c390 100644 --- a/src/handler/get_master_fingerprint.c +++ b/src/handler/get_master_fingerprint.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,10 @@ #include "../commands.h" #include "../crypto.h" -#include "get_master_fingerprint.h" +#include "handlers.h" -void handler_get_master_fingerprint(dispatcher_context_t *dc) { - // Device must be unlocked - if (os_global_pin_is_validated() != BOLOS_UX_OK) { - SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); - return; - } +void handler_get_master_fingerprint(dispatcher_context_t *dc, uint8_t protocol_version) { + (void) protocol_version; uint8_t master_pubkey[33]; if (!crypto_get_compressed_pubkey_at_path((uint32_t[]){}, 0, master_pubkey, NULL)) { diff --git a/src/handler/get_master_fingerprint.h b/src/handler/get_master_fingerprint.h deleted file mode 100644 index 258f07dac..000000000 --- a/src/handler/get_master_fingerprint.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "../boilerplate/dispatcher.h" - -/// State of GET_MASTER_FINGERPRINT handler -typedef struct { - /// Machine context for command dispatcher - machine_context_t ctx; -} get_master_fingerprint_t; - -/** - * Handles GET_MASTER_FINGERPRINT command. - * - * @param[in,out] dispatcher_context - * Dispatcher context. - */ -void handler_get_master_fingerprint(dispatcher_context_t *dispatcher_context); diff --git a/src/handler/get_wallet_address.c b/src/handler/get_wallet_address.c index 348aa8cb2..0828f6c29 100644 --- a/src/handler/get_wallet_address.c +++ b/src/handler/get_wallet_address.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ #include "boilerplate/io.h" #include "boilerplate/sw.h" #include "../common/base58.h" +#include "../common/bip32.h" #include "../common/buffer.h" #include "../common/merkle.h" #include "../common/read.h" @@ -33,150 +34,88 @@ #include "../ui/display.h" #include "../ui/menu.h" +#include "../swap/swap_globals.h" +#include "../swap/handle_swap_sign_transaction.h" + #include "lib/policy.h" #include "lib/get_preimage.h" +#include "lib/get_merkle_leaf_element.h" -#include "get_wallet_address.h" +#include "handlers.h" #include "client_commands.h" -/// State of the callback function obtaining `scriptPubKey` of the processed descriptor. -typedef struct { - /// Dispatcher context. - dispatcher_context_t *dc; - /// State of the parent handler (GET_WALLET_ADDRESS handler) - get_wallet_address_state_t *parent; -} get_script_callback_state_t; +void handler_get_wallet_address(dispatcher_context_t *dc, uint8_t protocol_version) { + (void) protocol_version; -/** - * Computes wallet address. - * - * This step was separated from the main handler to optimize stack usage. - * - * @param[in,out] dc - * Dispatcher context. - */ -static void compute_address(dispatcher_context_t *dc); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); -/** - * Sends response APDU. - * - * @param[in,out] dc - * Dispatcher context. - */ -static void send_response(dispatcher_context_t *dc); + uint8_t display_address; -/** - * Obtains public key information information. - * - * @param[in,out] dc - * Dispatcher context. - * @param[in,out] state - * State of GET_WALLET_ADDRESS handler. - * @param[in] key_index - * Key index, zero-based. - * @param[out] key_info - * Pointer to structure instance receiving key information. - * - * @return true if successful, false otherwise. - */ -static bool get_key_info(dispatcher_context_t *dc, - get_wallet_address_state_t *state, - uint32_t key_index, - policy_map_key_info_t *key_info) { - int key_info_len = call_get_merkle_leaf_element(dc, - state->wallet_header_keys_info_merkle_root, - state->wallet_header_n_keys, - key_index, - state->key_info_str, - sizeof(state->key_info_str)); - if (key_info_len < 0) { - return false; - } + uint32_t address_index; + uint8_t is_change; - // Make a sub-buffer for the pubkey info - buffer_t key_info_buffer = buffer_create(state->key_info_str, key_info_len); + uint8_t wallet_id[32]; + uint8_t wallet_hmac[32]; - return 0 == parse_policy_map_key_info(&key_info_buffer, key_info); -} - -void handler_get_wallet_address(dispatcher_context_t *dc) { - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - get_wallet_address_state_t *state = (get_wallet_address_state_t *) &G_command_state; + bool is_wallet_default; // whether the wallet policy can be used without being registered - // Device must be unlocked - if (os_global_pin_is_validated() != BOLOS_UX_OK) { - SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); - return; - } + policy_map_wallet_header_t wallet_header; -#ifdef HAVE_LIQUID - state->pubkey_wildcard_id = KEY_WILDCARD_NONE; -#endif + union { + uint8_t bytes[MAX_WALLET_POLICY_BYTES]; + policy_node_t parsed; + } wallet_policy_map; - if (!buffer_read_u8(&dc->read_buffer, &state->display_address) || - !buffer_read_bytes(&dc->read_buffer, state->wallet_id, 32) || - !buffer_read_bytes(&dc->read_buffer, state->wallet_hmac, 32)) { + if (!buffer_read_u8(&dc->read_buffer, &display_address) || + !buffer_read_bytes(&dc->read_buffer, wallet_id, 32) || + !buffer_read_bytes(&dc->read_buffer, wallet_hmac, 32)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); return; } // change - if (!buffer_read_u8(&dc->read_buffer, &state->is_change)) { + if (!buffer_read_u8(&dc->read_buffer, &is_change)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); return; } - if (state->is_change != 0 && state->is_change != 1) { + if (is_change != 0 && is_change != 1) { SEND_SW(dc, SW_INCORRECT_DATA); return; } // address index - if (!buffer_read_u32(&dc->read_buffer, &state->address_index, BE)) { + if (!buffer_read_u32(&dc->read_buffer, &address_index, BE)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); return; } - -#ifdef HAVE_LIQUID - if (state->address_index > LIQUID_LAST_ADDRESS_INDEX) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } -#endif - - // Fetch the serialized wallet policy from the client - int serialized_wallet_policy_len = call_get_preimage(dc, - state->wallet_id, - state->serialized_wallet_policy, - sizeof(state->serialized_wallet_policy)); - if (serialized_wallet_policy_len < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - buffer_t serialized_wallet_policy_buf = - buffer_create(state->serialized_wallet_policy, serialized_wallet_policy_len); - if ((read_policy_map_wallet(&serialized_wallet_policy_buf, &state->wallet_header)) < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); + if (address_index >= BIP32_FIRST_HARDENED_CHILD) { + SEND_SW(dc, SW_INCORRECT_DATA); // it must be unhardened return; } - memcpy(state->wallet_header_keys_info_merkle_root, - state->wallet_header.keys_info_merkle_root, - sizeof(state->wallet_header_keys_info_merkle_root)); - state->wallet_header_n_keys = state->wallet_header.n_keys; + { + uint8_t serialized_wallet_policy[MAX_WALLET_POLICY_SERIALIZED_LENGTH]; + + // Fetch the serialized wallet policy from the client + int serialized_wallet_policy_len = call_get_preimage(dc, + wallet_id, + serialized_wallet_policy, + sizeof(serialized_wallet_policy)); + if (serialized_wallet_policy_len < 0) { + SEND_SW(dc, SW_INCORRECT_DATA); + return; + } - buffer_t policy_map_buffer = - buffer_create(&state->wallet_header.policy_map, state->wallet_header.policy_map_len); + buffer_t serialized_wallet_policy_buf = + buffer_create(serialized_wallet_policy, serialized_wallet_policy_len); - { - int ret = parse_policy_map(&policy_map_buffer, - state->wallet_policy_map_bytes, - sizeof(state->wallet_policy_map_bytes), - BIP32_PUBKEY_VERSION, - BIP32_PRIVKEY_VERSION); - if (ret < 0) { - PRINTF("Failure parsing policy map, error=%i\n", ret); + uint8_t policy_map_descriptor[MAX_DESCRIPTOR_TEMPLATE_LENGTH]; + if (0 > read_and_parse_wallet_policy(dc, + &serialized_wallet_policy_buf, + &wallet_header, + policy_map_descriptor, + wallet_policy_map.bytes, + sizeof(wallet_policy_map.bytes))) { SEND_SW(dc, SW_INCORRECT_DATA); return; } @@ -185,94 +124,34 @@ void handler_get_wallet_address(dispatcher_context_t *dc) { // the binary OR of all the hmac bytes (so == 0 iff the hmac is identically 0) uint8_t hmac_or = 0; for (int i = 0; i < 32; i++) { - hmac_or = hmac_or | state->wallet_hmac[i]; + hmac_or = hmac_or | wallet_hmac[i]; } if (hmac_or == 0) { - // No hmac, verify that the policy is a canonical one that is allowed by default - state->address_type = get_policy_address_type(&state->wallet_policy_map); - if (state->address_type == -1) { - PRINTF("Non-standard policy, and no hmac provided\n"); - SEND_SW(dc, SW_SIGNATURE_FAIL); - return; - } - - if (state->wallet_header.n_keys != 1) { - PRINTF("Standard wallets must have exactly 1 key\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - // we check if the key is indeed internal - uint32_t master_key_fingerprint = crypto_get_master_key_fingerprint(); + // No hmac, verify that the policy is indeed a default one - // Get infomation about the first and only public key - policy_map_key_info_t key_info; - if (!get_key_info(dc, state, 0, &key_info)) { + if (!is_wallet_policy_standard(dc, &wallet_header, &wallet_policy_map.parsed)) { SEND_SW(dc, SW_INCORRECT_DATA); return; } - if (!validate_policy_map_extended_pubkey(&key_info, BIP32_PUBKEY_VERSION)) { + if (wallet_header.name_len != 0) { + PRINTF("Name must be zero-length for a standard wallet policy\n"); SEND_SW(dc, SW_INCORRECT_DATA); return; } - if (read_u32_be(key_info.master_key_fingerprint, 0) != master_key_fingerprint) { + if (address_index > MAX_BIP44_ADDRESS_INDEX_RECOMMENDED) { + PRINTF("Address index is too large\n"); SEND_SW(dc, SW_INCORRECT_DATA); return; } - // generate pubkey and check if it matches - char pubkey_derived[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; - int serialized_pubkey_len = - get_serialized_extended_pubkey_at_path(key_info.master_key_derivation, - key_info.master_key_derivation_len, - BIP32_PUBKEY_VERSION, - pubkey_derived); - if (serialized_pubkey_len == -1) { - SEND_SW(dc, SW_BAD_STATE); - return; - } - - if (strncmp(key_info.ext_pubkey, pubkey_derived, MAX_SERIALIZED_PUBKEY_LENGTH) != 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - // check if derivation path is indeed standard - - // Based on the address type, we set the expected bip44 purpose for this canonical wallet - int bip44_purpose = get_bip44_purpose(state->address_type); - - if (key_info.master_key_derivation_len != 3) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - uint32_t coin_types[2] = {BIP44_COIN_TYPE, BIP44_COIN_TYPE_2}; - - uint32_t bip32_path[5]; - for (int i = 0; i < 3; i++) { - bip32_path[i] = key_info.master_key_derivation[i]; - } - bip32_path[3] = state->is_change ? 1 : 0; - bip32_path[4] = state->address_index; - - if (!is_address_path_standard(bip32_path, 5, bip44_purpose, coin_types, 2, -1)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - -#ifdef HAVE_LIQUID - state->pubkey_wildcard_id = key_info.wildcard_id; -#endif - - state->is_wallet_canonical = true; + is_wallet_default = true; } else { // Verify hmac - if (!check_wallet_hmac(state->wallet_id, state->wallet_hmac)) { + if (!check_wallet_hmac(wallet_id, wallet_hmac)) { PRINTF("Incorrect hmac\n"); SEND_SW(dc, SW_SIGNATURE_FAIL); return; @@ -288,151 +167,73 @@ void handler_get_wallet_address(dispatcher_context_t *dc) { state->pubkey_wildcard_id = key_info.wildcard_id; #endif - state->is_wallet_canonical = false; + is_wallet_default = false; } - // Compute the wallet id (sha256 of the serialization) - get_policy_wallet_id(&state->wallet_header, state->computed_wallet_id); - - if (memcmp(state->wallet_id, state->computed_wallet_id, sizeof(state->wallet_id)) != 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; + // 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); + finalize_exchange_sign_transaction(false); } - dc->next(compute_address); -} + // 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); + finalize_exchange_sign_transaction(false); + } -#ifdef HAVE_LIQUID -/** - * Callback function obtaining `scriptPubKey` of the processed descriptor. - * - * If `p_key_wildcard_to_verify` is not NULL, this function assumes it points to a constant which - * must be compared with each of the wallet's public key's wildcard identifier. This parameter is - * optional. If wildcard verification is not required it should be set to NULL. - * - * @param[in,out] state_in - * Callback state, stores necessary properties of the processed descriptor. - * @param[in] bip44_change - * Change element of the derivation path, defined according to BIP 44. - * @param[in] bip44_address_index - * Address index element of the derivation path, defined according to BIP 44. - * @param[out] out_buffer - * Buffer receiving `scriptPubKey`. - * @param[in] p_key_wildcard_to_verify - * If not NULL, requests to verify all wallet's public key wildcard IDs to be equal to value, - * pointed by this parameter. - * - * @return true if successful, false if error. - */ -static bool get_script_callback(void *state_in, - uint32_t bip44_change, - uint32_t bip44_address_index, - buffer_t *out_buffer, - const policy_map_key_wildcard_id_t *p_key_wildcard_to_verify) { - get_script_callback_state_t *state = (get_script_callback_state_t *)state_in; - - return 0 < call_get_wallet_script(state->dc, - liquid_policy_unwrap_ct(&state->parent->wallet_policy_map), - state->parent->wallet_header_keys_info_merkle_root, - state->parent->wallet_header_n_keys, - bip44_change, - bip44_address_index, - out_buffer, - p_key_wildcard_to_verify); -} -#endif + { + uint8_t computed_wallet_id[32]; + // Compute the wallet id (sha256 of the serialization) + get_policy_wallet_id(&wallet_header, computed_wallet_id); -/** - * Returns script address with support of ct() tag in wallet policy. - * - * @param[in,out] dc - * Dispatcher context. - * @param[in,out] state - * Handler state. - * - * @return length of produced address in bytes. - */ -static inline int get_script_address_wrapper(dispatcher_context_t *dc, - get_wallet_address_state_t *state) { -#ifdef HAVE_LIQUID - if (liquid_policy_is_blinded(&state->wallet_policy_map)) { - // Derive blinding public key from script - uint8_t blinding_pubkey[33]; - get_script_callback_state_t callback_state = { .dc = dc, .parent = state }; - if(!liquid_get_blinding_public_key(&state->wallet_policy_map, - state->script, - state->script_len, - state->pubkey_wildcard_id, - get_script_callback, - &callback_state, - blinding_pubkey)) { - return -1; + if (memcmp(wallet_id, computed_wallet_id, sizeof(wallet_id)) != 0) { + PRINTF("Mismatching wallet policy id\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + return; } - - int addr_len = liquid_get_script_confidential_address(state->script, - state->script_len, - &G_liquid_network_config, - blinding_pubkey, - sizeof(blinding_pubkey), - state->address, - sizeof(state->address)); - - explicit_bzero(blinding_pubkey, sizeof(blinding_pubkey)); - return addr_len; } -#endif // HAVE_LIQUID - return get_script_address(state->script, - state->script_len, - state->address, - sizeof(state->address)); -} + { + uint8_t script[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; + + int script_len = get_wallet_script( + dc, + &wallet_policy_map.parsed, + &(wallet_derivation_info_t){.wallet_version = wallet_header.version, + .keys_merkle_root = wallet_header.keys_info_merkle_root, + .n_keys = wallet_header.n_keys, + .change = is_change, + .address_index = address_index}, + script); + if (script_len < 0) { + PRINTF("Couldn't produce wallet script\n"); + SEND_SW(dc, SW_BAD_STATE); // unexpected + return; + } -// stack-intensive, split from the previous function to optimize stack usage -static void compute_address(dispatcher_context_t *dc) { - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + int address_len; + char address[MAX_ADDRESS_LENGTH_STR + 1]; // null-terminated string - get_wallet_address_state_t *state = (get_wallet_address_state_t *) &G_command_state; - buffer_t script_buf = buffer_create(state->script, sizeof(state->script)); + address_len = get_script_address(script, script_len, address, sizeof(address)); - state->script_len = call_get_wallet_script(dc, -#ifdef HAVE_LIQUID - liquid_policy_unwrap_ct(&state->wallet_policy_map), -#else - &state->wallet_policy_map -#endif - state->wallet_header_keys_info_merkle_root, - state->wallet_header_n_keys, - state->is_change, - state->address_index, - &script_buf, - NULL); - if (state->script_len < 0) { - SEND_SW(dc, SW_BAD_STATE); // unexpected - return; - } - - state->address_len = get_script_address_wrapper(dc, state); + if (address_len < 0) { + PRINTF("Could not produce address\n"); + SEND_SW(dc, SW_BAD_STATE); // unexpected + return; + } - if (state->address_len < 0) { - SEND_SW(dc, SW_BAD_STATE); // unexpected - return; - } + if (display_address != 0) { + if (!ui_display_wallet_address(dc, + is_wallet_default ? NULL : wallet_header.name, + address)) { + SEND_SW(dc, SW_DENY); + return; + } + } - if (state->display_address == 0) { - dc->next(send_response); - } else { - ui_display_wallet_address(dc, - state->is_wallet_canonical ? NULL : state->wallet_header.name, - state->address, - send_response); + SEND_RESPONSE(dc, address, address_len, SW_OK); } } - -static void send_response(dispatcher_context_t *dc) { - get_wallet_address_state_t *state = (get_wallet_address_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - SEND_RESPONSE(dc, state->address, state->address_len, SW_OK); -} diff --git a/src/handler/get_wallet_address.h b/src/handler/get_wallet_address.h deleted file mode 100644 index 0c3f08f67..000000000 --- a/src/handler/get_wallet_address.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include "../crypto.h" -#include "../common/bip32.h" -#include "../common/wallet.h" -#include "../boilerplate/dispatcher.h" - -#include "lib/get_merkle_leaf_element.h" - -#ifdef HAVE_LIQUID -#include "../liquid/liquid.h" -#endif - -/// State of GET_WALLET_ADDRESS handler -typedef struct { - /// Machine context for command dispatcher - machine_context_t ctx; - - /// Desired address index - uint32_t address_index; - /// Flag indicates change address if true - uint8_t is_change; - /// Flag enabling address display if true - uint8_t display_address; - - /// Flag indicating that the wallet is canonical (doesn't need registration) - bool is_wallet_canonical; - /// Address type, one of ADDRESS_TYPE_* constants - int address_type; - - // as deriving wallet addresses is stack-intensive, we move some - // variables here to use less stack overall - - /// Wallet header with basic information from wallet policy - policy_map_wallet_header_t wallet_header; - - /// Computed wallet identifier (hash) - uint8_t computed_wallet_id[32]; - /// Received wallet identifier - uint8_t wallet_id[32]; - /// The HMAC of a registered wallet - uint8_t wallet_hmac[32]; - - /// Root of a Merkle tree of the list of keys information - uint8_t wallet_header_keys_info_merkle_root[32]; - /// Number of keys in wallet header - size_t wallet_header_n_keys; - - union { - /// Serialized wallet policy - uint8_t serialized_wallet_policy[MAX_POLICY_MAP_SERIALIZED_LENGTH]; - /// Wallet policy map as array of bytes - uint8_t wallet_policy_map_bytes[MAX_POLICY_MAP_BYTES]; - /// Wallet policy map as a structure - policy_node_t wallet_policy_map; - }; - - /// Length of the address script - int script_len; - /// Address script - uint8_t script[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; - - /// Length of the address - int address_len; - /// Address, a null-terminated string - char address[MAX_ADDRESS_LENGTH_STR + 1]; - - /// Key information string - uint8_t key_info_str[MAX_POLICY_KEY_INFO_LEN]; -#ifdef HAVE_LIQUID - /// Public key wildcard defining the rules for child key derivation. - uint8_t pubkey_wildcard_id; -#endif -} get_wallet_address_state_t; - -/** - * Handles GET_WALLET_ADDRESS command. - * - * @param[in,out] dispatcher_context - * Dispatcher context. - */ -void handler_get_wallet_address(dispatcher_context_t *dispatcher_context); diff --git a/src/handler/handlers.h b/src/handler/handlers.h new file mode 100644 index 000000000..ad3d28d2b --- /dev/null +++ b/src/handler/handlers.h @@ -0,0 +1,10 @@ +#pragma once + +#include "../boilerplate/dispatcher.h" + +void handler_get_extended_pubkey(dispatcher_context_t *dispatcher_context, uint8_t p2); +void handler_get_master_fingerprint(dispatcher_context_t *dispatcher_context, uint8_t p2); +void handler_get_wallet_address(dispatcher_context_t *dispatcher_context, uint8_t p2); +void handler_register_wallet(dispatcher_context_t *dispatcher_context, uint8_t p2); +void handler_sign_message(dispatcher_context_t *dispatcher_context, uint8_t p2); +void handler_sign_psbt(dispatcher_context_t *dispatcher_context, uint8_t p2); diff --git a/src/handler/lib/check_merkle_tree_sorted.c b/src/handler/lib/check_merkle_tree_sorted.c index 06dbdb6f4..5bc07062e 100644 --- a/src/handler/lib/check_merkle_tree_sorted.c +++ b/src/handler/lib/check_merkle_tree_sorted.c @@ -3,16 +3,20 @@ #include "check_merkle_tree_sorted.h" #include "get_merkle_leaf_element.h" +#include "../../common/merkle.h" + static int compare_byte_arrays(const uint8_t array1[], size_t array1_len, const uint8_t array2[], size_t array2_len); int call_check_merkle_tree_sorted_with_callback(dispatcher_context_t *dispatcher_context, + void *callback_state, const uint8_t root[static 32], size_t size, - dispatcher_callback_descriptor_t callback) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + merkle_tree_elements_callback_t callback, + const merkleized_map_commitment_t *map_commitment) { + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); int prev_el_len = 0; uint8_t prev_el[MAX_CHECK_MERKLE_TREE_SORTED_PREIMAGE_SIZE]; @@ -39,10 +43,10 @@ int call_check_merkle_tree_sorted_with_callback(dispatcher_context_t *dispatcher memcpy(prev_el, cur_el, MIN((size_t)cur_el_len, sizeof(prev_el))); prev_el_len = cur_el_len; - if (callback.fn != NULL) { + if (callback != NULL) { // call callback with data buffer_t buf = buffer_create(cur_el, cur_el_len); - callback.fn(callback.state, &buf); + callback(dispatcher_context, callback_state, map_commitment, cur_el_idx, &buf); } } return 0; diff --git a/src/handler/lib/check_merkle_tree_sorted.h b/src/handler/lib/check_merkle_tree_sorted.h index 04a3d6ca6..e62ddaa9f 100644 --- a/src/handler/lib/check_merkle_tree_sorted.h +++ b/src/handler/lib/check_merkle_tree_sorted.h @@ -1,10 +1,20 @@ #pragma once #include "../../boilerplate/dispatcher.h" +#include "../../common/merkle.h" +#include "../../common/wallet.h" // this flow aborts if any element is larger than this size -// In PSBT, keys are currently up to 1+78 (for a serialized extended public key). -#define MAX_CHECK_MERKLE_TREE_SORTED_PREIMAGE_SIZE 80 +// TODO: we might remove this limitation altogether with a more careful implementation. +// Here we make sure that we have enough space for control block of a taptree of the maximum +// supported depth +#define MAX_CHECK_MERKLE_TREE_SORTED_PREIMAGE_SIZE (34 + 32 * (MAX_TAPTREE_POLICY_DEPTH - 1)) + +typedef void (*merkle_tree_elements_callback_t)(struct dispatcher_context_s *, + void *, + const merkleized_map_commitment_t *, + int, + buffer_t *); /** * Given a Merkle tree root and the size of the tree, it requests all the elements to the client @@ -15,9 +25,11 @@ * Returns 0 on success, or a negative number on failure. */ int call_check_merkle_tree_sorted_with_callback(dispatcher_context_t *dispatcher_context, + void *callback_state, const uint8_t root[static 32], size_t size, - dispatcher_callback_descriptor_t callback); + merkle_tree_elements_callback_t callback, + const merkleized_map_commitment_t *map_commitment); /** * Convenience function to call the get_merkle_tree_sorted flow, with a void callback. @@ -26,7 +38,9 @@ static inline int call_check_merkle_tree_sorted(dispatcher_context_t *dispatcher const uint8_t root[static 32], size_t size) { return call_check_merkle_tree_sorted_with_callback(dispatcher_context, + NULL, root, size, - make_callback(NULL, NULL)); + NULL, + NULL); } diff --git a/src/handler/lib/get_merkle_leaf_element.c b/src/handler/lib/get_merkle_leaf_element.c index 82448a924..5ab4b041b 100644 --- a/src/handler/lib/get_merkle_leaf_element.c +++ b/src/handler/lib/get_merkle_leaf_element.c @@ -9,7 +9,7 @@ int call_get_merkle_leaf_element(dispatcher_context_t *dispatcher_context, uint32_t leaf_index, uint8_t *out_ptr, size_t out_ptr_len) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t leaf_hash[32]; diff --git a/src/handler/lib/get_merkle_leaf_hash.c b/src/handler/lib/get_merkle_leaf_hash.c index 0385aa5c4..0da741073 100644 --- a/src/handler/lib/get_merkle_leaf_hash.c +++ b/src/handler/lib/get_merkle_leaf_hash.c @@ -9,13 +9,15 @@ #include "../../boilerplate/sw.h" #include "../client_commands.h" +#include "debug-helpers/debug.h" + // Reads the inputs and sends the GET_MERKLE_LEAF_PROOF request. int call_get_merkle_leaf_hash(dispatcher_context_t *dc, const uint8_t merkle_root[static 32], uint32_t tree_size, uint32_t leaf_index, uint8_t out[static 32]) { - // LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); PRINT_STACK_POINTER(); diff --git a/src/handler/lib/get_merkle_leaf_index.c b/src/handler/lib/get_merkle_leaf_index.c index 56e6d668b..66897781f 100644 --- a/src/handler/lib/get_merkle_leaf_index.c +++ b/src/handler/lib/get_merkle_leaf_index.c @@ -9,7 +9,7 @@ int call_get_merkle_leaf_index(dispatcher_context_t *dispatcher_context, size_t size, const uint8_t root[static 32], const uint8_t leaf_hash[static 32]) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); { // free memory as soon as possible uint8_t request[1 + 32 + 32]; diff --git a/src/handler/lib/get_merkle_preimage.c b/src/handler/lib/get_merkle_preimage.c index f7b4050e4..5afe1bd37 100644 --- a/src/handler/lib/get_merkle_preimage.c +++ b/src/handler/lib/get_merkle_preimage.c @@ -7,13 +7,17 @@ #include "../../crypto.h" #include "../client_commands.h" +#include "debug-helpers/debug.h" + +#include "cxram_stash.h" + // TODO: refactor common code with stream_preimage.c int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context, const uint8_t hash[static 32], uint8_t *out_ptr, size_t out_ptr_len) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); PRINT_STACK_POINTER(); @@ -56,12 +60,19 @@ int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context, uint8_t *data_ptr = dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset; - cx_sha256_t hash_context; +#ifdef USE_CXRAM_SECTION + // allocate buffers inside the cxram section to save memory + // this is safe as there are no syscalls here that use the cxram + cx_sha256_t *hash_context = (cx_sha256_t *) get_cxram_buffer(); +#else + cx_sha256_t hash_context_obj; + cx_sha256_t *hash_context = &hash_context_obj; +#endif - cx_sha256_init(&hash_context); + cx_sha256_init(hash_context); // update hash - crypto_hash_update(&hash_context.header, data_ptr, partial_data_len); + crypto_hash_update(&hash_context->header, data_ptr, partial_data_len); buffer_t out_buffer = buffer_create(out_ptr, out_ptr_len); @@ -97,7 +108,7 @@ int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context, // update hash crypto_hash_update( - &hash_context.header, + &hash_context->header, dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset, n_bytes); @@ -109,9 +120,9 @@ int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context, // hack: we pass the address of the final accumulator inside cx_sha256_t, so we don't need // an additional variable in the stack to store the final hash. - crypto_hash_digest(&hash_context.header, (uint8_t *) &hash_context.acc, 32); + crypto_hash_digest(&hash_context->header, (uint8_t *) &hash_context->acc, 32); - if (memcmp(hash_context.acc, hash, 32) != 0) { + if (memcmp(hash_context->acc, hash, 32) != 0) { PRINTF("Hash mismatch.\n"); return -10; } diff --git a/src/handler/lib/get_merkleized_map.c b/src/handler/lib/get_merkleized_map.c index ac2ea8ab2..1e6e7a914 100644 --- a/src/handler/lib/get_merkleized_map.c +++ b/src/handler/lib/get_merkleized_map.c @@ -8,12 +8,13 @@ #include "../../common/buffer.h" int call_get_merkleized_map_with_callback(dispatcher_context_t *dispatcher_context, + void *callback_state, const uint8_t root[static 32], int size, int index, - dispatcher_callback_descriptor_t keys_callback, + merkle_tree_elements_callback_t callback, merkleized_map_commitment_t *out_ptr) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t raw_output[9 + 2 * 32]; // maximum size of serialized result (9 bytes for the varint, // and the 2 Merkle roots) @@ -36,7 +37,9 @@ int call_get_merkleized_map_with_callback(dispatcher_context_t *dispatcher_conte } return call_check_merkle_tree_sorted_with_callback(dispatcher_context, + callback_state, out_ptr->keys_root, out_ptr->size, - keys_callback); + callback, + out_ptr); } \ No newline at end of file diff --git a/src/handler/lib/get_merkleized_map.h b/src/handler/lib/get_merkleized_map.h index 9bbb3eeec..c3ed69ec7 100644 --- a/src/handler/lib/get_merkleized_map.h +++ b/src/handler/lib/get_merkleized_map.h @@ -3,14 +3,17 @@ #include "../../boilerplate/dispatcher.h" #include "../../common/merkle.h" +#include "check_merkle_tree_sorted.h" + /** * TODO: docs */ int call_get_merkleized_map_with_callback(dispatcher_context_t *dispatcher_context, + void *callback_state, const uint8_t root[static 32], int size, int index, - dispatcher_callback_descriptor_t keys_callback, + merkle_tree_elements_callback_t callback, merkleized_map_commitment_t *out_ptr); /** @@ -22,9 +25,10 @@ static inline int call_get_merkleized_map(dispatcher_context_t *dispatcher_conte int index, merkleized_map_commitment_t *out_ptr) { return call_get_merkleized_map_with_callback(dispatcher_context, + NULL, root, size, index, - make_callback(NULL, NULL), + NULL, out_ptr); } diff --git a/src/handler/lib/get_merkleized_map_value.c b/src/handler/lib/get_merkleized_map_value.c index ba84823db..f9baf2fe9 100644 --- a/src/handler/lib/get_merkleized_map_value.c +++ b/src/handler/lib/get_merkleized_map_value.c @@ -11,7 +11,7 @@ int call_get_merkleized_map_value(dispatcher_context_t *dispatcher_context, int key_len, uint8_t *out, int out_len) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t key_merkle_hash[32]; merkle_compute_element_hash(key, key_len, key_merkle_hash); diff --git a/src/handler/lib/get_merkleized_map_value_hash.c b/src/handler/lib/get_merkleized_map_value_hash.c index aff8d22f5..29ee4b08b 100644 --- a/src/handler/lib/get_merkleized_map_value_hash.c +++ b/src/handler/lib/get_merkleized_map_value_hash.c @@ -10,7 +10,7 @@ int call_get_merkleized_map_value_hash(dispatcher_context_t *dispatcher_context, const uint8_t *key, int key_len, uint8_t out[static 32]) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t key_merkle_hash[32]; merkle_compute_element_hash(key, key_len, key_merkle_hash); diff --git a/src/handler/lib/get_preimage.c b/src/handler/lib/get_preimage.c index 8f4d05ac7..b5f5f5cd7 100644 --- a/src/handler/lib/get_preimage.c +++ b/src/handler/lib/get_preimage.c @@ -10,7 +10,7 @@ int call_get_preimage(dispatcher_context_t *dispatcher_context, const uint8_t hash[static 32], uint8_t *out, size_t out_len) { - // LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + // LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t cmd = CCMD_GET_PREIMAGE; dispatcher_context->add_to_response(&cmd, 1); @@ -86,13 +86,12 @@ int call_get_preimage(dispatcher_context_t *dispatcher_context, return -8; } - uint8_t *data_ptr2 = - dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset; + data_ptr = dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset; // update hash - crypto_hash_update(&hash_context.header, data_ptr2, n_bytes); + crypto_hash_update(&hash_context.header, data_ptr, n_bytes); - buffer_write_bytes(&buffer_out, data_ptr2, n_bytes); + buffer_write_bytes(&buffer_out, data_ptr, n_bytes); bytes_remaining -= n_bytes; } diff --git a/src/handler/lib/policy.c b/src/handler/lib/policy.c index a3b140dfc..23059af9d 100644 --- a/src/handler/lib/policy.c +++ b/src/handler/lib/policy.c @@ -1,68 +1,62 @@ #include #include "policy.h" -#include "util.h" #include "../lib/get_merkle_leaf_element.h" +#include "../lib/get_preimage.h" #include "../../crypto.h" #include "../../common/base58.h" +#include "../../common/bitvector.h" +#include "../../common/read.h" +#include "../../common/script.h" #include "../../common/segwit_addr.h" -#include "../../common/wif.h" -#include "../../liquid/liquid.h" +#include "../../common/wallet.h" + +#include "debug-helpers/debug.h" #include "ledger_assert.h" -#ifdef HAVE_LIQUID -#define MAX_POLICY_DEPTH 4 -#else -#define MAX_POLICY_DEPTH 3 -#endif +#define MAX_POLICY_DEPTH 10 + +// The last opcode must be processed as a VERIFY flag +#define PROCESSOR_FLAG_V 1 -#define MODE_OUT_BYTES 0 -#define MODE_OUT_HASH 1 +/** + * The label used to derive the symmetric key used to register/verify wallet policies on device. + */ +#define WALLET_SLIP0021_LABEL "\0LEDGER-Wallet policy" +#define WALLET_SLIP0021_LABEL_LEN \ + (sizeof(WALLET_SLIP0021_LABEL) - 1) // sizeof counts the terminating 0 typedef struct { const policy_node_t *policy_node; - // Only one of the two is used, depending on the `mode` - union { - cx_sha256_t *hash_context; - buffer_t *out_buf; - }; - + // bytes written to output + uint16_t length; // used to identify the stage of execution for nodes that require multiple rounds uint8_t step; - // MODE_OUT_BYTES if the current node is outputting the actual script bytes, or MODE_OUT_HASH - // if it is outputting the script hash - uint8_t mode; + uint8_t flags; } policy_parser_node_state_t; typedef struct { dispatcher_context_t *dispatcher_context; - const uint8_t *keys_merkle_root; - uint32_t n_keys; - bool change; - size_t address_index; + const wallet_derivation_info_t *wdi; + bool is_taproot; policy_parser_node_state_t nodes[MAX_POLICY_DEPTH]; // stack of nodes being processed int node_stack_eos; // index of node being processed within nodes; will be set -1 at the end of // processing - cx_sha256_t hash_context; // shared among all the nodes; there are never two concurrent hash - // computations in process. - uint8_t hash[32]; // when a node processed in hash mode is popped, the hash is computed here - - // If not NULL, requests to verify all wallet's public key wildcards to be equal to value, - // pointed by this parameter. - const policy_map_key_wildcard_id_t *p_key_wildcard_to_verify; + cx_hash_t *hash_context; + uint8_t hash[32]; // when a node is popped, the hash is computed here } policy_parser_state_t; -// comparator for pointers to compressed pubkeys -static int cmp_compressed_pubkeys(const void *a, const void *b) { +// comparator for pointers to arrays of equal length +static int cmp_arrays(const void *a, const void *b, size_t length) { const uint8_t *key_a = (const uint8_t *) a; const uint8_t *key_b = (const uint8_t *) b; - for (int i = 0; i < 33; i++) { + for (size_t i = 0; i < length; i++) { int diff = key_a[i] - key_b[i]; if (diff != 0) { return diff; @@ -71,27 +65,365 @@ static int cmp_compressed_pubkeys(const void *a, const void *b) { return 0; } +typedef int (*policy_parser_processor_t)(policy_parser_state_t *state, const void *arg); + +typedef enum { + CMD_CODE_OP, // data is a byte to emit (usually an opcode) + CMD_CODE_OP_V, // data is an opcode, but transform according to 'v' if necessary + CMD_CODE_PUSH_PK, // push the compressed pubkey indicated by the current policy_node_with_key_t + CMD_CODE_PUSH_PKH, // push the hash160 of the compressed pubkey indicated by the current + // policy_node_with_key_t + CMD_CODE_PUSH_UINT32, // push the integer in the current policy_node_with_uint32_t + CMD_CODE_PUSH_HASH20, // push a 20 bytes hash in the current policy_node_with_hash_160_t + CMD_CODE_PUSH_HASH32, // push a 32 bytes hash in the current policy_node_with_hash_256_t + CMD_CODE_PROCESS_CHILD, // process the i-th script of a policy_node_with_scripts_t, + // where i is indicated by the command data + CMD_CODE_PROCESS_CHILD_V, // like the previous, but it propagates the v flag to the child + CMD_CODE_PROCESS_CHILD_VV, // like the previous, but it activates the v flag in the child + + CMD_CODE_END // last step, should terminate here +} generic_processor_command_code_e; + +typedef struct { + uint8_t code; + uint8_t data; +} generic_processor_command_t; + +// Whitelistes for allowed fragments when processing inner scripts expressions +static const uint8_t fragment_whitelist_sh[] = {TOKEN_WPKH, TOKEN_MULTI, TOKEN_SORTEDMULTI}; +static const uint8_t fragment_whitelist_sh_wsh[] = {TOKEN_MULTI, TOKEN_SORTEDMULTI}; +static const uint8_t fragment_whitelist_wsh[] = { + /* tokens for scripts on segwit */ + TOKEN_0, + TOKEN_1, + TOKEN_PK, + TOKEN_PKH, + TOKEN_PK_K, + TOKEN_PK_H, + TOKEN_OLDER, + TOKEN_AFTER, + TOKEN_SHA256, + TOKEN_HASH256, + TOKEN_RIPEMD160, + TOKEN_HASH160, + TOKEN_ANDOR, + TOKEN_AND_V, + TOKEN_AND_B, + TOKEN_AND_N, + TOKEN_MULTI, + TOKEN_OR_B, + TOKEN_OR_C, + TOKEN_OR_D, + TOKEN_OR_I, + TOKEN_SORTEDMULTI, + TOKEN_THRESH, + // wrappers + TOKEN_A, + TOKEN_S, + TOKEN_C, + TOKEN_T, + TOKEN_D, + TOKEN_V, + TOKEN_J, + TOKEN_N, + TOKEN_L, + TOKEN_U}; +static const uint8_t fragment_whitelist_tapscript[] = { + /* tokens for scripts in taptrees */ + TOKEN_0, + TOKEN_1, + TOKEN_PK, + TOKEN_PKH, + TOKEN_PK_K, + TOKEN_PK_H, + TOKEN_OLDER, + TOKEN_AFTER, + TOKEN_SHA256, + TOKEN_HASH256, + TOKEN_RIPEMD160, + TOKEN_HASH160, + TOKEN_ANDOR, + TOKEN_AND_V, + TOKEN_AND_B, + TOKEN_AND_N, + TOKEN_MULTI_A, + TOKEN_OR_B, + TOKEN_OR_C, + TOKEN_OR_D, + TOKEN_OR_I, + TOKEN_SORTEDMULTI_A, + TOKEN_THRESH, + // wrappers + TOKEN_A, + TOKEN_S, + TOKEN_C, + TOKEN_T, + TOKEN_D, + TOKEN_V, + TOKEN_J, + TOKEN_N, + TOKEN_L, + TOKEN_U}; + +static const generic_processor_command_t commands_0[] = {{CMD_CODE_OP_V, OP_0}, {CMD_CODE_END, 0}}; +static const generic_processor_command_t commands_1[] = {{CMD_CODE_OP_V, OP_1}, {CMD_CODE_END, 0}}; +static const generic_processor_command_t commands_pk_k[] = {{CMD_CODE_PUSH_PK, 0}, + {CMD_CODE_END, 0}}; +static const generic_processor_command_t commands_pk_h[] = {{CMD_CODE_OP, OP_DUP}, + {CMD_CODE_OP, OP_HASH160}, + {CMD_CODE_PUSH_PKH, 0}, + {CMD_CODE_OP, OP_EQUALVERIFY}, + {CMD_CODE_END, 0}}; +static const generic_processor_command_t commands_pk[] = {{CMD_CODE_PUSH_PK, 0}, + {CMD_CODE_OP_V, OP_CHECKSIG}, + {CMD_CODE_END, 0}}; +static const generic_processor_command_t commands_older[] = {{CMD_CODE_PUSH_UINT32, 0}, + {CMD_CODE_OP_V, OP_CSV}, + {CMD_CODE_END, 0}}; +static const generic_processor_command_t commands_after[] = {{CMD_CODE_PUSH_UINT32, 0}, + {CMD_CODE_OP_V, OP_CLTV}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_sha256[] = {{CMD_CODE_OP, OP_SIZE}, + {CMD_CODE_OP, 1}, // 1-byte push + {CMD_CODE_OP, 32}, // pushed value + {CMD_CODE_OP, OP_EQUALVERIFY}, + {CMD_CODE_OP, OP_SHA256}, + {CMD_CODE_PUSH_HASH32, 0}, + {CMD_CODE_OP_V, OP_EQUAL}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_hash256[] = {{CMD_CODE_OP, OP_SIZE}, + {CMD_CODE_OP, 1}, // 1-byte push + {CMD_CODE_OP, 32}, // pushed value + {CMD_CODE_OP, OP_EQUALVERIFY}, + {CMD_CODE_OP, OP_HASH256}, + {CMD_CODE_PUSH_HASH32, 0}, + {CMD_CODE_OP_V, OP_EQUAL}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_ripemd160[] = {{CMD_CODE_OP, OP_SIZE}, + {CMD_CODE_OP, 1}, // 1-byte push + {CMD_CODE_OP, 32}, // pushed value + {CMD_CODE_OP, OP_EQUALVERIFY}, + {CMD_CODE_OP, OP_RIPEMD160}, + {CMD_CODE_PUSH_HASH20, 0}, + {CMD_CODE_OP_V, OP_EQUAL}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_hash160[] = {{CMD_CODE_OP, OP_SIZE}, + {CMD_CODE_OP, 1}, // 1-byte push + {CMD_CODE_OP, 32}, // pushed value + {CMD_CODE_OP, OP_EQUALVERIFY}, + {CMD_CODE_OP, OP_HASH160}, + {CMD_CODE_PUSH_HASH20, 0}, + {CMD_CODE_OP_V, OP_EQUAL}, + {CMD_CODE_END, 0}}; + +// andor(X,Y,X) ==> [X] NOTIF [Z] ELSE [Y] ENDIF +static const generic_processor_command_t commands_andor[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP, OP_NOTIF}, + {CMD_CODE_PROCESS_CHILD, 2}, + {CMD_CODE_OP, OP_ELSE}, + {CMD_CODE_PROCESS_CHILD, 1}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_and_v[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_PROCESS_CHILD_V, 1}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_and_b[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_PROCESS_CHILD, 1}, + {CMD_CODE_OP_V, OP_BOOLAND}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_and_n[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP, OP_NOTIF}, + {CMD_CODE_OP, OP_0}, + {CMD_CODE_OP, OP_ELSE}, + {CMD_CODE_PROCESS_CHILD, 1}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_or_b[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_PROCESS_CHILD, 1}, + {CMD_CODE_OP_V, OP_BOOLOR}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_or_c[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP, OP_NOTIF}, + {CMD_CODE_PROCESS_CHILD, 1}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_or_d[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP, OP_IFDUP}, + {CMD_CODE_OP, OP_NOTIF}, + {CMD_CODE_PROCESS_CHILD, 1}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_or_i[] = {{CMD_CODE_OP, OP_IF}, + {CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP, OP_ELSE}, + {CMD_CODE_PROCESS_CHILD, 1}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_a[] = {{CMD_CODE_OP, OP_TOALTSTACK}, + {CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP, OP_FROMALTSTACK}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_s[] = {{CMD_CODE_OP, OP_SWAP}, + {CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_c[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP_V, OP_CHECKSIG}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_t[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP_V, OP_1}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_d[] = {{CMD_CODE_OP, OP_DUP}, + {CMD_CODE_OP, OP_IF}, + {CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_v[] = {{CMD_CODE_PROCESS_CHILD_VV, 0}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_j[] = {{CMD_CODE_OP, OP_SIZE}, + {CMD_CODE_OP, OP_0NOTEQUAL}, + {CMD_CODE_OP, OP_IF}, + {CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_n[] = {{CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP_V, OP_0NOTEQUAL}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_l[] = {{CMD_CODE_OP, OP_IF}, + {CMD_CODE_OP, OP_0}, + {CMD_CODE_OP, OP_ELSE}, + {CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +static const generic_processor_command_t commands_u[] = {{CMD_CODE_OP, OP_IF}, + {CMD_CODE_PROCESS_CHILD, 0}, + {CMD_CODE_OP, OP_ELSE}, + {CMD_CODE_OP, OP_0}, + {CMD_CODE_OP_V, OP_ENDIF}, + {CMD_CODE_END, 0}}; + +int read_and_parse_wallet_policy( + dispatcher_context_t *dispatcher_context, + buffer_t *buf, + policy_map_wallet_header_t *wallet_header, + uint8_t policy_map_descriptor_template[static MAX_DESCRIPTOR_TEMPLATE_LENGTH], + uint8_t *policy_map_bytes, + size_t policy_map_bytes_len) { + if ((read_wallet_policy_header(buf, wallet_header)) < 0) { + return WITH_ERROR(-1, "Failed reading wallet policy header"); + } + + if (wallet_header->version == WALLET_POLICY_VERSION_V1) { + memcpy(policy_map_descriptor_template, + wallet_header->descriptor_template, + wallet_header->descriptor_template_len); + } else { + // if V2, stream and parse descriptor template from client first + int descriptor_template_len = call_get_preimage(dispatcher_context, + wallet_header->descriptor_template_sha256, + policy_map_descriptor_template, + MAX_DESCRIPTOR_TEMPLATE_LENGTH); + if (descriptor_template_len < 0) { + return WITH_ERROR(-1, "Failed getting wallet policy descriptor template"); + } + } + + buffer_t policy_map_buffer = + buffer_create(policy_map_descriptor_template, wallet_header->descriptor_template_len); + + int desc_temp_len = parse_descriptor_template(&policy_map_buffer, + policy_map_bytes, + policy_map_bytes_len, + wallet_header->version); + if (desc_temp_len < 0) { + return WITH_ERROR(-1, "Failed parsing descriptor template"); + } + return desc_temp_len; +} + +/** + * Pushes a node onto the stack. Returns 0 on success, -1 if the stack is exhausted. + */ +__attribute__((warn_unused_result)) static int state_stack_push(policy_parser_state_t *state, + const policy_node_t *policy_node, + uint8_t flags) { + ++state->node_stack_eos; + + if (state->node_stack_eos >= MAX_POLICY_DEPTH) { + return WITH_ERROR(-1, "Reached maximum policy depth"); + } + + policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; + node->policy_node = policy_node; + node->length = 0; + node->step = 0; + node->flags = flags; + + return 0; +} + +/** + * Pops a node from the stack. + * Returns the emitted length on success, -1 on error. + */ +__attribute__((warn_unused_result)) static int state_stack_pop(policy_parser_state_t *state) { + policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; + + if (state->node_stack_eos <= -1) { + return WITH_ERROR(-1, "Stack underflow"); + } + + --state->node_stack_eos; + + if (state->node_stack_eos >= 0) { + state->nodes[state->node_stack_eos].length += node->length; + } + return node->length; +} + +__attribute__((warn_unused_result)) static inline int +execute_processor(policy_parser_state_t *state, policy_parser_processor_t proc, const void *arg) { + int ret = proc(state, arg); + + // if the processor is done, pop the stack + if (ret > 0) { + return state_stack_pop(state); + } + + return ret; +} + // p2pkh ==> legacy address (start with 1 on mainnet, m or n on testnet) // p2sh (also nested segwit) ==> legacy script (start with 3 on mainnet, 2 on testnet) // p2wpkh or p2wsh ==> bech32 (sart with bc1 on mainnet, tb1 on testnet) -/** - * Gets the extended public key at given index. - * - * This is a convenience function, split from get_derived_pubkey only to improve stack usage. - * - * @param[in,out] state - * Parser state. - * @param[in] key_index - * Public key index. - * @param[out] out - * Pointer to structure instance receiving serialized extended public key. - * - * @return returns -1 on error, or a non-negative public key wildcard identifier. - */ -static int __attribute__((noinline)) get_extended_pubkey(policy_parser_state_t *state, - int key_index, - serialized_extended_pubkey_t *out) { +// convenience function, split from get_derived_pubkey only to improve stack usage +// returns -1 on error, 0 if the returned key info has no wildcard (**), 1 if it has the wildcard +__attribute__((noinline, warn_unused_result)) static int get_extended_pubkey( + dispatcher_context_t *dispatcher_context, + const wallet_derivation_info_t *wdi, + int key_index, + serialized_extended_pubkey_t *out) { PRINT_STACK_POINTER(); policy_map_key_info_t key_info; @@ -99,9 +431,9 @@ static int __attribute__((noinline)) get_extended_pubkey(policy_parser_state_t * { char key_info_str[MAX_POLICY_KEY_INFO_LEN]; - int key_info_len = call_get_merkle_leaf_element(state->dispatcher_context, - state->keys_merkle_root, - state->n_keys, + int key_info_len = call_get_merkle_leaf_element(dispatcher_context, + wdi->keys_merkle_root, + wdi->n_keys, key_index, (uint8_t *) key_info_str, sizeof(key_info_str)); @@ -112,143 +444,238 @@ static int __attribute__((noinline)) get_extended_pubkey(policy_parser_state_t * // Make a sub-buffer for the pubkey info buffer_t key_info_buffer = buffer_create(key_info_str, key_info_len); - if (parse_policy_map_key_info(&key_info_buffer, &key_info) == -1) { + if (parse_policy_map_key_info(&key_info_buffer, &key_info, wdi->wallet_version) == -1) { return -1; } } + *out = key_info.ext_pubkey; - // decode pubkey - serialized_extended_pubkey_check_t decoded_pubkey_check; - if (base58_decode(key_info.ext_pubkey, - strnlen(key_info.ext_pubkey, sizeof(key_info.ext_pubkey) - 1), - (uint8_t *) &decoded_pubkey_check, - sizeof(decoded_pubkey_check)) == -1) { - return -1; - } - - uint8_t checksum[4]; - crypto_get_checksum((uint8_t *)&decoded_pubkey_check.serialized_extended_pubkey, - sizeof(decoded_pubkey_check.serialized_extended_pubkey), - checksum); - if (!memeq(checksum, decoded_pubkey_check.checksum, sizeof(checksum))) { - return -1; - } - - memcpy(out, - &decoded_pubkey_check.serialized_extended_pubkey, - sizeof(decoded_pubkey_check.serialized_extended_pubkey)); - - return key_info.wildcard_id; + return key_info.has_wildcard ? 1 : 0; } -/** - * @brief Get the derived public key at given index. - * - * @param[in,out] state - * Parser state. - * @param[in] key_index - * Public key index. - * @param[out] out - * Buffer receiving compressed public key, must be not smaller than 33 bytes. - * - * @return returns -1 on error, or a non-negative public key wildcard identifier. - */ -static int get_derived_pubkey(policy_parser_state_t *state, int key_index, uint8_t out[static 33]) { +__attribute__((warn_unused_result)) static int get_derived_pubkey( + dispatcher_context_t *dispatcher_context, + const wallet_derivation_info_t *wdi, + const policy_node_key_placeholder_t *key_placeholder, + uint8_t out[static 33]) { PRINT_STACK_POINTER(); serialized_extended_pubkey_t ext_pubkey; - int wildcard_id = get_extended_pubkey(state, key_index, &ext_pubkey); - if (wildcard_id < 0) { + int ret = get_extended_pubkey(dispatcher_context, wdi, key_placeholder->key_index, &ext_pubkey); + if (ret < 0) { return -1; } - switch(wildcard_id) { - case KEY_WILDCARD_NONE: - // No wildcard, returning pubkey "as is" - break; - - case KEY_WILDCARD_ANY: - case KEY_WILDCARD_STANDARD_CHAINS: - case KEY_WILDCARD_EXTERNAL_CHAIN: - case KEY_WILDCARD_INTERNAL_CHAIN: - // Check if requested derivation is allowed by pubkey wildcard - if ( (KEY_WILDCARD_EXTERNAL_CHAIN == wildcard_id && 0 != state->change) || - (KEY_WILDCARD_INTERNAL_CHAIN == wildcard_id && 1 != state->change) ) { - return -1; - } - // Derive the /chain/i child of this pubkey reusing the same memory of ext_pubkey - if (0 != bip32_CKDpub(&ext_pubkey, state->change, &ext_pubkey)) { - return -1; - } - if (0 != bip32_CKDpub(&ext_pubkey, state->address_index, &ext_pubkey)) { - return -1; - } - break; - - default: - // Unsupported wildcard - return -1; + // we derive the // child of this pubkey + // we reuse the same memory of ext_pubkey + ret = bip32_CKDpub(&ext_pubkey, + wdi->change ? key_placeholder->num_second : key_placeholder->num_first, + &ext_pubkey); + if (ret < 0) { + return -1; } - - memcpy(out, ext_pubkey.compressed_pubkey, 33); - - return wildcard_id; -} - -/** - * Pushes a node onto the stack. Returns 0 on success, -1 if the stack is exhausted. - */ -static int state_stack_push(policy_parser_state_t *state, policy_node_t *policy_node) { - ++state->node_stack_eos; - - if (state->node_stack_eos >= MAX_POLICY_DEPTH) { + ret = bip32_CKDpub(&ext_pubkey, wdi->address_index, &ext_pubkey); + if (ret < 0) { return -1; } - policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; - cx_sha256_init(&state->hash_context); - node->policy_node = policy_node; - node->step = 0; - node->mode = MODE_OUT_HASH; - node->hash_context = &state->hash_context; + memcpy(out, ext_pubkey.compressed_pubkey, 33); return 0; } -/** - * Pops a node the stack. If the node is in HASH mode, computes the hash. - * Returns 0 on success, -1 on error. - */ -static int state_stack_pop(policy_parser_state_t *state) { +static void update_output(policy_parser_state_t *state, const uint8_t *data, size_t data_len) { policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; - - if (state->node_stack_eos >= MAX_POLICY_DEPTH || state->node_stack_eos <= -1) { - return -1; + node->length += data_len; + if (state->hash_context != NULL) { + crypto_hash_update(state->hash_context, data, data_len); } +} + +static inline void update_output_u8(policy_parser_state_t *state, uint8_t data) { + update_output(state, &data, 1); +} - if (node->mode == MODE_OUT_HASH) { - crypto_hash_digest(&state->hash_context.header, state->hash, 32); +// outputs the minimal push opcode for an unsigned 32bit integer +static void update_output_push_u32(policy_parser_state_t *state, uint32_t n) { + if (n == 0) { + update_output_u8(state, OP_0); + } else if (n <= 16) { + update_output_u8(state, 0x50 + (uint8_t) n); + } else { + uint8_t n_le[4]; + write_u32_le(n_le, 0, n); + uint8_t byte_size; + if (n <= 0x7f) + byte_size = 1; + else if (n <= 0x7fff) + byte_size = 2; + else if (n <= 0x7fffff) + byte_size = 3; + else if (n <= 0x7fffffff) + byte_size = 4; + else + byte_size = 5; // no 32-bit number needs more than 5 bytes + + update_output_u8(state, byte_size); + if (byte_size < 5) { + update_output(state, n_le, byte_size); + } else { + // Since numbers are signed little endian, unsigned numbers bigger than 0x7FFFFFFF + // need an extra 0x00 byte. + update_output(state, n_le, 4); + update_output_u8(state, 0); + } } +} - --state->node_stack_eos; - return 0; +static void update_output_op_v(policy_parser_state_t *state, uint8_t op) { + const policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; + if (node->flags & PROCESSOR_FLAG_V) { + if (op == OP_CHECKSIG || op == OP_CHECKMULTISIG || op == OP_NUMEQUAL || op == OP_EQUAL) { + // the _VERIFY versions of the opcodes are all 1 larger + update_output_u8(state, op + 1); + } else { + update_output_u8(state, op); + update_output_u8(state, OP_VERIFY); + } + } else { + update_output_u8(state, op); + } } -static void update_output(policy_parser_state_t *state, const uint8_t *data, size_t data_len) { +__attribute__((warn_unused_result)) static int process_generic_node(policy_parser_state_t *state, + const void *arg) { policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; - if (node->mode == MODE_OUT_BYTES) { - buffer_write_bytes(node->out_buf, data, data_len); + + const generic_processor_command_t *commands = (const generic_processor_command_t *) arg; + + size_t n_commands = 0; + while (commands[n_commands].code != CMD_CODE_END) ++n_commands; + + if (node->step > n_commands) { + return WITH_ERROR(-1, "Inconsistent state"); + } else if (node->step == n_commands) { + return 1; } else { - crypto_hash_update(&node->hash_context->header, data, data_len); + uint8_t cmd_code = commands[node->step].code; + uint8_t cmd_data = commands[node->step].data; + + switch (cmd_code) { + case CMD_CODE_OP: { + update_output_u8(state, cmd_data); + break; + } + case CMD_CODE_OP_V: { + update_output_op_v(state, cmd_data); + break; + } + case CMD_CODE_PUSH_PK: { + const policy_node_with_key_t *policy = + (const policy_node_with_key_t *) node->policy_node; + uint8_t compressed_pubkey[33]; + if (-1 == + get_derived_pubkey(state->dispatcher_context, + state->wdi, + r_policy_node_key_placeholder(&policy->key_placeholder), + compressed_pubkey)) { + return -1; + } + + if (!state->is_taproot) { + update_output_u8(state, 33); // PUSH 33 bytes + update_output(state, compressed_pubkey, 33); + } else { + // x-only pubkey if within taproot + update_output_u8(state, 32); // PUSH 32 bytes + update_output(state, compressed_pubkey + 1, 32); + } + break; + } + case CMD_CODE_PUSH_PKH: { + const policy_node_with_key_t *policy = + (const policy_node_with_key_t *) node->policy_node; + uint8_t compressed_pubkey[33]; + if (-1 == + get_derived_pubkey(state->dispatcher_context, + state->wdi, + r_policy_node_key_placeholder(&policy->key_placeholder), + compressed_pubkey)) { + return -1; + } + if (!state->is_taproot) { + crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory + } else { + // x-only pubkey if within taproot + crypto_hash160(compressed_pubkey + 1, 32, compressed_pubkey); // reuse memory + } + + update_output_u8(state, 20); // PUSH 20 bytes + update_output(state, compressed_pubkey, 20); + break; + } + case CMD_CODE_PUSH_UINT32: { + const policy_node_with_uint32_t *policy = + (const policy_node_with_uint32_t *) node->policy_node; + update_output_push_u32(state, policy->n); + break; + } + case CMD_CODE_PUSH_HASH20: { + const policy_node_with_hash_160_t *policy = + (const policy_node_with_hash_160_t *) node->policy_node; + update_output_u8(state, 20); + update_output(state, policy->h, 20); + break; + } + case CMD_CODE_PUSH_HASH32: { + const policy_node_with_hash_256_t *policy = + (const policy_node_with_hash_256_t *) node->policy_node; + update_output_u8(state, 32); + update_output(state, policy->h, 32); + break; + } + case CMD_CODE_PROCESS_CHILD: { + const policy_node_with_scripts_t *policy = + (const policy_node_with_scripts_t *) node->policy_node; + if (0 > state_stack_push(state, r_policy_node(&policy->scripts[cmd_data]), 0)) { + return -1; + } + break; + } + case CMD_CODE_PROCESS_CHILD_V: { + const policy_node_with_scripts_t *policy = + (const policy_node_with_scripts_t *) node->policy_node; + if (0 > state_stack_push(state, + r_policy_node(&policy->scripts[cmd_data]), + node->flags)) { + return -1; + } + break; + } + case CMD_CODE_PROCESS_CHILD_VV: { + const policy_node_with_scripts_t *policy = + (const policy_node_with_scripts_t *) node->policy_node; + if (0 > state_stack_push(state, + r_policy_node(&policy->scripts[cmd_data]), + node->flags | PROCESSOR_FLAG_V)) { + return -1; + } + break; + } + default: + PRINTF("Unexpected command code: %d\n", cmd_code); + return -1; + } + ++node->step; + return 0; } } -static void update_output_u8(policy_parser_state_t *state, const uint8_t data) { - update_output(state, &data, 1); -} +__attribute__((warn_unused_result)) static int process_pkh_wpkh_node(policy_parser_state_t *state, + const void *arg) { + UNUSED(arg); -static int __attribute__((noinline)) process_pkh_wpkh_node(policy_parser_state_t *state) { PRINT_STACK_POINTER(); policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; @@ -259,331 +686,1271 @@ static int __attribute__((noinline)) process_pkh_wpkh_node(policy_parser_state_t policy_node_with_key_t *policy = (policy_node_with_key_t *) node->policy_node; - unsigned int out_len; - if (policy->type == TOKEN_PKH) { - out_len = 3 + 20 + 2; - } else { - out_len = 2 + 20; - } - - if (node->mode == MODE_OUT_BYTES && !buffer_can_read(node->out_buf, out_len)) { - return -1; - } - - uint8_t compressed_pubkey[33]; - - if (node->mode == MODE_OUT_HASH) { - cx_sha256_init(&state->hash_context); - } + uint8_t compressed_pubkey[33]; - int wildcard_id = get_derived_pubkey(state, policy->key_index, compressed_pubkey); - if (-1 == wildcard_id) { - return -1; - } - // Verify public key wildcard if requested - if (NULL != state->p_key_wildcard_to_verify && - wildcard_id != *state->p_key_wildcard_to_verify) { + if (-1 == get_derived_pubkey(state->dispatcher_context, + state->wdi, + r_policy_node_key_placeholder(&policy->key_placeholder), + compressed_pubkey)) { return -1; - } + } else if (policy->base.type == TOKEN_PKH) { + update_output_u8(state, OP_DUP); + update_output_u8(state, OP_HASH160); - int result; - if (policy->type == TOKEN_PKH) { - update_output_u8(state, 0x76); - update_output_u8(state, 0xa9); - update_output_u8(state, 0x14); + update_output_u8(state, 20); // PUSH 20 bytes - crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory + if (!state->is_taproot) { + crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory + } else { + // x-only pubkey if within taproot + crypto_hash160(compressed_pubkey + 1, 32, compressed_pubkey); // reuse memory + } update_output(state, compressed_pubkey, 20); - update_output_u8(state, 0x88); - update_output_u8(state, 0xac); + update_output_u8(state, OP_EQUALVERIFY); + update_output_op_v(state, OP_CHECKSIG); + } else { // policy->base.type == TOKEN_WPKH + if (state->is_taproot) { + PRINTF("wpkh is invalid within taproot context"); + return -1; + } + + update_output_u8(state, OP_0); - result = 3 + 20 + 2; - } else { // policy->type == TOKEN_WPKH - update_output_u8(state, 0x00); - update_output_u8(state, 0x14); + update_output_u8(state, 20); // PUSH 20 bytes crypto_hash160(compressed_pubkey, 33, compressed_pubkey); // reuse memory update_output(state, compressed_pubkey, 20); - - result = 2 + 20; } - if (-1 == state_stack_pop(state)) { - return -1; - } - return result; + return 1; } -static int __attribute__((noinline)) process_sh_wsh_node(policy_parser_state_t *state) { +__attribute__((warn_unused_result)) static int process_thresh_node(policy_parser_state_t *state, + const void *arg) { + UNUSED(arg); + PRINT_STACK_POINTER(); policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; + const policy_node_thresh_t *policy = (const policy_node_thresh_t *) node->policy_node; - policy_node_with_script_t *policy = (policy_node_with_script_t *) node->policy_node; + // [X1] [X2] ADD ... [Xn] ADD EQUAL - if (node->step != 0 && node->step != 1) { - return -1; - } + /* + It's a bit unnatural to encode thresh in a way that is compatible with our + stack-based encoder, as every "step" that needs to recur on a child Script + must emit the child script as its last thing. The natural way of splitting + this would be: - if (node->step == 0) { - // process child in HASH mode - if (-1 == state_stack_push(state, policy->script)) { - return -1; - } - ++node->step; - return 0; - } + [X1] / [X2] ADD / [X3] ADD / ... / [Xn] ADD / EQUAL - // child already computed + Instead, we have to split it as follows: - if (node->mode == MODE_OUT_HASH) { - cx_sha256_init(&state->hash_context); - } + [X1] / [X2] / ADD [X3] / ... / ADD [Xn] / ADD EQUAL - int result; - if (policy->type == TOKEN_SH) { - update_output_u8(state, 0xa9); - update_output_u8(state, 0x14); + But this is incorrect if n == 1, because the correct encoding is just - crypto_ripemd160(state->hash, 32, state->hash); // reuse memory - update_output(state, state->hash, 20); + [X1] EQUAL - update_output_u8(state, 0x87); + Therefore, the case n == 1 needs to be handled separately to avoid the extra ADD. + */ - result = 2 + 20 + 1; - } else { // policy->type == TOKEN_WSH - update_output_u8(state, 0x00); - update_output_u8(state, 0x20); + // n+1 steps + // at step i, for 0 <= i < n, we produce [Xi] if i <= 1, or ADD [Xi] otherwise + // at step n, we produce EQUAL if n == 1, or ADD EQUAL otherwise - update_output(state, state->hash, 32); + if (node->step < policy->n) { + // find the current child node + policy_node_scriptlist_t *cur = r_policy_node_scriptlist(&policy->scriptlist); + LEDGER_ASSERT(cur != NULL, "This should never happen"); + for (size_t i = 0; i < node->step; i++) { + cur = r_policy_node_scriptlist(&cur->next); + LEDGER_ASSERT(cur != NULL, "This should never happen"); + } - result = 2 + 32; - } + // process child node + if (node->step > 1) { + update_output_u8(state, OP_ADD); + } - if (-1 == state_stack_pop(state)) { - return -1; + if (-1 == state_stack_push(state, r_policy_node(&cur->script), 0)) { + return -1; + } + ++node->step; + return 0; + } else { + // final step + if (policy->n >= 2) { + // no OP_ADD if n == 1, per comment above + update_output_u8(state, OP_ADD); + } + update_output_push_u32(state, policy->k); + update_output_op_v(state, OP_EQUAL); + return 1; } - return result; } -static int __attribute__((noinline)) process_multi_sortedmulti_node(policy_parser_state_t *state) { +__attribute__((warn_unused_result)) static int process_multi_sortedmulti_node( + policy_parser_state_t *state, + const void *arg) { + UNUSED(arg); + PRINT_STACK_POINTER(); policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; + const policy_node_multisig_t *policy = (const policy_node_multisig_t *) node->policy_node; - if (node->step != 0) { - return -1; + if (policy->n > 16) { + return WITH_ERROR(-1, "Implemented only for n <= 16"); } - policy_node_multisig_t *policy = (policy_node_multisig_t *) node->policy_node; - // k {pubkey_1} ... {pubkey_n} n OP_CHECKMULTISIG - unsigned int out_len = 1 + 34 * policy->n + 1 + 1; - - if (node->mode == MODE_OUT_BYTES && !buffer_can_read(node->out_buf, out_len)) { - return -1; - } - - if (node->mode == MODE_OUT_HASH) { - cx_sha256_init(&state->hash_context); - } update_output_u8(state, 0x50 + policy->k); // OP_k - // derive each key - uint8_t compressed_pubkeys[MAX_POLICY_MAP_KEYS][33]; - for (unsigned int i = 0; i < policy->n; i++) { - int wildcard_id = get_derived_pubkey(state, policy->key_indexes[i], compressed_pubkeys[i]); - if (-1 == wildcard_id) { - return -1; - } - // Verify public key wildcard if requested - if (NULL != state->p_key_wildcard_to_verify && - wildcard_id != *state->p_key_wildcard_to_verify) { - return -1; - } - } + // 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(used)); - if (policy->type == TOKEN_SORTEDMULTI) { - // sort the pubkeys (we avoid using qsort, as it takes ~700 bytes in binary size) + for (int i = 0; i < policy->n; i++) { + uint8_t compressed_pubkey[33]; - // bubble sort - bool swapped; - do { - swapped = false; - for (unsigned int i = 1; i < policy->n; i++) { - if (cmp_compressed_pubkeys(compressed_pubkeys[i - 1], compressed_pubkeys[i]) > 0) { - swapped = true; + if (policy->base.type == TOKEN_MULTI) { + if (-1 == + get_derived_pubkey(state->dispatcher_context, + state->wdi, + &r_policy_node_key_placeholder(&policy->key_placeholders)[i], + compressed_pubkey)) { + return -1; + } + } else { + // sortedmulti is problematic, especially for very large wallets: we don't have enough + // memory on Nano S to keep all the keys in memory. Therefore, we use a slow method: at + // each iteration, find the lexicographically smallest key that was not already used + // (basically, like in insertion sort). This means quadratic communication with the + // client, and a quadratic number of pubkey derivations as well, which are quite slow. + // Performance might become an issue for very large multisig wallets, but this allows us + // to remove any limitation on the supported number of pubkeys, and to keep the code + // simple. + // Should speed be reported as an issue in practice, sorting could be done in-memory for + // non-Nano S devices, instead (requiring 33*MAX_PUBKEYS_PER_MULTISIG > 500 bytes more + // memory). + + int smallest_pubkey_index = -1; + memset(compressed_pubkey, 0xFF, sizeof(compressed_pubkey)); // init to largest value + + for (int j = 0; j < policy->n; j++) { + if (!bitvector_get(used, j)) { + uint8_t cur_pubkey[33]; + if (-1 == get_derived_pubkey( + state->dispatcher_context, + state->wdi, + &r_policy_node_key_placeholder(&policy->key_placeholders)[j], + cur_pubkey)) { + return -1; + } - for (int j = 0; j < 33; j++) { - uint8_t t = compressed_pubkeys[i - 1][j]; - compressed_pubkeys[i - 1][j] = compressed_pubkeys[i][j]; - compressed_pubkeys[i][j] = t; + if (cmp_arrays(compressed_pubkey, cur_pubkey, 33) > 0) { + memcpy(compressed_pubkey, cur_pubkey, 33); + smallest_pubkey_index = j; } } } - } while (swapped); - } + bitvector_set(used, smallest_pubkey_index, true); // mark the key as used + } - for (unsigned int i = 0; i < policy->n; i++) { // push (33 = 0x21 bytes) update_output_u8(state, 0x21); - update_output(state, compressed_pubkeys[i], 33); + update_output(state, compressed_pubkey, 33); } - update_output_u8(state, 0x50 + policy->n); // OP_n - update_output_u8(state, 0xae); // OP_CHECKMULTISIG + update_output_u8(state, 0x50 + policy->n); // OP_n + update_output_op_v(state, OP_CHECKMULTISIG); // OP_CHECKMULTISIG - if (-1 == state_stack_pop(state)) { - return -1; - } - return out_len; + return 1; } -static int __attribute__((noinline)) process_tr_node(policy_parser_state_t *state) { +__attribute__((warn_unused_result)) static int process_multi_a_sortedmulti_a_node( + policy_parser_state_t *state, + const void *arg) { + UNUSED(arg); + PRINT_STACK_POINTER(); policy_parser_node_state_t *node = &state->nodes[state->node_stack_eos]; + const policy_node_multisig_t *policy = (const policy_node_multisig_t *) node->policy_node; - if (node->step != 0) { - return -1; + if (policy->k > 16) { + return WITH_ERROR(-1, "Implemented only for k <= 16"); } - policy_node_with_key_t *policy = (policy_node_with_key_t *) node->policy_node; + // OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD OP_NUMEQUAL - unsigned int out_len = 2 + 32; + // 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)); - if (node->mode == MODE_OUT_BYTES && !buffer_can_read(node->out_buf, out_len)) { - return -1; - } + for (int i = 0; i < policy->n; i++) { + uint8_t compressed_pubkey[33]; - int result; + if (policy->base.type == TOKEN_MULTI_A) { + if (-1 == + get_derived_pubkey(state->dispatcher_context, + state->wdi, + &r_policy_node_key_placeholder(&policy->key_placeholders)[i], + compressed_pubkey)) { + return -1; + } + } else { + // Inefficient O(n^2) sorting; check process_multi_sortedmulti_node for the motivation. + + int smallest_pubkey_index = -1; + memset(compressed_pubkey, 0xFF, sizeof(compressed_pubkey)); // init to largest value + + for (int j = 0; j < policy->n; j++) { + if (!bitvector_get(used, j)) { + uint8_t cur_pubkey[33]; + if (-1 == get_derived_pubkey( + state->dispatcher_context, + state->wdi, + &r_policy_node_key_placeholder(&policy->key_placeholders)[j], + cur_pubkey)) { + return -1; + } - uint8_t compressed_pubkey[33]; - uint8_t tweaked_key[32]; + // x-only pubkeys must be compared ignoring the first byte + if (cmp_arrays(compressed_pubkey + 1, cur_pubkey + 1, 32) > 0) { + memcpy(compressed_pubkey, cur_pubkey, 33); + smallest_pubkey_index = j; + } + } + } + bitvector_set(used, smallest_pubkey_index, true); // mark the key as used + } - int wildcard_id = get_derived_pubkey(state, policy->key_index, compressed_pubkey); - if (-1 == wildcard_id) { - return -1; - } - // Verify public key wildcard if requested - if (NULL != state->p_key_wildcard_to_verify && - wildcard_id != *state->p_key_wildcard_to_verify) { - return -1; + // push as x-only key (32 = 0x20 bytes) + update_output_u8(state, 0x20); + update_output(state, compressed_pubkey + 1, 32); + + if (i == 0) { + update_output_u8(state, OP_CHECKSIG); + } else { + update_output_u8(state, OP_CHECKSIGADD); + } } - update_output_u8(state, 0x51); - update_output_u8(state, 0x20); + update_output_u8(state, 0x50 + policy->k); // + update_output_op_v(state, OP_NUMEQUAL); // OP_NUMEQUAL - uint8_t parity; - if (0 != crypto_tr_tweak_pubkey(compressed_pubkey + 1, &parity, tweaked_key)) { - return -1; + return 1; +} + +__attribute__((warn_unused_result, noinline)) static int compute_tapleaf_hash( + dispatcher_context_t *dispatcher_context, + const wallet_derivation_info_t *wdi, + const policy_node_t *script_policy, + uint8_t out[static 32]) { + cx_sha256_t hash_context; + crypto_tr_tapleaf_hash_init(&hash_context); + + // we compute the tapscript once just to compute its length + // this avoids having to store the script in memory + int tapscript_len = get_wallet_internal_script_hash(dispatcher_context, + script_policy, + wdi, + WRAPPED_SCRIPT_TYPE_TAPSCRIPT, + NULL); + + if (tapscript_len < 0) { + return WITH_ERROR(-1, "Failed to compute tapleaf script"); } - update_output(state, tweaked_key, 32); + crypto_hash_update_u8(&hash_context.header, 0xC0); + crypto_hash_update_varint(&hash_context.header, tapscript_len); + + if (0 > get_wallet_internal_script_hash(dispatcher_context, + script_policy, + wdi, + WRAPPED_SCRIPT_TYPE_TAPSCRIPT, + &hash_context.header)) { + return WITH_ERROR(-1, "Failed to compute tapscript hash"); // should never happen! + } - result = 2 + 32; + crypto_hash_digest(&hash_context.header, out, 32); + return 0; +} - if (-1 == state_stack_pop(state)) { +// Separated from compute_taptree_hash to optimize its stack usage +__attribute__((warn_unused_result, noinline)) static int compute_and_combine_taptree_child_hashes( + dispatcher_context_t *dc, + const wallet_derivation_info_t *wdi, + const policy_node_tree_t *tree, + uint8_t out[static 32]) { + uint8_t left_h[32], right_h[32]; + if (0 > compute_taptree_hash(dc, wdi, r_policy_node_tree(&tree->left_tree), left_h)) return -1; + if (0 > compute_taptree_hash(dc, wdi, r_policy_node_tree(&tree->right_tree), right_h)) return -1; - } - return result; + crypto_tr_combine_taptree_hashes(left_h, right_h, out); + return 0; } -int call_get_wallet_script(dispatcher_context_t *dispatcher_context, - const policy_node_t *policy, - const uint8_t keys_merkle_root[static 32], - uint32_t n_keys, - bool change, - size_t address_index, - buffer_t *out_buf, - const policy_map_key_wildcard_id_t *p_key_wildcard_to_verify) { - policy_parser_state_t state = {.dispatcher_context = dispatcher_context, - .keys_merkle_root = keys_merkle_root, - .n_keys = n_keys, - .change = change, - .address_index = address_index, - .node_stack_eos = 0, - .p_key_wildcard_to_verify = p_key_wildcard_to_verify}; +// See taproot_tree_helper in BIP-0341 +__attribute__((noinline)) int compute_taptree_hash(dispatcher_context_t *dc, + const wallet_derivation_info_t *wdi, + const policy_node_tree_t *tree, + uint8_t out[static 32]) { + if (tree->is_leaf) + return compute_tapleaf_hash(dc, wdi, r_policy_node(&tree->script), out); + else + return compute_and_combine_taptree_child_hashes(dc, wdi, tree, out); +} - state.nodes[0] = (policy_parser_node_state_t){.mode = MODE_OUT_BYTES, - .step = 0, - .policy_node = policy, - .out_buf = out_buf}; +#pragma GCC diagnostic push +// make sure that the compiler gives an error if any PolicyNodeType is missed +#pragma GCC diagnostic error "-Wswitch-enum" - int ret; - do { - switch (state.nodes[state.node_stack_eos].policy_node->type) { - case TOKEN_PKH: +int get_wallet_script(dispatcher_context_t *dispatcher_context, + const policy_node_t *policy, + const wallet_derivation_info_t *wdi, + uint8_t out[static 34]) { + int script_type = -1; + + cx_sha256_t hash_context; + cx_sha256_init(&hash_context); + + if (policy->type == TOKEN_PKH) { + uint8_t compressed_pubkey[33]; + policy_node_with_key_t *pkh_policy = (policy_node_with_key_t *) policy; + if (0 > get_derived_pubkey(dispatcher_context, + wdi, + r_policy_node_key_placeholder(&pkh_policy->key_placeholder), + compressed_pubkey)) { + return -1; + } + out[0] = OP_DUP; + out[1] = OP_HASH160; + + out[2] = 20; // PUSH 20 bytes + + crypto_hash160(compressed_pubkey, 33, out + 3); + + out[23] = OP_EQUALVERIFY; + out[24] = OP_CHECKSIG; + return 25; + } else if (policy->type == TOKEN_WPKH) { + uint8_t compressed_pubkey[33]; + policy_node_with_key_t *wpkh_policy = (policy_node_with_key_t *) policy; + if (0 > get_derived_pubkey(dispatcher_context, + wdi, + r_policy_node_key_placeholder(&wpkh_policy->key_placeholder), + compressed_pubkey)) { + return -1; + } + out[0] = OP_0; + out[1] = 20; // PUSH 20 bytes + + crypto_hash160(compressed_pubkey, 33, out + 2); + + return 22; + } else if (policy->type == TOKEN_SH || policy->type == TOKEN_WSH) { + const policy_node_t *core_policy; + if (policy->type == TOKEN_SH) { + const policy_node_t *child = + r_policy_node(&((const policy_node_with_script_t *) policy)->script); + if (child->type == TOKEN_WSH) { + script_type = WRAPPED_SCRIPT_TYPE_SH_WSH; + core_policy = r_policy_node(&((const policy_node_with_script_t *) child)->script); + } else { + script_type = WRAPPED_SCRIPT_TYPE_SH; + core_policy = child; + } + } else { // if (policy->type == TOKEN_WSH + script_type = WRAPPED_SCRIPT_TYPE_WSH; + core_policy = r_policy_node(&((const policy_node_with_script_t *) policy)->script); + } + + if (0 > get_wallet_internal_script_hash(dispatcher_context, + core_policy, + wdi, + script_type, + &hash_context.header)) { + return -1; + } + + uint8_t script_hash[32]; + crypto_hash_digest(&hash_context.header, script_hash, 32); + + switch (script_type) { + case WRAPPED_SCRIPT_TYPE_SH: + case WRAPPED_SCRIPT_TYPE_SH_WSH: { + if (script_type == WRAPPED_SCRIPT_TYPE_SH_WSH) { + cx_sha256_init(&hash_context); + crypto_hash_update_u8(&hash_context.header, OP_0); + + crypto_hash_update_u8(&hash_context.header, 32); // PUSH 32 bytes + crypto_hash_update(&hash_context.header, script_hash, 32); + + crypto_hash_digest(&hash_context.header, script_hash, 32); + } + + out[0] = OP_HASH160; + out[1] = 20; // PUSH 20 bytes + + crypto_ripemd160(script_hash, 32, out + 2); + + out[22] = OP_EQUAL; + return 1 + 1 + 20 + 1; + } + case WRAPPED_SCRIPT_TYPE_WSH: { + out[0] = OP_0; + out[1] = 32; // PUSH 32 bytes + + memcpy(out + 2, script_hash, 32); + + return 1 + 1 + 32; + } + default: { + // This should never happen! + return -1; + } + } + } else if (policy->type == TOKEN_TR) { + policy_node_tr_t *tr_policy = (policy_node_tr_t *) policy; + + uint8_t compressed_pubkey[33]; + + if (0 > get_derived_pubkey(dispatcher_context, + wdi, + r_policy_node_key_placeholder(&tr_policy->key_placeholder), + compressed_pubkey)) { + return -1; + } + + out[0] = OP_1; + out[1] = 32; // PUSH 32 bytes + + // uint8_t h[32]; + uint8_t *h = out + 2; // hack: re-use the output array to save memory + + int h_length = 0; + if (!isnull_policy_node_tree(&tr_policy->tree)) { + if (0 > compute_taptree_hash(dispatcher_context, + wdi, + r_policy_node_tree(&tr_policy->tree), + h)) { + return -1; + } + h_length = 32; + } + + uint8_t parity; + if (0 > crypto_tr_tweak_pubkey(compressed_pubkey + 1, h, h_length, &parity, out + 2)) { + return -1; + } + + return 34; + } + + PRINTF("Invalid or unsupported top-level script\n"); + return -1; +} + +__attribute__((noinline)) int get_wallet_internal_script_hash( + dispatcher_context_t *dispatcher_context, + const policy_node_t *policy, + const wallet_derivation_info_t *wdi, + internal_script_type_e script_type, + cx_hash_t *hash_context) { + const uint8_t *whitelist; + size_t whitelist_len; + switch (script_type) { + case WRAPPED_SCRIPT_TYPE_SH: + whitelist = fragment_whitelist_sh; + whitelist_len = sizeof(fragment_whitelist_sh); + break; + case WRAPPED_SCRIPT_TYPE_SH_WSH: + whitelist = fragment_whitelist_sh_wsh; + whitelist_len = sizeof(fragment_whitelist_sh_wsh); + break; + case WRAPPED_SCRIPT_TYPE_WSH: + whitelist = fragment_whitelist_wsh; + whitelist_len = sizeof(fragment_whitelist_wsh); + break; + case WRAPPED_SCRIPT_TYPE_TAPSCRIPT: + whitelist = fragment_whitelist_tapscript; + whitelist_len = sizeof(fragment_whitelist_tapscript); + break; + default: + PRINTF("Unexpected script_type: %d\n", script_type); + return -1; + } + + policy_parser_state_t state = {.dispatcher_context = dispatcher_context, + .wdi = wdi, + .is_taproot = (script_type == WRAPPED_SCRIPT_TYPE_TAPSCRIPT), + .node_stack_eos = 0, + .hash_context = hash_context}; + + state.nodes[0] = + (policy_parser_node_state_t){.length = 0, .flags = 0, .step = 0, .policy_node = policy}; + + int ret; + do { + const policy_parser_node_state_t *node = &state.nodes[state.node_stack_eos]; + + if (node->policy_node == NULL) { + PRINTF("Unexpected uninitialized policy_node\n"); + return -1; + } + + bool is_whitelisted = false; + for (size_t i = 0; i < whitelist_len; i++) { + if (node->policy_node->type == whitelist[i]) { + is_whitelisted = true; + break; + } + } + + if (!is_whitelisted) { + PRINTF("Fragment %d not allowed in script type %d\n", + node->policy_node->type, + script_type); + return -1; + } + + switch (node->policy_node->type) { + case TOKEN_0: + ret = execute_processor(&state, process_generic_node, commands_0); + break; + case TOKEN_1: + ret = execute_processor(&state, process_generic_node, commands_1); + break; + case TOKEN_PK_K: + ret = execute_processor(&state, process_generic_node, commands_pk_k); + break; + case TOKEN_PK_H: + ret = execute_processor(&state, process_generic_node, commands_pk_h); + break; + case TOKEN_PK: + ret = execute_processor(&state, process_generic_node, commands_pk); + break; + case TOKEN_PKH: case TOKEN_WPKH: - ret = process_pkh_wpkh_node(&state); + ret = execute_processor(&state, process_pkh_wpkh_node, NULL); break; - case TOKEN_SH: - case TOKEN_WSH: - ret = process_sh_wsh_node(&state); + case TOKEN_OLDER: + ret = execute_processor(&state, process_generic_node, commands_older); + break; + case TOKEN_AFTER: + ret = execute_processor(&state, process_generic_node, commands_after); + break; + + case TOKEN_SHA256: + ret = execute_processor(&state, process_generic_node, commands_sha256); + break; + case TOKEN_HASH256: + ret = execute_processor(&state, process_generic_node, commands_hash256); + break; + case TOKEN_RIPEMD160: + ret = execute_processor(&state, process_generic_node, commands_ripemd160); + break; + case TOKEN_HASH160: + ret = execute_processor(&state, process_generic_node, commands_hash160); + break; + + case TOKEN_ANDOR: + ret = execute_processor(&state, process_generic_node, commands_andor); + break; + case TOKEN_AND_V: + ret = execute_processor(&state, process_generic_node, commands_and_v); + break; + case TOKEN_AND_B: + ret = execute_processor(&state, process_generic_node, commands_and_b); + break; + case TOKEN_AND_N: + ret = execute_processor(&state, process_generic_node, commands_and_n); + break; + + case TOKEN_OR_B: + ret = execute_processor(&state, process_generic_node, commands_or_b); + break; + case TOKEN_OR_C: + ret = execute_processor(&state, process_generic_node, commands_or_c); + break; + case TOKEN_OR_D: + ret = execute_processor(&state, process_generic_node, commands_or_d); + break; + case TOKEN_OR_I: + ret = execute_processor(&state, process_generic_node, commands_or_i); + break; + + case TOKEN_THRESH: + ret = execute_processor(&state, process_thresh_node, NULL); break; + case TOKEN_MULTI: case TOKEN_SORTEDMULTI: - ret = process_multi_sortedmulti_node(&state); + ret = execute_processor(&state, process_multi_sortedmulti_node, NULL); break; - case TOKEN_TR: - ret = process_tr_node(&state); + case TOKEN_MULTI_A: + case TOKEN_SORTEDMULTI_A: + ret = execute_processor(&state, process_multi_a_sortedmulti_a_node, NULL); + break; + case TOKEN_A: + ret = execute_processor(&state, process_generic_node, commands_a); + break; + case TOKEN_S: + ret = execute_processor(&state, process_generic_node, commands_s); break; + case TOKEN_C: + ret = execute_processor(&state, process_generic_node, commands_c); + break; + case TOKEN_T: + ret = execute_processor(&state, process_generic_node, commands_t); + break; + case TOKEN_D: + ret = execute_processor(&state, process_generic_node, commands_d); + break; + case TOKEN_V: + ret = execute_processor(&state, process_generic_node, commands_v); + break; + case TOKEN_J: + ret = execute_processor(&state, process_generic_node, commands_j); + break; + case TOKEN_N: + ret = execute_processor(&state, process_generic_node, commands_n); + break; + case TOKEN_L: + ret = execute_processor(&state, process_generic_node, commands_l); + break; + case TOKEN_U: + ret = execute_processor(&state, process_generic_node, commands_u); + break; + case TOKEN_TR: + case TOKEN_SH: + case TOKEN_WSH: + PRINTF("Unexpected token type: %d\n", node->policy_node->type); + return -1; + + case TOKEN_INVALID: default: - ret = -1; + PRINTF("Unknown token type: %d\n", node->policy_node->type); + return -1; } } while (ret >= 0 && state.node_stack_eos >= 0); + + if (ret < 0) { + return WITH_ERROR(ret, "Processor failed"); + } + return ret; } -int get_policy_address_type(const policy_node_t *policy) { - // legacy, native segwit, wrapped segwit, or taproot - switch (policy->type) { +#pragma GCC diagnostic pop + +// For a standard descriptor template, return the corresponding BIP44 purpose +// Otherwise, returns -1. +static int get_bip44_purpose(const policy_node_t *descriptor_template) { + const policy_node_key_placeholder_t *kp = NULL; + int purpose = -1; + switch (descriptor_template->type) { case TOKEN_PKH: - return ADDRESS_TYPE_LEGACY; + kp = r_policy_node_key_placeholder( + &((const policy_node_with_key_t *) descriptor_template)->key_placeholder); + purpose = 44; // legacy + break; case TOKEN_WPKH: - return ADDRESS_TYPE_WIT; - case TOKEN_SH: - // wrapped segwit - if (((policy_node_with_script_t *) policy)->script->type == TOKEN_WPKH) { - return ADDRESS_TYPE_SH_WIT; + kp = r_policy_node_key_placeholder( + &((const policy_node_with_key_t *) descriptor_template)->key_placeholder); + purpose = 84; // native segwit + break; + case TOKEN_SH: { + const policy_node_t *inner = + r_policy_node(&((const policy_node_with_script_t *) descriptor_template)->script); + if (inner->type != TOKEN_WPKH) { + return -1; } - return -1; - case TOKEN_TR: - return ADDRESS_TYPE_TR; -#ifdef HAVE_LIQUID - case TOKEN_CT: { - if(liquid_is_blinding_key_acceptable(policy)) { - return get_policy_address_type(((const policy_node_ct_t *)policy)->script); + + kp = r_policy_node_key_placeholder( + &((const policy_node_with_key_t *) inner)->key_placeholder); + purpose = 49; // nested segwit + break; + } + case TOKEN_TR: { + const policy_node_tr_t *tr = (const policy_node_tr_t *) descriptor_template; + if (!isnull_policy_node_tree(&tr->tree)) { + return -1; } - return -1; + + kp = r_policy_node_key_placeholder( + &((const policy_node_tr_t *) descriptor_template)->key_placeholder); + purpose = 86; // standard single-key P2TR + break; } -#endif default: return -1; } + + if (kp->key_index != 0 || kp->num_first != 0 || kp->num_second != 1) { + return -1; + } + + return purpose; +} + +bool is_wallet_policy_standard(dispatcher_context_t *dispatcher_context, + const policy_map_wallet_header_t *wallet_policy_header, + const policy_node_t *descriptor_template) { + // Based on the address type, we set the expected bip44 purpose + int bip44_purpose = get_bip44_purpose(descriptor_template); + if (bip44_purpose < 0) { + PRINTF("Non-standard policy, and no hmac provided\n"); + return false; + } + + if (wallet_policy_header->n_keys != 1) { + PRINTF("Standard wallets must have exactly 1 key\n"); + return false; + } + + // we check if the key is indeed internal + uint32_t master_key_fingerprint = crypto_get_master_key_fingerprint(); + + uint8_t key_info_str[MAX_POLICY_KEY_INFO_LEN]; + int key_info_len = call_get_merkle_leaf_element(dispatcher_context, + wallet_policy_header->keys_info_merkle_root, + wallet_policy_header->n_keys, + 0, // only one key + key_info_str, + sizeof(key_info_str)); + if (key_info_len < 0) { + return false; + } + + // Make a sub-buffer for the pubkey info + buffer_t key_info_buffer = buffer_create(key_info_str, key_info_len); + + policy_map_key_info_t key_info; + if (0 > parse_policy_map_key_info(&key_info_buffer, &key_info, wallet_policy_header->version)) { + return false; + } + + if (!key_info.has_key_origin) { + return false; + } + + if (read_u32_be(key_info.master_key_fingerprint, 0) != master_key_fingerprint) { + return false; + } + + // generate pubkey and check if it matches + serialized_extended_pubkey_t derived_pubkey; + if (0 > get_extended_pubkey_at_path(key_info.master_key_derivation, + key_info.master_key_derivation_len, + BIP32_PUBKEY_VERSION, + &derived_pubkey)) { + PRINTF("Failed to derive pubkey\n"); + return false; + } + + if (memcmp(&key_info.ext_pubkey, &derived_pubkey, sizeof(derived_pubkey)) != 0) { + return false; + } + + // check if derivation path of the key is indeed standard + + // per BIP-0044, derivation must be + // m / purpose' / coin_type' / account' + + const uint32_t H = BIP32_FIRST_HARDENED_CHILD; + if (key_info.master_key_derivation_len != 3 || + key_info.master_key_derivation[0] != H + bip44_purpose || + key_info.master_key_derivation[1] != H + BIP44_COIN_TYPE || + key_info.master_key_derivation[2] < H || + key_info.master_key_derivation[2] > H + MAX_BIP44_ACCOUNT_RECOMMENDED) { + return false; + } + + return true; +} + +bool compute_wallet_hmac(const uint8_t wallet_id[static 32], uint8_t wallet_hmac[static 32]) { + uint8_t key[32]; + + bool result = false; + + if (!crypto_derive_symmetric_key(WALLET_SLIP0021_LABEL, WALLET_SLIP0021_LABEL_LEN, key)) { + goto end; + } + + cx_hmac_sha256(key, sizeof(key), wallet_id, 32, wallet_hmac, 32); + + result = true; + +end: + explicit_bzero(key, sizeof(key)); + + return result; } bool check_wallet_hmac(const uint8_t wallet_id[static 32], const uint8_t wallet_hmac[static 32]) { uint8_t key[32]; uint8_t correct_hmac[32]; - bool ok = crypto_derive_symmetric_key(WALLET_SLIP0021_LABEL, WALLET_SLIP0021_LABEL_LEN, key); + bool result = false; - ok = ok && sizeof(correct_hmac) == cx_hmac_sha256(key, - sizeof(key), - wallet_id, - 32, - correct_hmac, - sizeof(correct_hmac)); + if (!crypto_derive_symmetric_key(WALLET_SLIP0021_LABEL, WALLET_SLIP0021_LABEL_LEN, key)) { + goto end; + } + + cx_hmac_sha256(key, sizeof(key), wallet_id, 32, correct_hmac, 32); // It is important to use a constant-time function to compare the hmac, // to avoid timing-attack that could be exploited to extract it. - ok = ok && 0 == os_secure_memcmp((void *) wallet_hmac, (void *) correct_hmac, 32); + result = os_secure_memcmp((void *) wallet_hmac, (void *) correct_hmac, 32) == 0; +end: explicit_bzero(key, sizeof(key)); explicit_bzero(correct_hmac, sizeof(correct_hmac)); - return ok; -} \ No newline at end of file + return result; +} + +#pragma GCC diagnostic push +// make sure that the compiler gives an error if any PolicyNodeType is missed +#pragma GCC diagnostic error "-Wswitch-enum" + +static int get_key_placeholder_by_index_in_tree(const policy_node_tree_t *tree, + unsigned int i, + const policy_node_t **out_tapleaf_ptr, + policy_node_key_placeholder_t *out_placeholder) { + if (tree->is_leaf) { + int ret = + get_key_placeholder_by_index(r_policy_node(&tree->script), i, NULL, out_placeholder); + if (ret >= 0 && out_tapleaf_ptr != NULL && i < (unsigned) ret) { + *out_tapleaf_ptr = r_policy_node(&tree->script); + } + return ret; + } else { + int ret1 = get_key_placeholder_by_index_in_tree(r_policy_node_tree(&tree->left_tree), + i, + out_tapleaf_ptr, + out_placeholder); + if (ret1 < 0) return -1; + + bool found = i < (unsigned int) ret1; + + int ret2 = get_key_placeholder_by_index_in_tree(r_policy_node_tree(&tree->right_tree), + found ? 0 : i - ret1, + found ? NULL : out_tapleaf_ptr, + found ? NULL : out_placeholder); + if (ret2 < 0) return -1; + + return ret1 + ret2; + } +} + +int get_key_placeholder_by_index(const policy_node_t *policy, + unsigned int i, + const policy_node_t **out_tapleaf_ptr, + policy_node_key_placeholder_t *out_placeholder) { + // make sure that out_placeholder is a valid pointer, if the output is not needed + policy_node_key_placeholder_t tmp; + if (out_placeholder == NULL) { + out_placeholder = &tmp; + } + + switch (policy->type) { + // terminal nodes with absolutely no keys + case TOKEN_0: + case TOKEN_1: + case TOKEN_OLDER: + case TOKEN_AFTER: + case TOKEN_SHA256: + case TOKEN_HASH256: + case TOKEN_RIPEMD160: + case TOKEN_HASH160: + return 0; + + // terminal nodes with exactly 1 key + case TOKEN_PK_K: + case TOKEN_PK_H: + case TOKEN_PK: + case TOKEN_PKH: + case TOKEN_WPKH: { + if (i == 0) { + policy_node_with_key_t *wpkh = (policy_node_with_key_t *) policy; + memcpy(out_placeholder, + r_policy_node_key_placeholder(&wpkh->key_placeholder), + sizeof(policy_node_key_placeholder_t)); + } + return 1; + } + case TOKEN_TR: { + policy_node_tr_t *tr = (policy_node_tr_t *) policy; + if (i == 0) { + memcpy(out_placeholder, + r_policy_node_key_placeholder(&tr->key_placeholder), + sizeof(policy_node_key_placeholder_t)); + } + if (!isnull_policy_node_tree(&tr->tree)) { + int ret_tree = get_key_placeholder_by_index_in_tree( + r_policy_node_tree(&tr->tree), + i == 0 ? 0 : i - 1, + i == 0 ? NULL : out_tapleaf_ptr, + i == 0 ? NULL : out_placeholder); // if i == 0, we already found it; so we + // recur with out_placeholder set to NULL + if (ret_tree < 0) { + return -1; + } + return 1 + ret_tree; + } else { + return 1; + } + } + + // terminal nodes with multiple keys + case TOKEN_MULTI: + case TOKEN_MULTI_A: + case TOKEN_SORTEDMULTI: + case TOKEN_SORTEDMULTI_A: { + const policy_node_multisig_t *node = (const policy_node_multisig_t *) policy; + + if (i < (unsigned int) node->n) { + policy_node_key_placeholder_t *placeholders = + r_policy_node_key_placeholder(&node->key_placeholders); + memcpy(out_placeholder, &placeholders[i], sizeof(policy_node_key_placeholder_t)); + } + + return node->n; + } + + // nodes with a single child script (including miniscript wrappers) + case TOKEN_SH: + case TOKEN_WSH: + case TOKEN_A: + case TOKEN_S: + case TOKEN_C: + case TOKEN_T: + case TOKEN_D: + case TOKEN_V: + case TOKEN_J: + case TOKEN_N: + case TOKEN_L: + case TOKEN_U: { + return get_key_placeholder_by_index( + r_policy_node(&((const policy_node_with_script_t *) policy)->script), + i, + out_tapleaf_ptr, + out_placeholder); + } + + // nodes with exactly two child scripts + case TOKEN_AND_V: + case TOKEN_AND_B: + case TOKEN_AND_N: + case TOKEN_OR_B: + case TOKEN_OR_C: + case TOKEN_OR_D: + case TOKEN_OR_I: { + const policy_node_with_script2_t *node = (const policy_node_with_script2_t *) policy; + int ret1 = get_key_placeholder_by_index(r_policy_node(&node->scripts[0]), + i, + out_tapleaf_ptr, + out_placeholder); + if (ret1 < 0) return -1; + + bool found = i < (unsigned int) ret1; + int ret2 = get_key_placeholder_by_index(r_policy_node(&node->scripts[1]), + found ? 0 : i - ret1, + found ? NULL : out_tapleaf_ptr, + found ? NULL : out_placeholder); + if (ret2 < 0) return -1; + + return ret1 + ret2; + } + + // nodes with exactly three child scripts + case TOKEN_ANDOR: { + const policy_node_with_script3_t *node = (const policy_node_with_script3_t *) policy; + int ret1 = get_key_placeholder_by_index(r_policy_node(&node->scripts[0]), + i, + out_tapleaf_ptr, + out_placeholder); + if (ret1 < 0) return -1; + + bool found = i < (unsigned int) ret1; + int ret2 = get_key_placeholder_by_index(r_policy_node(&node->scripts[1]), + found ? 0 : i - ret1, + found ? NULL : out_tapleaf_ptr, + found ? NULL : out_placeholder); + if (ret2 < 0) return -1; + + found = i < (unsigned int) (ret1 + ret2); + int ret3 = get_key_placeholder_by_index(r_policy_node(&node->scripts[2]), + found ? 0 : i - ret1 - ret2, + found ? NULL : out_tapleaf_ptr, + found ? NULL : out_placeholder); + if (ret3 < 0) return -1; + return ret1 + ret2 + ret3; + } + + // nodes with multiple child scripts + case TOKEN_THRESH: { + const policy_node_thresh_t *node = (const policy_node_thresh_t *) policy; + bool found; + int ret = 0; + policy_node_scriptlist_t *cur_child = r_policy_node_scriptlist(&node->scriptlist); + for (int script_idx = 0; script_idx < node->n; script_idx++) { + LEDGER_ASSERT(cur_child != NULL, + "The script should always have exactly n child scripts"); + + found = i < (unsigned int) ret; + int ret_partial = get_key_placeholder_by_index(r_policy_node(&cur_child->script), + found ? 0 : i - ret, + found ? NULL : out_tapleaf_ptr, + found ? NULL : out_placeholder); + if (ret_partial < 0) return -1; + + ret += ret_partial; + cur_child = r_policy_node_scriptlist(&cur_child->next); + } + return ret; + } + + case TOKEN_INVALID: + default: + PRINTF("Unknown token type: %d\n", policy->type); + return -1; + } + + // unreachable + assert(0); + return -1; +} + +int count_distinct_keys_info(const policy_node_t *policy) { + int ret = -1; + + int n_placeholders = get_key_placeholder_by_index(policy, 0, NULL, NULL); + if (n_placeholders < 0) { + return -1; + } + + for (int cur = 0; cur < n_placeholders; ++cur) { + policy_node_key_placeholder_t placeholder; + if (0 > get_key_placeholder_by_index(policy, cur, NULL, &placeholder)) { + return -1; + } + ret = MAX(ret, placeholder.key_index + 1); + } + return ret; +} + +// Utility function to extract and decode the i-th xpub from the keys information vector +static int get_pubkey_from_merkle_tree(dispatcher_context_t *dispatcher_context, + int wallet_version, + const uint8_t keys_merkle_root[static 32], + uint32_t n_keys, + uint32_t index, + serialized_extended_pubkey_t *out) { + char key_info_str[MAX_POLICY_KEY_INFO_LEN]; + int key_info_len = call_get_merkle_leaf_element(dispatcher_context, + keys_merkle_root, + n_keys, + index, + (uint8_t *) key_info_str, + sizeof(key_info_str)); + if (key_info_len == -1) { + return WITH_ERROR(-1, "Failed to retrieve key info"); + } + + // Make a sub-buffer for the pubkey info + buffer_t key_info_buffer = buffer_create(key_info_str, key_info_len); + + policy_map_key_info_t key_info; + if (parse_policy_map_key_info(&key_info_buffer, &key_info, wallet_version) == -1) { + return WITH_ERROR(-1, "Failed to parse key information"); + } + *out = key_info.ext_pubkey; + return 0; +} + +static int is_miniscript_sane(const policy_node_t *script, MiniscriptContext context) { + if (context != MINISCRIPT_CONTEXT_P2WSH && context != MINISCRIPT_CONTEXT_TAPSCRIPT) { + return WITH_ERROR(-1, "Unknown miniscript context"); + } + if (!script->flags.is_miniscript) { + return WITH_ERROR(-1, "This function can only be called for miniscript"); + } + + // Top level node in miniscript must be type B + if (script->flags.miniscript_type != MINISCRIPT_TYPE_B) { + return WITH_ERROR(-1, "Top level miniscript node must be of type B"); + } + + // check miniscript sanity conditions + policy_node_ext_info_t ext_info; + if (0 > compute_miniscript_policy_ext_info(script, &ext_info, context)) { + return WITH_ERROR(-1, "Error analyzing miniscript policy"); + } + + // Check that non-malleability can be guaranteed + if (!ext_info.m) { + return WITH_ERROR(-1, "Miniscript cannot always be satisfied non-malleably"); + } + + // Check that a signature is always required to satisfy the miniscript + if (!ext_info.s) { + return WITH_ERROR(-1, "Miniscript does not always require a signature"); + } + + // Check that there is no time-lock mix + if (!ext_info.k) { + return WITH_ERROR(-1, "Miniscript with time-lock mix"); + } + + // Note: the following limits could be relaxed for taproot miniscript; however, that + // would mean that tapscripts could run into the maximum stack size limits during + // execution, which we didn't implement explicit checks against. + // Therefore, we rather apply the conservative limit for segwit even to tapscripts. + // We don't expect these limits to be reached in real-world policies. + + // Check the maximum stack size to satisfy the policy + if (ext_info.ss.sat == -1 || (uint32_t) ext_info.ss.sat > MAX_STANDARD_P2WSH_STACK_ITEMS) { + return WITH_ERROR(-1, "Miniscript exceeds maximum standard stack size"); + } + + if (ext_info.ops.sat == -1) { + // Should never happen for non-malleable scripts + return WITH_ERROR(-1, "Invalid maximum ops computations"); + } + + // Check ops limit + if ((uint32_t) ext_info.ops.count + (uint32_t) ext_info.ops.sat > MAX_OPS_PER_SCRIPT) { + return WITH_ERROR(-1, "Miniscript exceeds maximum ops"); + } + + // Check the script size + if (ext_info.script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) { + return WITH_ERROR(-1, "Miniscript exceeds maximum script size"); + } + return 0; +} + +static int is_taptree_miniscript_sane(const policy_node_tree_t *taptree) { + // Recurse until leaves are found, then check sanity if they contain miniscript. + // No check is performed on leaves not containing miniscript. + if (taptree->is_leaf) { + const policy_node_t *script = r_policy_node(&taptree->script); + if (script->flags.is_miniscript && // only check for miniscript leaves + 0 > is_miniscript_sane(script, MINISCRIPT_CONTEXT_TAPSCRIPT)) { + return -1; + } + } else { + if (0 > is_taptree_miniscript_sane(r_policy_node_tree(&taptree->left_tree))) { + return -1; + } + if (0 > is_taptree_miniscript_sane(r_policy_node_tree(&taptree->right_tree))) { + return -1; + } + } + + return 0; +} + +int is_policy_sane(dispatcher_context_t *dispatcher_context, + const policy_node_t *policy, + int wallet_version, + const uint8_t keys_merkle_root[static 32], + uint32_t n_keys) { + if (policy->type == TOKEN_WSH) { + const policy_node_t *inner = + r_policy_node(&((const policy_node_with_script_t *) policy)->script); + if (inner->flags.is_miniscript) { + if (0 > is_miniscript_sane(inner, MINISCRIPT_CONTEXT_P2WSH)) { + return -1; + } + } + } else if (policy->type == TOKEN_TR) { + // if there is a taptree, we check the sanity of every miniscript leaf + const policy_node_tr_t *tr = (const policy_node_tr_t *) policy; + const policy_node_tree_t *taptree = r_policy_node_tree(&tr->tree); + if (taptree != NULL && 0 > is_taptree_miniscript_sane(taptree)) { + return -1; + } + } + + // check that all the xpubs are different + for (unsigned int i = 0; i < n_keys - 1; i++) { // no point in running this for the last key + serialized_extended_pubkey_t pubkey_i; + if (0 > get_pubkey_from_merkle_tree(dispatcher_context, + wallet_version, + keys_merkle_root, + n_keys, + i, + &pubkey_i)) { + return -1; + } + + for (unsigned int j = i + 1; j < n_keys; j++) { + serialized_extended_pubkey_t pubkey_j; + if (0 > get_pubkey_from_merkle_tree(dispatcher_context, + wallet_version, + keys_merkle_root, + n_keys, + j, + &pubkey_j)) { + return -1; + } + + // We reject if any two xpubs have the same pubkey + // Conservatively, we only compare the compressed pubkey, rather than the whole xpub: + // there is no good reason for allowing two different xpubs with the same pubkey. + if (memcmp(pubkey_i.compressed_pubkey, + pubkey_j.compressed_pubkey, + sizeof(pubkey_i.compressed_pubkey)) == 0) { + // duplicated pubkey + return WITH_ERROR(-1, "Repeated pubkey in wallet policy"); + } + } + } + + // check that all the key placeholders for the same xpub do indeed have different + // derivations + int n_placeholders = get_key_placeholder_by_index(policy, 0, NULL, NULL); + if (n_placeholders < 0) { + return WITH_ERROR(-1, "Unexpected error while counting placeholders"); + } + + // The following loop computationally very inefficient (quadratic in the number of + // placeholders), but more efficient solutions likely require a substantial amount of RAM + // (proportional to the number of key placeholders). Instead, this only requires stack depth + // proportional to the depth of the wallet policy's abstract syntax tree. + for (int i = 0; i < n_placeholders - 1; + i++) { // no point in running this for the last placeholder + policy_node_key_placeholder_t kp_i; + if (0 > get_key_placeholder_by_index(policy, i, NULL, &kp_i)) { + return WITH_ERROR(-1, "Unexpected error retrieving placeholders from the policy"); + } + for (int j = i + 1; j < n_placeholders; j++) { + policy_node_key_placeholder_t kp_j; + if (0 > get_key_placeholder_by_index(policy, j, NULL, &kp_j)) { + return WITH_ERROR(-1, "Unexpected error retrieving placeholders from the policy"); + } + + // placeholders for the same key must have disjoint derivation options + if (kp_i.key_index == kp_j.key_index) { + if (kp_i.num_first == kp_j.num_first || kp_i.num_first == kp_j.num_second || + kp_i.num_second == kp_j.num_first || kp_i.num_second == kp_j.num_second) { + return WITH_ERROR(-1, + "Key placeholders with repeated derivations in miniscript"); + } + } + } + } + return 0; +} + +#pragma GCC diagnostic pop diff --git a/src/handler/lib/policy.h b/src/handler/lib/policy.h index 995d7461c..5d9cd751b 100644 --- a/src/handler/lib/policy.h +++ b/src/handler/lib/policy.h @@ -3,51 +3,124 @@ #include "../../boilerplate/dispatcher.h" #include "../../common/wallet.h" +#ifdef HAVE_LIQUID +#include "liquid_policy.h" +#endif + /** - * The label used to derive the symmetric key used to register/verify wallet policies on device. + * Parses a serialized wallet policy, saving the wallet header, the policy map descriptor and the + * policy descriptor. Then, it parses the descriptor into the Abstract Syntax Tree into the + * policy_map_bytes array. + * + * It returns -1 if any error occurs. + * + * @param dispatcher_context Pointer to the dispatcher content + * @param buf Pointer to the buffer from which the serialized policy is read from + * @param wallet_header Pointer to policy_map_wallet_header_t that will receive the policy map + * header + * @param policy_map_descriptor_template Pointer to a buffer of MAX_DESCRIPTOR_TEMPLATE_LENGTH bytes + * that will contain the descriptor template as a string + * @param policy_map_bytes Pointer to an array of bytes that will be used for the parsed abstract + * syntax tree + * @param policy_map_bytes_len Length of policy_map_bytes in bytes. + * @return The memory size of the parsed descriotor template on success, a negative number in case + * of error. */ -#define WALLET_SLIP0021_LABEL "\0LEDGER-Wallet policy" -#define WALLET_SLIP0021_LABEL_LEN \ - (sizeof(WALLET_SLIP0021_LABEL) - 1) // sizeof counts the terminating 0 +// TODO: we should distinguish actual errors from just "policy too big to fit in memory" +__attribute__((warn_unused_result)) int read_and_parse_wallet_policy( + dispatcher_context_t *dispatcher_context, + buffer_t *buf, + policy_map_wallet_header_t *wallet_header, + uint8_t policy_map_descriptor[static MAX_DESCRIPTOR_TEMPLATE_LENGTH], + uint8_t *policy_map_bytes, + size_t policy_map_bytes_len); + +typedef enum { + WRAPPED_SCRIPT_TYPE_SH, + WRAPPED_SCRIPT_TYPE_WSH, + WRAPPED_SCRIPT_TYPE_SH_WSH, + WRAPPED_SCRIPT_TYPE_TAPSCRIPT +} internal_script_type_e; + +// Bundles together some parameters relative to a call to +// get_wallet_script or get_wallet_internal_script_hash +typedef struct { + int wallet_version; // The wallet policy version, either WALLET_POLICY_VERSION_V1 or + // WALLET_POLICY_VERSION_V2 + const uint8_t + *keys_merkle_root; // The Merkle root of the tree of key informations in the policy + uint32_t n_keys; // The number of key information placeholders in the policy + size_t address_index; // The address index to use in the derivation + bool change; // whether a change address or a receive address is derived +} wallet_derivation_info_t; /** - * Computes the script corresponding to a wallet policy, for a certain change and address index. + * Computes the hash of a taptree, to be used as tweak for the internal key per BIP-0341; + * The returned hash is the second value in the tuple returned by taproot_tree_helper in + * BIP-0341, assuming leaf_version 0xC0. * - * If `p_key_wildcard_to_verify` is not NULL, this function assumes it points to a constant which - * must be compared with each of the wallet's public key's wildcard identifier. This parameter is - * optional. If wildcard verification is not required it should be set to NULL. + * @param[in] dispatcher_context + * Pointer to the dispatcher context + * @param[in] wdi + * Pointer to a wallet_derivation_info_t structure containing multiple other parameters + * @param[in] tree + * Pointer to the root of the taptree + * @param[out] out + * A buffer of 32 bytes to receive the output + * + * @return 0 on success, a negative number on failure. + */ +__attribute__((warn_unused_result)) int compute_taptree_hash( + dispatcher_context_t *dispatcher_context, + const wallet_derivation_info_t *wdi, + const policy_node_tree_t *tree, + uint8_t out[static 32]); + +/** + * Computes the script corresponding to a wallet policy, for a certain change and address index. * * @param[in] dispatcher_context * Pointer to the dispatcher context * @param[in] policy * Pointer to the root node of the policy - * @param[in] keys_merkle_root - * The Merkle root of the tree of key informations in the policy - * @param[in] n_keys - * The number of key information placeholders in the policy - * @param[in] change - * 0 for a receive address, 1 for a change address - * @param[in] address_index - * The address index - * @param[out] out_buf - * A buffer to contain the script. If the available space in the buffer is not enough, the result - * is truncated, but the correct length is still returned in case of success. - * @param[in] p_key_wildcard_to_verify - * If not NULL, requests to verify all wallet's public key wildcard IDs to be equal to value, - * pointed by this parameter. + * @param[in] wdi + * Pointer to a wallet_derivation_info_t structure containing multiple other parameters + * @param[out] out + * A buffer of at least 34 bytes to contain the script. The actual length of the output might be + * smaller. * * @return The length of the output on success; -1 in case of error. * */ -int call_get_wallet_script(dispatcher_context_t *dispatcher_context, - const policy_node_t *policy, - const uint8_t keys_merkle_root[static 32], - uint32_t n_keys, - bool change, - size_t address_index, - buffer_t *out_buf, - const policy_map_key_wildcard_id_t *p_key_wildcard_to_verify); +__attribute__((warn_unused_result)) int get_wallet_script(dispatcher_context_t *dispatcher_context, + const policy_node_t *policy, + const wallet_derivation_info_t *wdi, + uint8_t out[static 34]); +/** + * Computes the script corresponding to a wallet policy, for a certain change and address index. + * + * @param[in] dispatcher_context + * Pointer to the dispatcher context + * @param[in] policy + * Pointer to the root node of the policy + * @param[in] wdi + * Pointer to a wallet_derivation_info_t structure containing multiple other parameters + * @param[out] hash_context + * A pointer to an already initialized hash context that will be updated with the bytes from the + * produced script. If NULL, it is ignored. + * + * @return the length of the script on success; a negative number in case of error. + * + */ +__attribute__((warn_unused_result)) int get_wallet_internal_script_hash( + dispatcher_context_t *dispatcher_context, + const policy_node_t *policy, + const wallet_derivation_info_t *wdi, + internal_script_type_e script_type, + cx_hash_t *hash_context); + +// TODO: consuder removing /** * Returns the address type constant corresponding to a standard policy type. * @@ -59,15 +132,104 @@ int call_get_wallet_script(dispatcher_context_t *dispatcher_context, */ int get_policy_address_type(const policy_node_t *policy); +/** + * Returns true if the descriptor template is a standard one. + * Standard wallet policies are single-signature policies as per the following standards: + * - BIP-44 (legacy, P2PKH) + * - BIP-84 (native segwit, P2WPKH) + * - BIP-49 (wrapped segwit, P2SH-P2WPKH) + * - BIP-86 (standard single key P2TR) + * with the standard derivations for the key placeholders, and unhardened steps for the + * change / address_index steps (using 0 for non-change, 1 for change addresses). + * + * @param[in] dispatcher_context + * Pointer to the dispatcher context + * @param[in] wallet_policy_header + * Pointer the wallet policy header + * @param[in] descriptor_template + * Pointer to the root node of the policy + * + * @return true if the descriptor_template is not standard; false if not, or in case of error. + */ +__attribute__((warn_unused_result)) bool is_wallet_policy_standard( + dispatcher_context_t *dispatcher_context, + const policy_map_wallet_header_t *wallet_policy_header, + const policy_node_t *descriptor_template); + +/** + * Computes and returns the wallet_hmac, using the symmetric key derived + * with the WALLET_SLIP0021_LABEL label according to SLIP-0021. + * + * @param[in] wallet_id + * Pointer to the a 32-bytes array containing the 32-byte wallet policy id. + * @param[out] wallet_hmac + * Pointer to the a 32-bytes array containing the wallet policy registration hmac. + * @return true if the given hmac is valid, false otherwise. + */ +bool compute_wallet_hmac(const uint8_t wallet_id[static 32], uint8_t wallet_hmac[static 32]); + /** * Verifies if the wallet_hmac is correct for the given wallet_id, using the symmetric key derived * with the WALLET_SLIP0021_LABEL label according to SLIP-0021. * + * @param[in] wallet_id + * Pointer to the a 32-bytes array containing the 32-byte wallet policy id. + * @param[in] wallet_hmac + * Pointer to the a 32-bytes array containing the expected wallet policy registration hmac. + * @return true if the given hmac is valid, false otherwise. + */ +bool check_wallet_hmac(const uint8_t wallet_id[static 32], const uint8_t wallet_hmac[static 32]); + +/** + * Copies the i-th placeholder (indexing from 0) of the given policy into `out_placeholder` (if not + * null). + * * @param[in] policy * Pointer to the root node of the policy + * @param[in] i + * Index of the wanted placeholder. Ignored if out_placeholder is NULL. + * @param[out] out_tapleaf_ptr + * If not NULL, and if the i-th placeholder is in a tapleaf of the policy, receives the pointer to + * the tapleaf's script. + * @param[out] out_placeholder + * If not NULL, it is a pointer that will receive the i-th placeholder of the policy. + * @return the number of placeholders in the policy on success; -1 in case of error. + */ +__attribute__((warn_unused_result)) int get_key_placeholder_by_index( + const policy_node_t *policy, + unsigned int i, + const policy_node_t **out_tapleaf_ptr, + policy_node_key_placeholder_t *out_placeholder); + +/** + * Determines the expected number of unique keys in the provided policy's key information. + * The function calculates this by finding the maximum key index from placeholders and increments it + * by 1. For instance, if the maximum key index found in the placeholders is `n`, then the result + * would be `n + 1`. + * * @param[in] policy * Pointer to the root node of the policy + * @return the expected number of items in the keys information vector; -1 in case of error. + */ +__attribute__((warn_unused_result)) int count_distinct_keys_info(const policy_node_t *policy); - * @return true if the given hmac is valid, false otherwise. +/** + * Checks if a wallet policy is sane, verifying that pubkeys are never repeated and (if miniscript) + * that the miniscript is "sane". + * @param[in] dispatcher_context + * Pointer to the dispatcher context + * @param[in] policy + * Pointer to the root node of the policy + * @param[in] wallet_version + * The version of the wallet policy (since it affects the format of keys in the vector of keys) + * @param[in] keys_merkle_root + * The root of the Merkle tree of the vector of keys information in the wallet policy + * @param[in] n_keys + * The number of keys in the vector of keys + * @return 0 on success; -1 in case of error. */ -bool check_wallet_hmac(const uint8_t wallet_id[static 32], const uint8_t wallet_hmac[static 32]); +__attribute__((warn_unused_result)) int is_policy_sane(dispatcher_context_t *dispatcher_context, + const policy_node_t *policy, + int wallet_version, + const uint8_t keys_merkle_root[static 32], + uint32_t n_keys); \ No newline at end of file diff --git a/src/handler/lib/psbt_parse_rawtx.c b/src/handler/lib/psbt_parse_rawtx.c index 2098f0bea..bde4ac242 100644 --- a/src/handler/lib/psbt_parse_rawtx.c +++ b/src/handler/lib/psbt_parse_rawtx.c @@ -530,7 +530,7 @@ int call_psbt_parse_rawtx(dispatcher_context_t *dispatcher_context, int key_len, int output_index, txid_parser_outputs_t *outputs) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); cx_sha256_t hash_context; cx_sha256_init(&hash_context); diff --git a/src/handler/lib/stream_merkle_leaf_element.c b/src/handler/lib/stream_merkle_leaf_element.c index 40d1ab6c5..751e0b02e 100644 --- a/src/handler/lib/stream_merkle_leaf_element.c +++ b/src/handler/lib/stream_merkle_leaf_element.c @@ -11,7 +11,7 @@ int call_stream_merkle_leaf_element(dispatcher_context_t *dispatcher_context, void (*len_callback)(size_t, void *), void (*callback)(buffer_t *, void *), void *callback_state) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t leaf_hash[32]; int res = call_get_merkle_leaf_hash(dispatcher_context, diff --git a/src/handler/lib/stream_merkleized_map_value.c b/src/handler/lib/stream_merkleized_map_value.c index bc263ef2e..0c7c44a6a 100644 --- a/src/handler/lib/stream_merkleized_map_value.c +++ b/src/handler/lib/stream_merkleized_map_value.c @@ -9,7 +9,7 @@ int call_stream_merkleized_map_value(dispatcher_context_t *dispatcher_context, void (*len_callback)(size_t, void *), void (*callback)(buffer_t *, void *), void *callback_state) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t key_merkle_hash[32]; merkle_compute_element_hash(key, key_len, key_merkle_hash); diff --git a/src/handler/lib/stream_preimage.c b/src/handler/lib/stream_preimage.c index 37b8e5e44..cd6a0c795 100644 --- a/src/handler/lib/stream_preimage.c +++ b/src/handler/lib/stream_preimage.c @@ -11,7 +11,7 @@ int call_stream_preimage(dispatcher_context_t *dispatcher_context, void (*len_callback)(size_t, void *), void (*callback)(buffer_t *, void *), void *callback_state) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); uint8_t cmd = CCMD_GET_PREIMAGE; dispatcher_context->add_to_response(&cmd, 1); @@ -90,14 +90,13 @@ int call_stream_preimage(dispatcher_context_t *dispatcher_context, return -8; } - uint8_t *data_ptr2 = - dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset; + data_ptr = dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset; // update hash - crypto_hash_update(&hash_context.header, data_ptr2, n_bytes); + crypto_hash_update(&hash_context.header, data_ptr, n_bytes); // call callback with data - buffer_t buf = buffer_create(data_ptr2, n_bytes); + buffer_t buf = buffer_create(data_ptr, n_bytes); callback(&buf, callback_state); bytes_remaining -= n_bytes; diff --git a/src/handler/liquid_get_blinding_key.c b/src/handler/liquid_get_blinding_key.c index eb73b353c..f3a60bc2a 100644 --- a/src/handler/liquid_get_blinding_key.c +++ b/src/handler/liquid_get_blinding_key.c @@ -1,19 +1,4 @@ -/***************************************************************************** - * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *****************************************************************************/ +#ifdef HAVE_LIQUID #include #include @@ -65,3 +50,5 @@ void handler_liquid_get_blinding_key(dispatcher_context_t *dc) { SEND_RESPONSE(dc, blinding_key, sizeof(blinding_key), SW_OK); } } + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/handler/liquid_get_blinding_key.h b/src/handler/liquid_get_blinding_key.h index 542c6064d..0cad80a18 100644 --- a/src/handler/liquid_get_blinding_key.h +++ b/src/handler/liquid_get_blinding_key.h @@ -1,4 +1,5 @@ #pragma once +#ifdef HAVE_LIQUID #include "../boilerplate/dispatcher.h" @@ -15,3 +16,5 @@ typedef struct { * Dispatcher context. */ void handler_liquid_get_blinding_key(dispatcher_context_t *dispatcher_context); + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/handler/liquid_get_master_blinding_key.c b/src/handler/liquid_get_master_blinding_key.c index f2713e960..e8ebf5a8b 100644 --- a/src/handler/liquid_get_master_blinding_key.c +++ b/src/handler/liquid_get_master_blinding_key.c @@ -1,19 +1,4 @@ -/***************************************************************************** - * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *****************************************************************************/ +#ifdef HAVE_LIQUID #include #include @@ -44,3 +29,5 @@ void handler_liquid_get_master_blinding_key(dispatcher_context_t *dc) { SEND_RESPONSE(dc, mbk, sizeof(mbk), SW_OK); } } + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/handler/liquid_get_master_blinding_key.h b/src/handler/liquid_get_master_blinding_key.h index 360e97ea8..83ad7dd58 100644 --- a/src/handler/liquid_get_master_blinding_key.h +++ b/src/handler/liquid_get_master_blinding_key.h @@ -1,4 +1,5 @@ #pragma once +#ifdef HAVE_LIQUID #include "../boilerplate/dispatcher.h" @@ -15,3 +16,5 @@ typedef struct { * Dispatcher context. */ void handler_liquid_get_master_blinding_key(dispatcher_context_t *dispatcher_context); + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/handler/liquid_sign_pset.c b/src/handler/liquid_sign_pset.c index 95ad4f296..ea5d1660c 100644 --- a/src/handler/liquid_sign_pset.c +++ b/src/handler/liquid_sign_pset.c @@ -1,20 +1,3 @@ -/***************************************************************************** - * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *****************************************************************************/ - /* Current assumptions during signing: 1) exactly one of the keys in the wallet is internal (enforce during wallet registration) diff --git a/src/handler/register_wallet.c b/src/handler/register_wallet.c index 9f390cbe5..10d975d1c 100644 --- a/src/handler/register_wallet.c +++ b/src/handler/register_wallet.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ #include "../boilerplate/dispatcher.h" #include "../boilerplate/sw.h" +#include "../common/bip32.h" #include "../common/merkle.h" #include "../common/read.h" #include "../common/wallet.h" @@ -34,236 +35,207 @@ #include "../ui/display.h" #include "../ui/menu.h" +#include "lib/get_merkle_leaf_element.h" +#include "lib/get_preimage.h" #include "lib/policy.h" #include "client_commands.h" -#include "register_wallet.h" +#include "handlers.h" -/** - * Receives and parses the public key info asking the user to validate it. - * - * @param[in,out] dc - * Dispatcher context. - */ -static void process_cosigner_info(dispatcher_context_t *dc); - -/** - * Goes to the next public key until all are processed. - * - * @param[in,out] dc - * Dispatcher context. - */ -static void next_cosigner(dispatcher_context_t *dc); - -/** - * Finalizes and sends response to the REGISTER_WALLET command. - * - * @param[in,out] dc - * Dispatcher context. - */ -static void finalize_response(dispatcher_context_t *dc); +#ifdef HAVE_LIQUID +#include "liquid.h" +#endif -/** - * Checks whether wallet policy is acceptable. - * - * @param[in] policy - * Pointer to root policy node. - * - * @return true on success, false on error. - */ static bool is_policy_acceptable(const policy_node_t *policy); - -/** - * Checks whether wallet policy name is acceptable. - * - * @param[in] name - * Wallet name, may not be null-terminated. - * @param name_len - * Number of characters in wallet name. - * - * @return true on success, false on error. - */ static bool is_policy_name_acceptable(const char *name, size_t name_len); +static const uint8_t BIP0341_NUMS_PUBKEY[] = {0x02, 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, + 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e, 0x07, + 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, + 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0}; + /** * Validates the input, initializes the hash context and starts accumulating the wallet header in * it. */ -void handler_register_wallet(dispatcher_context_t *dc) { - register_wallet_state_t *state = (register_wallet_state_t *) &G_command_state; +void handler_register_wallet(dispatcher_context_t *dc, uint8_t protocol_version) { + (void) protocol_version; - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); - // Device must be unlocked - if (os_global_pin_is_validated() != BOLOS_UX_OK) { - SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); - return; - } + policy_map_wallet_header_t wallet_header; + + uint8_t wallet_id[32]; + union { + uint8_t bytes[MAX_WALLET_POLICY_BYTES]; + policy_node_t parsed; + } policy_map; + + size_t n_internal_keys = 0; uint64_t serialized_policy_map_len; if (!buffer_read_varint(&dc->read_buffer, &serialized_policy_map_len)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); return; } - if (serialized_policy_map_len > MAX_POLICY_MAP_SERIALIZED_LENGTH) { - PRINTF("Policy map too long\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - if ((read_policy_map_wallet(&dc->read_buffer, &state->wallet_header)) < 0) { - PRINTF("Failed reading policy map\n"); + uint8_t policy_map_descriptor[MAX_DESCRIPTOR_TEMPLATE_LENGTH]; + if (0 > read_and_parse_wallet_policy(dc, + &dc->read_buffer, + &wallet_header, + policy_map_descriptor, + policy_map.bytes, + sizeof(policy_map.bytes))) { SEND_SW(dc, SW_INCORRECT_DATA); return; } - buffer_t policy_map_buffer = - buffer_create(&state->wallet_header.policy_map, state->wallet_header.policy_map_len); - if (parse_policy_map(&policy_map_buffer, - state->policy_map_bytes, - sizeof(state->policy_map_bytes), - BIP32_PUBKEY_VERSION, - BIP32_PRIVKEY_VERSION) < 0) { - PRINTF("Failed parsing policy map\n"); + if (count_distinct_keys_info(&policy_map.parsed) != (int) wallet_header.n_keys) { + PRINTF("Number of keys in descriptor template doesn't provided keys\n"); SEND_SW(dc, SW_INCORRECT_DATA); return; } // Compute the wallet id (sha256 of the serialization) - get_policy_wallet_id(&state->wallet_header, state->wallet_id); + get_policy_wallet_id(&wallet_header, wallet_id); // Verify that the name is acceptable - if (!is_policy_name_acceptable(state->wallet_header.name, state->wallet_header.name_len)) { + 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); return; } - // check if policy is acceptable; only multisig is accepted at this time, - // and it must be one of the accepted patterns. - if (!is_policy_acceptable(&state->policy_map)) { + // check if policy is acceptable + if (!is_policy_acceptable(&policy_map.parsed)) { + PRINTF("Policy is not acceptable\n"); + SEND_SW(dc, SW_NOT_SUPPORTED); return; } - state->master_key_fingerprint = crypto_get_master_key_fingerprint(); - - state->next_pubkey_index = 0; + // make sure that the policy is sane (especially if it contains miniscript) + if (0 > is_policy_sane(dc, + &policy_map.parsed, + wallet_header.version, + wallet_header.keys_info_merkle_root, + wallet_header.n_keys)) { + PRINTF("Policy is not sane\n"); - ui_display_wallet_header(dc, &state->wallet_header, process_cosigner_info); -} - -/** - * Receives and parses the next pubkey info. - * Asks the user to validate the pubkey info. - */ -static void process_cosigner_info(dispatcher_context_t *dc) { - register_wallet_state_t *state = (register_wallet_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - int pubkey_info_len = call_get_merkle_leaf_element(dc, - state->wallet_header.keys_info_merkle_root, - state->wallet_header.n_keys, - state->next_pubkey_index, - state->next_pubkey_info, - MAX_POLICY_KEY_INFO_LEN); - - if (pubkey_info_len < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW(dc, SW_NOT_SUPPORTED); return; } - state->next_pubkey_info[pubkey_info_len] = 0; - - // Make a sub-buffer for the pubkey info - buffer_t key_info_buffer = buffer_create(state->next_pubkey_info, pubkey_info_len); - - policy_map_key_info_t key_info; - if (parse_policy_map_key_info(&key_info_buffer, &key_info) == -1) { - PRINTF("Incorrect policy map.\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - if (!validate_policy_map_extended_pubkey(&key_info, BIP32_PUBKEY_VERSION)) { - SEND_SW(dc, SW_INCORRECT_DATA); + if (!ui_display_register_wallet(dc, &wallet_header, (char *) policy_map_descriptor)) { + SEND_SW(dc, SW_DENY); + ui_post_processing_confirm_wallet_registration(dc, false); return; } - // We refuse to register wallets without key origin information, or whose keys don't end with - // the wildcard ('/**'). The key origin information is necessary when signing to identify which - // one is our key. Using addresses without a wildcard could potentially be supported, but - // disabled for now (question to address: can only _some_ of the keys have a wildcard?). + uint32_t master_key_fingerprint = crypto_get_master_key_fingerprint(); + + for (size_t cosigner_index = 0; cosigner_index < wallet_header.n_keys; cosigner_index++) { + /** + * Receives and parses the next pubkey info. + * Asks the user to validate the pubkey info. + */ + + uint8_t next_pubkey_info[MAX_POLICY_KEY_INFO_LEN + 1]; + int pubkey_info_len = call_get_merkle_leaf_element(dc, + wallet_header.keys_info_merkle_root, + wallet_header.n_keys, + cosigner_index, + next_pubkey_info, + MAX_POLICY_KEY_INFO_LEN); + + if (pubkey_info_len < 0) { + SEND_SW(dc, SW_INCORRECT_DATA); + ui_post_processing_confirm_wallet_registration(dc, false); + return; + } - if (!key_info.has_key_origin) { - PRINTF("Key info without origin unsupported.\n"); - SEND_SW(dc, SW_NOT_SUPPORTED); - return; - } + next_pubkey_info[pubkey_info_len] = 0; - if (KEY_WILDCARD_NONE == key_info.wildcard_id) { - PRINTF("Key info without wildcard unsupported.\n"); - SEND_SW(dc, SW_NOT_SUPPORTED); - return; - } + // Make a sub-buffer for the pubkey info + buffer_t key_info_buffer = buffer_create(next_pubkey_info, pubkey_info_len); - bool is_key_internal = false; - if (read_u32_be(key_info.master_key_fingerprint, 0) == state->master_key_fingerprint) { - // it could be a collision on the fingerprint; we verify that we can actually generate the - // same pubkey - char pubkey_derived[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; - int serialized_pubkey_len = - get_serialized_extended_pubkey_at_path(key_info.master_key_derivation, - key_info.master_key_derivation_len, - BIP32_PUBKEY_VERSION, - pubkey_derived); - if (serialized_pubkey_len == -1) { - SEND_SW(dc, SW_BAD_STATE); + policy_map_key_info_t key_info; + if (parse_policy_map_key_info(&key_info_buffer, &key_info, wallet_header.version) == -1) { + PRINTF("Incorrect policy map.\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + ui_post_processing_confirm_wallet_registration(dc, false); return; } - if (strncmp(key_info.ext_pubkey, pubkey_derived, MAX_SERIALIZED_PUBKEY_LENGTH) == 0) { - is_key_internal = true; - ++state->n_internal_keys; + if (read_u32_be(key_info.ext_pubkey.version, 0) != BIP32_PUBKEY_VERSION) { + PRINTF("Invalid pubkey version. Wrong network?\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + return; } - } - ui_display_policy_map_cosigner_pubkey(dc, - (char *) state->next_pubkey_info, - state->next_pubkey_index, // 1-indexed for the UI - state->wallet_header.n_keys, - is_key_internal, - next_cosigner); -} + // We refuse to register wallets without key origin information, or whose keys don't end + // with the wildcard ('/**'). The key origin information is necessary when signing to + // identify which one is our key. Using addresses without a wildcard could potentially be + // supported, but disabled for now (question to address: can only _some_ of the keys have a + // wildcard?). -static void next_cosigner(dispatcher_context_t *dc) { - register_wallet_state_t *state = (register_wallet_state_t *) &G_command_state; + key_type_e key_type; - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + if (memcmp(key_info.ext_pubkey.compressed_pubkey, + BIP0341_NUMS_PUBKEY, + sizeof(BIP0341_NUMS_PUBKEY)) == 0) { + // this public key is known to be unspendable + key_type = PUBKEY_TYPE_UNSPENDABLE; + } else { + key_type = PUBKEY_TYPE_EXTERNAL; + + // if there is key origin information and the fingerprint matches, we make sure it's not + // a false positive (it could be wrong info, or a collision). + if (key_info.has_key_origin && + read_u32_be(key_info.master_key_fingerprint, 0) == master_key_fingerprint) { + // we verify that we can actually generate the same pubkey + serialized_extended_pubkey_t pubkey_derived; + int serialized_pubkey_len = + get_extended_pubkey_at_path(key_info.master_key_derivation, + key_info.master_key_derivation_len, + BIP32_PUBKEY_VERSION, + &pubkey_derived); + if (serialized_pubkey_len == -1) { + SEND_SW(dc, SW_BAD_STATE); + ui_post_processing_confirm_wallet_registration(dc, false); + return; + } + + if (memcmp(&key_info.ext_pubkey, &pubkey_derived, sizeof(pubkey_derived)) == 0) { + key_type = PUBKEY_TYPE_INTERNAL; + ++n_internal_keys; + } + } + } - ++state->next_pubkey_index; - if (state->next_pubkey_index < state->wallet_header.n_keys) { - dc->next(process_cosigner_info); - } else { - dc->next(finalize_response); + if (!ui_display_policy_map_cosigner_pubkey(dc, + (char *) next_pubkey_info, + cosigner_index, // 1-indexed for the UI + wallet_header.n_keys, + key_type)) { + SEND_SW(dc, SW_DENY); + return; + } } -} -static void finalize_response(dispatcher_context_t *dc) { - register_wallet_state_t *state = (register_wallet_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - if (state->n_internal_keys != 1) { - // Unclear if there is any use case for multiple internal keys in the same wallet. + if (n_internal_keys < 1) { + // Unclear if there is any use case for registering policies with no internal keys. // We disallow that, might reconsider in future versions if needed. - SEND_SW(dc, SW_NOT_SUPPORTED); + PRINTF("Wallet policy with no internal keys\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + ui_post_processing_confirm_wallet_registration(dc, false); return; - } - - // Ensure the device is unlocked before outputting the response - if (os_global_pin_is_validated() != BOLOS_UX_OK) { - SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); + } 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 + PRINTF("V1 policies must have exactly 1 internal key\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + ui_post_processing_confirm_wallet_registration(dc, false); return; } @@ -272,7 +244,7 @@ static void finalize_response(dispatcher_context_t *dc) { uint8_t hmac[32]; } response; - memcpy(response.wallet_id, state->wallet_id, sizeof(response.wallet_id)); + memcpy(response.wallet_id, wallet_id, sizeof(wallet_id)); // TODO: we might want to add external info to be committed with the signature (e.g.: app // version). @@ -283,65 +255,33 @@ static void finalize_response(dispatcher_context_t *dc) { // And the signature would be on the concatenation of the wallet id and the metadata. // The client must persist the metadata, together with the signature. - { - // sign wallet id and produce response - uint8_t key[32]; - bool ok = crypto_derive_symmetric_key(WALLET_SLIP0021_LABEL, - WALLET_SLIP0021_LABEL_LEN, - (uint8_t*)key); - - ok = ok && sizeof(response.hmac) == cx_hmac_sha256((uint8_t*)key, - sizeof(key), - state->wallet_id, - sizeof(state->wallet_id), - response.hmac, - sizeof(response.hmac)); - - explicit_bzero((uint8_t*)key, sizeof(key)); - - if (!ok) { - SEND_SW(dc, SW_BAD_STATE); - return; - } - } + compute_wallet_hmac(wallet_id, response.hmac); SEND_RESPONSE(dc, &response, sizeof(response), SW_OK); + ui_post_processing_confirm_wallet_registration(dc, true); } static bool is_policy_acceptable(const policy_node_t *policy) { - policy_node_t *internal_script; - - if (policy->type == TOKEN_SH) { - policy_node_t *child_node = ((policy_node_with_script_t *) policy)->script; - if (child_node->type == TOKEN_WSH) { - // sh(wsh({sorted}multi(@0))) - internal_script = ((policy_node_with_script_t *) child_node)->script; - } else { - // sh({sorted}multi(@0)) - internal_script = child_node; - } - } else if (policy->type == TOKEN_WSH) { - // wsh({sorted}multi(@0)) - internal_script = ((policy_node_with_script_t *) policy)->script; - } else if (policy->type == TOKEN_CT) { + PolicyNodeType policy_type = policy->type; #ifdef HAVE_LIQUID + if (policy->type == TOKEN_CT) { // ct(, ) if(liquid_is_blinding_key_acceptable(policy)) { - internal_script = ((policy_node_ct_t *) policy)->script; - return is_policy_acceptable(internal_script); + policy_type = ((policy_node_ct_t *)policy)->script->type; + } + else { + return false; } -#endif // HAVE_LIQUID - return false; // unexpected policy - } else { - return false; // unexpected policy } +#endif // HAVE_LIQUID - return internal_script->type == TOKEN_MULTI || internal_script->type == TOKEN_SORTEDMULTI; + return policy_type == TOKEN_PKH || policy_type == TOKEN_WPKH || policy_type == TOKEN_SH || + policy_type == TOKEN_WSH || policy_type == TOKEN_TR; } static bool is_policy_name_acceptable(const char *name, size_t name_len) { - // between 1 and MAX_POLICY_MAP_NAME_LENGTH characters - if (name_len == 0 || name_len > MAX_POLICY_MAP_NAME_LENGTH) return false; + // between 1 and MAX_WALLET_NAME_LENGTH characters + if (name_len == 0 || name_len > MAX_WALLET_NAME_LENGTH) return false; // first and last characters must not be whitespace if (name[0] == ' ' || name[name_len - 1] == ' ') return false; @@ -351,4 +291,4 @@ static bool is_policy_name_acceptable(const char *name, size_t name_len) { if (name[i] < 0x20 || name[i] > 0x7E) return false; return true; -} +} \ No newline at end of file diff --git a/src/handler/register_wallet.h b/src/handler/register_wallet.h deleted file mode 100644 index fb03e704f..000000000 --- a/src/handler/register_wallet.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "../crypto.h" -#include "../common/bip32.h" -#include "../common/wallet.h" -#include "../boilerplate/dispatcher.h" - -#include "lib/get_merkle_leaf_element.h" - -/// State of REGISTER_WALLET handler -typedef struct { - /// Machine context for command dispatcher - machine_context_t ctx; - - /// Wallet header with basic information from wallet policy - policy_map_wallet_header_t wallet_header; - - /// Wallet identifier - uint8_t wallet_id[32]; - union { - /// Wallet policy map as array of bytes - uint8_t policy_map_bytes[MAX_POLICY_MAP_BYTES]; - /// Wallet policy map as a structure - policy_node_t policy_map; - }; - /// Number of internal keys - size_t n_internal_keys; - /// Master key fingerprint - uint32_t master_key_fingerprint; - - /// Zero-based public key index - uint8_t next_pubkey_index; - /// Public key information, a null-terminated string - uint8_t next_pubkey_info[MAX_POLICY_KEY_INFO_LEN + 1]; -} register_wallet_state_t; - -/** - * Handles REGISTER_WALLET command. - * - * @param[in,out] dispatcher_context - * Dispatcher context. - */ -void handler_register_wallet(dispatcher_context_t *dispatcher_context); diff --git a/src/handler/sign_message.c b/src/handler/sign_message.c index 281b11c88..e45a7dc94 100644 --- a/src/handler/sign_message.c +++ b/src/handler/sign_message.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,134 +20,226 @@ #include "boilerplate/io.h" #include "boilerplate/dispatcher.h" #include "boilerplate/sw.h" +#include "../common/bip32.h" #include "../commands.h" #include "../constants.h" #include "../crypto.h" #include "../ui/display.h" #include "../ui/menu.h" +#include "lib/get_merkle_leaf_element.h" -/** - * Signs Bitcoin Message Signing digest and sends response. - * - * @param[in,out] dc - * Dispatcher context. - */ -static void send_response(dispatcher_context_t *dc); +#include "handlers.h" + +#define MAX_DISPLAYBLE_CHUNK_NUMBER \ + (5 * MESSAGE_CHUNK_PER_DISPLAY) // If the message is too long we will not display it /// Magic prefix for Bitcoin Message Signing digest static unsigned char const BSM_SIGN_MAGIC[] = {'\x18', 'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 'S', 'i', 'g', 'n', 'e', 'd', ' ', 'M', 'e', 's', 's', 'a', 'g', 'e', ':', '\n'}; -void handler_sign_message(dispatcher_context_t *dc) { - sign_message_state_t *state = (sign_message_state_t *) &G_command_state; +static bool display_message_content_and_confirm(dispatcher_context_t* dc, + uint8_t* message_merkle_root, + size_t n_chunks, + uint8_t* path_str) { + reset_streaming_index(); + while (get_streaming_index() <= (n_chunks - 1) / MESSAGE_CHUNK_PER_DISPLAY) { + uint8_t message_chunk[MESSAGE_MAX_DISPLAY_SIZE]; + + int total_chunk_len = 0; + uint8_t offset = 0; + + if (get_streaming_index() > 0) { + message_chunk[offset++] = '.'; + message_chunk[offset++] = '.'; + message_chunk[offset++] = '.'; + } + + total_chunk_len += offset; - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + for (int j = 0; j < MESSAGE_CHUNK_PER_DISPLAY; j++) { + offset += j * MESSAGE_CHUNK_SIZE; - // Device must be unlocked - if (os_global_pin_is_validated() != BOLOS_UX_OK) { - SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); - return; + int chunk_len = + call_get_merkle_leaf_element(dc, + message_merkle_root, + n_chunks, + get_streaming_index() * MESSAGE_CHUNK_PER_DISPLAY + j, + message_chunk + offset, + MESSAGE_CHUNK_SIZE); + + total_chunk_len += chunk_len; + + if (chunk_len < MESSAGE_CHUNK_SIZE) { + break; + } + } + + if ((get_streaming_index() + 1) * MESSAGE_CHUNK_PER_DISPLAY < n_chunks) { + message_chunk[total_chunk_len] = '.'; + message_chunk[total_chunk_len + 1] = '.'; + message_chunk[total_chunk_len + 2] = '.'; + message_chunk[total_chunk_len + 3] = '\0'; + } else { + message_chunk[total_chunk_len] = '\0'; + } + + if (!ui_display_path_and_message_content(dc, + (char*) path_str, + (char*) message_chunk, + (n_chunks - 1) / MESSAGE_CHUNK_PER_DISPLAY)) { + return false; + } + } + + if (!ui_display_message_confirm(dc)) { + return false; } - if (!buffer_read_u8(&dc->read_buffer, &state->bip32_path_len) || - !buffer_read_bip32_path(&dc->read_buffer, state->bip32_path, state->bip32_path_len) || - !buffer_read_varint(&dc->read_buffer, &state->message_length) || - !buffer_read_bytes(&dc->read_buffer, state->message_merkle_root, 32)) { + return true; +} + +void handler_sign_message(dispatcher_context_t* dc, uint8_t protocol_version) { + (void) protocol_version; + + uint8_t bip32_path_len; + uint32_t bip32_path[MAX_BIP32_PATH_STEPS]; + uint64_t message_length; + uint8_t message_merkle_root[32]; + bool printable = true; + + if (!buffer_read_u8(&dc->read_buffer, &bip32_path_len) || + !buffer_read_bip32_path(&dc->read_buffer, bip32_path, bip32_path_len) || + !buffer_read_varint(&dc->read_buffer, &message_length) || + !buffer_read_bytes(&dc->read_buffer, message_merkle_root, 32)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); return; } - if (state->bip32_path_len > MAX_BIP32_PATH_STEPS || state->message_length >= (1LL << 32)) { + if (bip32_path_len > MAX_BIP32_PATH_STEPS || message_length >= (1LL << 32)) { SEND_SW(dc, SW_INCORRECT_DATA); return; } char path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1] = "(Master key)"; - if (state->bip32_path_len > 0) { - bip32_path_format(state->bip32_path, state->bip32_path_len, path_str, sizeof(path_str)); + if (bip32_path_len > 0) { + bip32_path_format(bip32_path, bip32_path_len, path_str, sizeof(path_str)); } - cx_sha256_init(&state->msg_hash_context); - cx_sha256_init(&state->bsm_digest_context); + cx_sha256_t msg_hash_context; // used to compute sha256(message) + cx_sha256_t bsm_digest_context; // used to compute the Bitcoin Message Signing digest + cx_sha256_init(&msg_hash_context); + cx_sha256_init(&bsm_digest_context); + + crypto_hash_update(&bsm_digest_context.header, BSM_SIGN_MAGIC, sizeof(BSM_SIGN_MAGIC)); + crypto_hash_update_varint(&bsm_digest_context.header, message_length); - crypto_hash_update(&state->bsm_digest_context.header, BSM_SIGN_MAGIC, sizeof(BSM_SIGN_MAGIC)); - crypto_hash_update_varint(&state->bsm_digest_context.header, state->message_length); + size_t n_chunks = (message_length + MESSAGE_CHUNK_SIZE - 1) / MESSAGE_CHUNK_SIZE; + + if (n_chunks > MAX_DISPLAYBLE_CHUNK_NUMBER) { + printable = false; + } - size_t n_chunks = (state->message_length + 63) / 64; for (unsigned int i = 0; i < n_chunks; i++) { - uint8_t message_chunk[64]; + uint8_t message_chunk[MESSAGE_CHUNK_SIZE]; int chunk_len = call_get_merkle_leaf_element(dc, - state->message_merkle_root, + message_merkle_root, n_chunks, i, message_chunk, sizeof(message_chunk)); - if (chunk_len < 0 || (chunk_len != 64 && i != n_chunks - 1)) { + if (chunk_len < 0 || (chunk_len != MESSAGE_CHUNK_SIZE && i != n_chunks - 1)) { SEND_SW(dc, SW_BAD_STATE); // should never happen return; } - crypto_hash_update(&state->msg_hash_context.header, message_chunk, chunk_len); - crypto_hash_update(&state->bsm_digest_context.header, message_chunk, chunk_len); - } - - crypto_hash_digest(&state->msg_hash_context.header, state->message_hash, 32); - crypto_hash_digest(&state->bsm_digest_context.header, state->bsm_digest, 32); - cx_hash_sha256(state->bsm_digest, 32, state->bsm_digest, 32); - - char message_hash_str[64 + 1]; - for (int i = 0; i < 32; i++) { - snprintf(message_hash_str + 2 * i, 3, "%02X", state->message_hash[i]); + if (printable) { + for (int j = 0; j < chunk_len; j++) { + if (message_chunk[j] < 0x20 || message_chunk[j] > 0x7E) { + printable = false; + break; + } + } + } + crypto_hash_update(&msg_hash_context.header, message_chunk, chunk_len); + crypto_hash_update(&bsm_digest_context.header, message_chunk, chunk_len); } - ui_display_message_hash(dc, path_str, message_hash_str, send_response); -} + uint8_t message_hash[32]; + uint8_t bsm_digest[32]; -static void send_response(dispatcher_context_t *dc) { - sign_message_state_t *state = (sign_message_state_t *) &G_command_state; + crypto_hash_digest(&msg_hash_context.header, message_hash, 32); + crypto_hash_digest(&bsm_digest_context.header, bsm_digest, 32); + cx_hash_sha256(bsm_digest, 32, bsm_digest, 32); - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + char message_hash_str[MESSAGE_CHUNK_SIZE + 1]; + for (int i = 0; i < MESSAGE_CHUNK_SIZE / 2; i++) { + snprintf(message_hash_str + 2 * i, 3, "%02X", message_hash[i]); + } + ui_pre_processing_message(); + if (printable) { + if (!display_message_content_and_confirm(dc, + message_merkle_root, + n_chunks, + (uint8_t*) path_str)) { + SEND_SW(dc, SW_DENY); + ui_post_processing_confirm_message(dc, false); + return; + } + } else { + if (!ui_display_message_path_hash_and_confirm(dc, path_str, message_hash_str)) { + SEND_SW(dc, SW_DENY); + ui_post_processing_confirm_message(dc, false); + return; + } + } uint8_t sig[MAX_DER_SIG_LEN]; uint32_t info; - int sig_len = crypto_ecdsa_sign_sha256_hash_with_key(state->bip32_path, - state->bip32_path_len, - state->bsm_digest, + int sig_len = crypto_ecdsa_sign_sha256_hash_with_key(bip32_path, + bip32_path_len, + bsm_digest, + NULL, sig, &info); - if (sig_len < 0) { // unexpected error when signing SEND_SW(dc, SW_BAD_STATE); + ui_post_processing_confirm_message(dc, false); return; } - // convert signature to the standard Bitcoin format, always 65 bytes long + { + // convert signature to the standard Bitcoin format, always 65 bytes long - uint8_t result[65]; - memset(result, 0, sizeof(result)); + uint8_t result[65]; + memset(result, 0, sizeof(result)); - // # Format signature into standard bitcoin format - int r_length = sig[3]; - int s_length = sig[4 + r_length + 1]; + // # Format signature into standard bitcoin format + int r_length = sig[3]; + int s_length = sig[4 + r_length + 1]; - if (r_length > 33 || s_length > 33) { - SEND_SW(dc, SW_BAD_STATE); // can never happen - return; - } + if (r_length > 33 || s_length > 33) { + SEND_SW(dc, SW_BAD_STATE); // can never happen + ui_post_processing_confirm_message(dc, false); + return; + } - // Write s, r, and the first byte in reverse order, as the two loops will underflow by 1 byte - // (that needs to be discarded) when s_length and r_length (respectively) are equal to 33. - for (int i = s_length - 1; i >= 0; --i) { - result[1 + 32 + 32 - s_length + i] = sig[4 + r_length + 2 + i]; - } - for (int i = r_length - 1; i >= 0; --i) { - result[1 + 32 - r_length + i] = sig[4 + i]; - } - result[0] = 27 + 4 + ((info & CX_ECCINFO_PARITY_ODD) ? 1 : 0); + // Write s, r, and the first byte in reverse order, as the two loops will underflow by 1 + // byte (that needs to be discarded) when s_length and r_length (respectively) are equal + // to 33. + for (int i = s_length - 1; i >= 0; --i) { + result[1 + 32 + 32 - s_length + i] = sig[4 + r_length + 2 + i]; + } + for (int i = r_length - 1; i >= 0; --i) { + result[1 + 32 - r_length + i] = sig[4 + i]; + } + result[0] = 27 + 4 + ((info & CX_ECCINFO_PARITY_ODD) ? 1 : 0); - SEND_RESPONSE(dc, result, sizeof(result), SW_OK); + SEND_RESPONSE(dc, result, sizeof(result), SW_OK); + ui_post_processing_confirm_message(dc, true); + return; + } } diff --git a/src/handler/sign_message.h b/src/handler/sign_message.h deleted file mode 100644 index 34735559b..000000000 --- a/src/handler/sign_message.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "cx.h" -#include "../common/bip32.h" -#include "../boilerplate/dispatcher.h" - -/// State of SIGN_MESSAGE handler -typedef struct { - /// Machine context for command dispatcher - machine_context_t ctx; - - /// Number of derivation steps in BIP32 path - uint8_t bip32_path_len; - /// BIP32 path used for signing - uint32_t bip32_path[MAX_BIP32_PATH_STEPS]; - /// The byte length of the message to sign - uint64_t message_length; - /// The Merkle root of the message - uint8_t message_merkle_root[32]; - - /// SHA-256 context used to compute sha256(message) - cx_sha256_t msg_hash_context; - /// SHA-256 context used to compute the Bitcoin Message Signing digest - cx_sha256_t bsm_digest_context; - - /// SHA-256 hash of the message - uint8_t message_hash[32]; - /// Computed Bitcoin Message Signing digest - uint8_t bsm_digest[32]; -} sign_message_state_t; - -/** - * Handles SIGN_MESSAGE command. - * - * @param[in,out] dispatcher_context - * Dispatcher context. - */ -void handler_sign_message(dispatcher_context_t *dispatcher_context); diff --git a/src/handler/sign_psbt.c b/src/handler/sign_psbt.c index 844a68f75..97aacb150 100644 --- a/src/handler/sign_psbt.c +++ b/src/handler/sign_psbt.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,17 @@ #include +#include "lib_standard_app/crypto_helpers.h" + #include "../boilerplate/dispatcher.h" #include "../boilerplate/sw.h" +#include "../common/bitvector.h" #include "../common/merkle.h" #include "../common/psbt.h" #include "../common/read.h" #include "../common/script.h" #include "../common/varint.h" +#include "../common/wallet.h" #include "../common/write.h" #include "../commands.h" @@ -41,52 +45,139 @@ #include "lib/get_preimage.h" #include "lib/get_merkleized_map.h" #include "lib/get_merkleized_map_value.h" +#include "lib/get_merkle_leaf_element.h" #include "lib/psbt_parse_rawtx.h" -#include "sign_psbt.h" +#include "handlers.h" #include "sign_psbt/compare_wallet_script_at_path.h" -#include "sign_psbt/get_fingerprint_and_path.h" -#include "sign_psbt/is_in_out_internal.h" +#include "sign_psbt/extract_bip32_derivation.h" #include "sign_psbt/update_hashes_with_map_value.h" #include "../swap/swap_globals.h" +#include "../swap/handle_swap_sign_transaction.h" + +// common info that applies to either the current input or the current output +typedef struct { + merkleized_map_commitment_t map; + + bool unexpected_pubkey_error; // Set to true if the pubkey in the keydata of + // PSBT_{IN,OUT}_BIP32_DERIVATION or + // PSBT_{IN,OUT}_TAP_BIP32_DERIVATION is not the correct length. + + bool placeholder_found; // Set to true if a matching placeholder is found in the input info -// Input validation -static void process_input_map(dispatcher_context_t *dc); -static void check_input_owned(dispatcher_context_t *dc); + bool is_change; + int address_index; -static void alert_external_inputs(dispatcher_context_t *dc); + // For an output, its scriptPubKey + // for an input, the prevout's scriptPubKey (either from the non-witness-utxo, or from the + // witness-utxo) -// Output validation -static void verify_outputs_init(dispatcher_context_t *dc); -static void process_output_map(dispatcher_context_t *dc); -static void check_output_owned(dispatcher_context_t *dc); -static void output_validate_external(dispatcher_context_t *dc); -static void output_next(dispatcher_context_t *dc); + uint8_t scriptPubKey[MAX_OUTPUT_SCRIPTPUBKEY_LEN]; + size_t scriptPubKey_len; +} in_out_info_t; -// User confirmation (all) -static void confirm_transaction(dispatcher_context_t *dc); +typedef struct { + in_out_info_t in_out; + bool has_witnessUtxo; + bool has_nonWitnessUtxo; + bool has_redeemScript; + bool has_sighash_type; -// Signing process (all) -static void sign_init(dispatcher_context_t *dc); -static void sign_process_input_map(dispatcher_context_t *dc); + uint64_t prevout_amount; // the value of the prevout of the current input -// Legacy sighash computation (P2PKH and P2SH) -static void sign_legacy(dispatcher_context_t *dc); -static void sign_legacy_compute_sighash(dispatcher_context_t *dc); + // we no longer need the script when we compute the taptree hash right before a taproot key-path + // spending; therefore, we reuse the same memory + union { + // the script used when signing, either from the witness utxo or the redeem script + uint8_t script[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; + uint8_t taptree_hash[32]; + }; -// Segwit sighash computation (P2WPKH, P2WSH and P2TR) -static void sign_segwit(dispatcher_context_t *dc); -static void sign_segwit_v0(dispatcher_context_t *dc); -static void sign_segwit_v1(dispatcher_context_t *dc); + size_t script_len; -// Sign input and yield result -static void sign_sighash_ecdsa(dispatcher_context_t *dc); -static void sign_sighash_schnorr(dispatcher_context_t *dc); + uint32_t sighash_type; +} input_info_t; -// End point and return -static void finalize(dispatcher_context_t *dc); +typedef struct { + in_out_info_t in_out; + uint64_t value; +} output_info_t; + +typedef struct { + policy_node_key_placeholder_t placeholder; + int cur_index; + uint32_t fingerprint; + uint8_t key_derivation_length; + uint32_t key_derivation[MAX_BIP32_PATH_STEPS]; + serialized_extended_pubkey_t pubkey; + bool is_tapscript; // true if signing with a BIP342 tapleaf script path spend + uint8_t tapleaf_hash[32]; // only used for tapscripts +} placeholder_info_t; + +// Cache for partial hashes during segwit signing (avoid quadratic hashing for segwit transactions) +typedef struct { + uint8_t sha_prevouts[32]; + uint8_t sha_amounts[32]; + uint8_t sha_scriptpubkeys[32]; + uint8_t sha_sequences[32]; + uint8_t sha_outputs[32]; +} segwit_hashes_t; + +#ifdef USE_NVRAM_STASH + +typedef struct { + // Aligning by 4 is necessary due to platform limitations. + // Aligning by 64 further guarantees that most policies will fit in a single + // NVRAM page boundary, which minimizes the amount of writes. + __attribute__((aligned(64))) uint8_t wallet_policy_bytes[MAX_WALLET_POLICY_BYTES]; +} nvram_stash_t; + +const nvram_stash_t N_nvram_stash_real; +#define N_nvram_stash (*(const volatile nvram_stash_t *) PIC(&N_nvram_stash_real)) + +#endif + +typedef struct { + uint32_t master_key_fingerprint; + uint32_t tx_version; + uint32_t locktime; + + unsigned int n_inputs; + uint8_t inputs_root[32]; // merkle root of the vector of input maps commitments + unsigned int n_outputs; + uint8_t outputs_root[32]; // merkle root of the vector of output maps commitments + + uint64_t inputs_total_amount; + + // aggregate info on outputs + struct { + uint64_t total_amount; // amount of all the outputs (external + change) + uint64_t change_total_amount; // total amount of all change outputs + int n_change; // count of outputs compatible with change outputs + int n_external; // count of external outputs + } outputs; + + bool is_wallet_default; + + uint8_t protocol_version; + +#ifndef USE_NVRAM_STASH + __attribute__((aligned(4))) uint8_t wallet_policy_map_bytes[MAX_WALLET_POLICY_BYTES]; +#endif + policy_node_t *wallet_policy_map; + + int wallet_header_version; + uint8_t wallet_header_keys_info_merkle_root[32]; + size_t wallet_header_n_keys; + + // if any segwitv0 input is missing the non-witness-utxo, we show a warning + bool show_missing_nonwitnessutxo_warning; + + // if any of the internal inputs has non-default sighash, we show a warning + bool show_nondefault_sighash_warning; +} sign_psbt_state_t; /* BIP0341 tags for computing the tagged hashes when computing he sighash */ static const uint8_t BIP0341_sighash_tag[] = {'T', 'a', 'p', 'S', 'i', 'g', 'h', 'a', 's', 'h'}; @@ -105,66 +196,64 @@ the right paths to identify internal inputs/outputs. */ // HELPER FUNCTIONS - -// Updates the hash_context with the network serialization of all the outputs +// Updates the hash_context with the output of given index // returns -1 on error. 0 on success. -static int hash_outputs(dispatcher_context_t *dc, cx_hash_t *hash_context) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - // TODO: support other SIGHASH FLAGS - for (unsigned int i = 0; i < state->n_outputs; i++) { - // get this output's map - merkleized_map_commitment_t ith_map; +static int hash_output_n(dispatcher_context_t *dc, + sign_psbt_state_t *st, + cx_hash_t *hash_context, + unsigned int index) { + if (index >= st->n_outputs) { + return -1; + } - int res = call_get_merkleized_map(dc, state->outputs_root, state->n_outputs, i, &ith_map); - if (res < 0) { - return -1; - } + // get this output's map + merkleized_map_commitment_t ith_map; - // get output's amount - uint8_t amount_raw[8]; - if (8 != call_get_merkleized_map_value(dc, - &ith_map, - (uint8_t[]){PSBT_OUT_AMOUNT}, - 1, - amount_raw, - 8)) { - return -1; - } + int res = call_get_merkleized_map(dc, st->outputs_root, st->n_outputs, index, &ith_map); + if (res < 0) { + return -1; + } - crypto_hash_update(hash_context, amount_raw, 8); + // get output's amount + uint8_t amount_raw[8]; + if (8 != call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_OUT_AMOUNT}, + 1, + amount_raw, + 8)) { + return -1; + } - // get output's scriptPubKey + crypto_hash_update(hash_context, amount_raw, 8); - uint8_t out_script[MAX_OUTPUT_SCRIPTPUBKEY_LEN]; - int out_script_len = call_get_merkleized_map_value(dc, - &ith_map, - (uint8_t[]){PSBT_OUT_SCRIPT}, - 1, - out_script, - sizeof(out_script)); - if (out_script_len == -1) { - return -1; - } + // get output's scriptPubKey - crypto_hash_update_varint(hash_context, out_script_len); - crypto_hash_update(hash_context, out_script, out_script_len); + uint8_t out_script[MAX_OUTPUT_SCRIPTPUBKEY_LEN]; + int out_script_len = call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_OUT_SCRIPT}, + 1, + out_script, + sizeof(out_script)); + if (out_script_len == -1) { + return -1; } + + crypto_hash_update_varint(hash_context, out_script_len); + crypto_hash_update(hash_context, out_script, out_script_len); return 0; } -static int get_segwit_version(const uint8_t scriptPubKey[], int scriptPubKey_len) { - if (scriptPubKey_len <= 1) { - return -1; - } - - if (scriptPubKey[0] == 0x00) { - return 0; - } else if (scriptPubKey[0] >= 0x51 && scriptPubKey[0] <= 0x60) { - return scriptPubKey[0] - 0x50; +// Updates the hash_context with the network serialization of all the outputs +// returns -1 on error. 0 on success. +static int hash_outputs(dispatcher_context_t *dc, sign_psbt_state_t *st, cx_hash_t *hash_context) { + for (unsigned int i = 0; i < st->n_outputs; i++) { + if (hash_output_n(dc, st, hash_context, i)) { + return -1; + } } - - return -1; + return 0; } /* @@ -174,7 +263,7 @@ static int get_segwit_version(const uint8_t scriptPubKey[], int scriptPubKey_len non-witness-utxo does not match the one pointed by expected_prevout_hash. Returns -1 on failure, 0 on success. */ -static int get_amount_scriptpubkey_from_psbt_nonwitness( +static int __attribute__((noinline)) get_amount_scriptpubkey_from_psbt_nonwitness( dispatcher_context_t *dc, const merkleized_map_commitment_t *input_map, uint64_t *amount, @@ -208,7 +297,7 @@ static int get_amount_scriptpubkey_from_psbt_nonwitness( return -1; } - // if expected_prevout_hash is give, check that it matches the txid obtained from the parser + // if expected_prevout_hash is given, check that it matches the txid obtained from the parser if (expected_prevout_hash != NULL && memcmp(parser_outputs.txid, expected_prevout_hash, 32) != 0) { PRINTF("Prevout hash did not match non-witness-utxo transaction hash\n"); @@ -217,12 +306,8 @@ static int get_amount_scriptpubkey_from_psbt_nonwitness( } *amount = parser_outputs.vout_value; - if (parser_outputs.vout_scriptpubkey_len <= MAX_PREVOUT_SCRIPTPUBKEY_LEN) { - *scriptPubKey_len = parser_outputs.vout_scriptpubkey_len; - memcpy(scriptPubKey, parser_outputs.vout_scriptpubkey, parser_outputs.vout_scriptpubkey_len); - } else { - return -1; - } + *scriptPubKey_len = parser_outputs.vout_scriptpubkey_len; + memcpy(scriptPubKey, parser_outputs.vout_scriptpubkey, parser_outputs.vout_scriptpubkey_len); return 0; } @@ -232,12 +317,12 @@ static int get_amount_scriptpubkey_from_psbt_nonwitness( a PSBTv2. Returns -1 on failure, 0 on success. */ -static int get_amount_scriptpubkey_from_psbt_witness( - dispatcher_context_t *dc, - const merkleized_map_commitment_t *input_map, - uint64_t *amount, - uint8_t scriptPubKey[static MAX_PREVOUT_SCRIPTPUBKEY_LEN], - size_t *scriptPubKey_len) { +static int __attribute__((noinline)) +get_amount_scriptpubkey_from_psbt_witness(dispatcher_context_t *dc, + const merkleized_map_commitment_t *input_map, + uint64_t *amount, + uint8_t scriptPubKey[static MAX_PREVOUT_SCRIPTPUBKEY_LEN], + size_t *scriptPubKey_len) { uint8_t raw_witnessUtxo[8 + 1 + MAX_PREVOUT_SCRIPTPUBKEY_LEN]; int wit_utxo_len = call_get_merkleized_map_value(dc, @@ -261,13 +346,8 @@ static int get_amount_scriptpubkey_from_psbt_witness( uint64_t wit_utxo_prevout_amount = read_u64_le(&raw_witnessUtxo[0], 0); *amount = wit_utxo_prevout_amount; - if (wit_utxo_scriptPubkey_len <= MAX_PREVOUT_SCRIPTPUBKEY_LEN) { - *scriptPubKey_len = wit_utxo_scriptPubkey_len; - memcpy(scriptPubKey, wit_utxo_scriptPubkey, wit_utxo_scriptPubkey_len); - } else { - return -1; - } - + *scriptPubKey_len = wit_utxo_scriptPubkey_len; + memcpy(scriptPubKey, wit_utxo_scriptPubkey, wit_utxo_scriptPubkey_len); return 0; } @@ -300,161 +380,181 @@ static int get_amount_scriptpubkey_from_psbt( NULL); } +// Convenience function to share common logic when processing all the +// PSBT_{IN|OUT}_{TAP}?_BIP32_DERIVATION fields. +static int read_change_and_index_from_psbt_bip32_derivation( + dispatcher_context_t *dc, + placeholder_info_t *placeholder_info, + in_out_info_t *in_out, + int psbt_key_type, + buffer_t *data, + const merkleized_map_commitment_t *map_commitment, + int index) { + uint8_t bip32_derivation_pubkey[33]; + + bool is_tap = psbt_key_type == PSBT_IN_TAP_BIP32_DERIVATION || + psbt_key_type == PSBT_OUT_TAP_BIP32_DERIVATION; + int key_len = is_tap ? 32 : 33; + + if (!buffer_read_bytes(data, + bip32_derivation_pubkey, + key_len) // read compressed pubkey or x-only pubkey + || buffer_can_read(data, 1) // ...but should not be able to read more + ) { + PRINTF("Unexpected pubkey length\n"); + in_out->unexpected_pubkey_error = true; + return -1; + } + + // get the corresponding value in the values Merkle tree, + // then fetch the bip32 path from the field + uint32_t fpt_der[1 + MAX_BIP32_PATH_STEPS]; + + int der_len = extract_bip32_derivation(dc, + psbt_key_type, + map_commitment->values_root, + map_commitment->size, + index, + fpt_der); + if (der_len < 0) { + PRINTF("Failed to read BIP32_DERIVATION\n"); + return -1; + } + + if (der_len < 2 || der_len > MAX_BIP32_PATH_STEPS) { + PRINTF("BIP32_DERIVATION path too long\n"); + return -1; + } + + // if this derivation path matches the internal placeholder, + // we use it to detect whether the current input is change or not, + // and store its address index + if (fpt_der[0] == placeholder_info->fingerprint && + der_len == placeholder_info->key_derivation_length + 2) { + for (int i = 0; i < placeholder_info->key_derivation_length; i++) { + if (placeholder_info->key_derivation[i] != fpt_der[1 + i]) { + return 0; + } + } + + uint32_t change = fpt_der[1 + der_len - 2]; + uint32_t addr_index = fpt_der[1 + der_len - 1]; + + // check that we can indeed derive the same key from the current placeholder + serialized_extended_pubkey_t pubkey; + if (0 > bip32_CKDpub(&placeholder_info->pubkey, change, &pubkey)) return -1; + if (0 > bip32_CKDpub(&pubkey, addr_index, &pubkey)) return -1; + + int pk_offset = is_tap ? 1 : 0; + if (memcmp(pubkey.compressed_pubkey + pk_offset, bip32_derivation_pubkey, key_len) != 0) { + return 0; + } + + // check if the 'change' derivation step is indeed coherent with placeholder + if (change == placeholder_info->placeholder.num_first) { + in_out->is_change = false; + in_out->address_index = addr_index; + } else if (change == placeholder_info->placeholder.num_second) { + in_out->is_change = true; + in_out->address_index = addr_index; + } else { + return 0; + } + + in_out->placeholder_found = true; + return 1; + } + return 0; +} + /** - * Validates the input, initializes the hash context and starts accumulating the wallet header in - * it. + * Verifies if a certain input/output is internal (that is, controlled by the wallet being used for + * signing). This uses the state of sign_psbt and is not meant as a general-purpose function; + * rather, it avoids some substantial code duplication and removes complexity from sign_psbt. + * + * @return 1 if the given input/output is internal; 0 if external; -1 on error. */ -void handler_sign_psbt(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; +static int is_in_out_internal(dispatcher_context_t *dispatcher_context, + const sign_psbt_state_t *state, + const in_out_info_t *in_out_info, + bool is_input) { + // If we did not find any info about the pubkey associated to the placeholder we're considering, + // then it's external + if (!in_out_info->placeholder_found) { + return 0; + } - // Device must be unlocked - if (os_global_pin_is_validated() != BOLOS_UX_OK) { - SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); - return; + if (!is_input && in_out_info->is_change != 1) { + // unlike for inputs, we only consider outputs internal if they are on the change path + return 0; } + return compare_wallet_script_at_path(dispatcher_context, + in_out_info->is_change, + in_out_info->address_index, + state->wallet_policy_map, + state->wallet_header_version, + state->wallet_header_keys_info_merkle_root, + state->wallet_header_n_keys, + in_out_info->scriptPubKey, + in_out_info->scriptPubKey_len); +} + +static bool __attribute__((noinline)) +init_global_state(dispatcher_context_t *dc, sign_psbt_state_t *st) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + merkleized_map_commitment_t global_map; if (!buffer_read_varint(&dc->read_buffer, &global_map.size)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); - return; + return false; } if (!buffer_read_bytes(&dc->read_buffer, global_map.keys_root, 32) || !buffer_read_bytes(&dc->read_buffer, global_map.values_root, 32)) { - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); SEND_SW(dc, SW_WRONG_DATA_LENGTH); - return; + return false; } - uint64_t n_inputs; - if (!buffer_read_varint(&dc->read_buffer, &n_inputs) || - !buffer_read_bytes(&dc->read_buffer, state->inputs_root, 32)) { + // we already know n_inputs and n_outputs, so we skip reading from the global map + + uint64_t n_inputs_u64; + if (!buffer_read_varint(&dc->read_buffer, &n_inputs_u64) || + !buffer_read_bytes(&dc->read_buffer, st->inputs_root, 32)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); - return; + return false; } - if (n_inputs > MAX_N_INPUTS_CAN_SIGN) { - // TODO: remove this limitation + + if (n_inputs_u64 > MAX_N_INPUTS_CAN_SIGN) { PRINTF("At most %d inputs are supported\n", MAX_N_INPUTS_CAN_SIGN); SEND_SW(dc, SW_NOT_SUPPORTED); - return; + return false; } - state->n_inputs = (unsigned int) n_inputs; + st->n_inputs = (unsigned int) n_inputs_u64; - uint64_t n_outputs; - if (!buffer_read_varint(&dc->read_buffer, &n_outputs) || - !buffer_read_bytes(&dc->read_buffer, state->outputs_root, 32)) { + uint64_t n_outputs_u64; + if (!buffer_read_varint(&dc->read_buffer, &n_outputs_u64) || + !buffer_read_bytes(&dc->read_buffer, st->outputs_root, 32)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); - return; + return false; } - state->n_outputs = (unsigned int) n_outputs; + st->n_outputs = (unsigned int) n_outputs_u64; + + policy_map_wallet_header_t wallet_header; - uint8_t wallet_id[32]; uint8_t wallet_hmac[32]; + uint8_t wallet_id[32]; if (!buffer_read_bytes(&dc->read_buffer, wallet_id, 32) || !buffer_read_bytes(&dc->read_buffer, wallet_hmac, 32)) { SEND_SW(dc, SW_WRONG_DATA_LENGTH); - return; - } - - // Fetch the serialized wallet policy from the client - uint8_t serialized_wallet_policy[MAX_POLICY_MAP_SERIALIZED_LENGTH]; - int serialized_wallet_policy_len = call_get_preimage(dc, - wallet_id, - serialized_wallet_policy, - sizeof(serialized_wallet_policy)); - if (serialized_wallet_policy_len < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - policy_map_wallet_header_t wallet_header; - buffer_t serialized_wallet_policy_buf = - buffer_create(serialized_wallet_policy, serialized_wallet_policy_len); - if ((read_policy_map_wallet(&serialized_wallet_policy_buf, &wallet_header)) < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - memcpy(state->wallet_header_keys_info_merkle_root, - wallet_header.keys_info_merkle_root, - sizeof(state->wallet_header_keys_info_merkle_root)); - state->wallet_header_n_keys = wallet_header.n_keys; - - buffer_t policy_map_buffer = - buffer_create(&wallet_header.policy_map, wallet_header.policy_map_len); - - if (parse_policy_map(&policy_map_buffer, - state->wallet_policy_map_bytes, - sizeof(state->wallet_policy_map_bytes), - BIP32_PUBKEY_VERSION, - BIP32_PRIVKEY_VERSION) < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - uint8_t hmac_or = - 0; // the binary OR of all the hmac bytes (so == 0 iff the hmac is identically 0) - for (int i = 0; i < 32; i++) { - hmac_or = hmac_or | wallet_hmac[i]; - } - if (hmac_or == 0) { - // No hmac, verify that the policy is a canonical one that is allowed by default - - if (state->wallet_header_n_keys != 1) { - PRINTF("Non-standard policy, it should only have 1 key\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - state->address_type = get_policy_address_type(&state->wallet_policy_map); - if (state->address_type == -1) { - PRINTF("Non-standard policy, and no hmac provided\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - state->is_wallet_canonical = true; - - // Based on the address type, we set the expected bip44 purpose for this canonical wallet - state->bip44_purpose = get_bip44_purpose(state->address_type); - if (state->bip44_purpose < 0) { - SEND_SW(dc, SW_BAD_STATE); - return; - } - - // We do not check here that the purpose field, coin_type and account (first three step of - // the bip44 derivation) are standard. Will check at signing time that the path is valid. - } else { - // Verify hmac - - if (!check_wallet_hmac(wallet_id, wallet_hmac)) { - PRINTF("Incorrect hmac\n"); - SEND_SW(dc, SW_SIGNATURE_FAIL); - return; - } - - state->is_wallet_canonical = false; - } - - // Swap feature: check that wallet is canonical - if (G_swap_state.called_from_swap && !state->is_wallet_canonical) { - PRINTF("Must be a canonical wallet for swap feature\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } - state->inputs_total_value = 0; - state->internal_inputs_total_value = 0; - memset(state->internal_inputs, 0, sizeof(state->internal_inputs)); - - state->master_key_fingerprint = crypto_get_master_key_fingerprint(); - - // process global map - { + { // process global map // Check integrity of the global map if (call_check_merkle_tree_sorted(dc, global_map.keys_root, (size_t) global_map.size) < 0) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } uint8_t raw_result[9]; // max size for a varint @@ -469,9 +569,9 @@ void handler_sign_psbt(dispatcher_context_t *dc) { sizeof(raw_result)); if (result_len != 4) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } - state->tx_version = read_u32_le(raw_result, 0); + st->tx_version = read_u32_le(raw_result, 0); // Read fallback locktime. // Unlike BIP-0370 recommendation, we use the fallback locktime as-is, ignoring each input's @@ -484,785 +584,841 @@ void handler_sign_psbt(dispatcher_context_t *dc) { raw_result, sizeof(raw_result)); if (result_len == -1) { - state->locktime = 0; + st->locktime = 0; } else if (result_len != 4) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } else { - state->locktime = read_u32_le(raw_result, 0); + st->locktime = read_u32_le(raw_result, 0); } - - // we already know n_inputs and n_outputs, so we skip reading from the global map } - state->cur_input_index = 0; - - if (state->is_wallet_canonical) { - // Canonical wallet, we start processing the psbt directly - dc->next(process_input_map); - } else { - // Show screen to authorize spend from a registered wallet - ui_authorize_wallet_spend(dc, wallet_header.name, process_input_map); + uint8_t hmac_or = + 0; // the binary OR of all the hmac bytes (so == 0 iff the hmac is identically 0) + for (int i = 0; i < 32; i++) { + hmac_or = hmac_or | wallet_hmac[i]; } -} - -/** Inputs verification flow - * - * Go though all the inputs: - * - verify the non_witness_utxo - * - compute value spent - * - detect internal inputs that should be signed, and external inputs that shouldn't - */ -/** - * Callback to process all the keys of the current input map. - * Keeps track if the current input has a witness_utxo and/or a redeemScript. - */ -static void input_keys_callback(sign_psbt_state_t *state, buffer_t *data) { - size_t data_len = data->size - data->offset; - if (data_len >= 1) { - uint8_t key_type; - buffer_read_u8(data, &key_type); - if (key_type == PSBT_IN_WITNESS_UTXO) { - state->cur.input.has_witnessUtxo = true; - } else if (key_type == PSBT_IN_NON_WITNESS_UTXO) { - state->cur.input.has_nonWitnessUtxo = true; - } else if (key_type == PSBT_IN_REDEEM_SCRIPT) { - state->cur.input.has_redeemScript = true; - } else if (key_type == PSBT_IN_SIGHASH_TYPE) { - state->cur.input.has_sighash_type = true; - } else if ((key_type == PSBT_IN_BIP32_DERIVATION || - key_type == PSBT_IN_TAP_BIP32_DERIVATION) && - !state->cur.in_out.has_bip32_derivation) { - // The first time that we encounter a PSBT_IN_BIP32_DERIVATION or - // PSBT_IN_TAP_BIP32_DERIVATION (handled below) key, we store the pubkey. Since we only - // use this to identify the change and address_index, it does not matter which of the - // keys we use here (if there are multiple), as per the assumptions above. - state->cur.in_out.has_bip32_derivation = true; - - // x-only pubkeys for taproot, normal compressed pubkeys otherwise - size_t key_len = (key_type == PSBT_IN_TAP_BIP32_DERIVATION ? 32 : 33); - - if (!buffer_read_bytes(data, - state->cur.in_out.bip32_derivation_pubkey, - key_len) // read compressed pubkey or x-only pubkey - || buffer_can_read(data, 1) // ...but should not be able to read more - ) { - state->cur.in_out.unexpected_pubkey_error = true; - } + if (hmac_or != 0) { + // Verify hmac + if (!check_wallet_hmac(wallet_id, wallet_hmac)) { + PRINTF("Incorrect hmac\n"); + SEND_SW(dc, SW_SIGNATURE_FAIL); + return false; } - } -} - -static void process_input_map(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - if (state->cur_input_index >= state->n_inputs) { - // all inputs already processed - dc->next(alert_external_inputs); - return; - } - - // Reset cur struct - memset(&state->cur, 0, sizeof(state->cur)); - - int res = call_get_merkleized_map_with_callback( - dc, - state->inputs_root, - state->n_inputs, - state->cur_input_index, - make_callback(state, (dispatcher_callback_t) input_keys_callback), - &state->cur.in_out.map); - if (res < 0) { - PRINTF("Failed to process input map\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - if (state->cur.in_out.unexpected_pubkey_error) { - PRINTF("Unexpected pubkey length\n"); // only compressed pubkeys are supported - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - // either witness utxo or non-witness utxo (or both) must be present. - if (!state->cur.input.has_nonWitnessUtxo && !state->cur.input.has_witnessUtxo) { - PRINTF("No witness utxo nor non-witness utxo present in input.\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; + st->is_wallet_default = false; + } else { + st->is_wallet_default = true; } - // validate non-witness utxo (if present) and witness utxo (if present) - - if (state->cur.input.has_nonWitnessUtxo) { - uint8_t prevout_hash[32]; - - // check if the prevout_hash of the transaction matches the computed one from the - // non-witness utxo - if (0 > call_get_merkleized_map_value(dc, - &state->cur.in_out.map, - (uint8_t[]){PSBT_IN_PREVIOUS_TXID}, - 1, - prevout_hash, - sizeof(prevout_hash))) { + { + // Fetch the serialized wallet policy from the client + uint8_t serialized_wallet_policy[MAX_WALLET_POLICY_SERIALIZED_LENGTH]; + int serialized_wallet_policy_len = call_get_preimage(dc, + wallet_id, + serialized_wallet_policy, + sizeof(serialized_wallet_policy)); + if (serialized_wallet_policy_len < 0) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } - // request non-witness utxo, and get the prevout's value and scriptpubkey - if (0 > get_amount_scriptpubkey_from_psbt_nonwitness(dc, - &state->cur.in_out.map, - &state->cur.input.prevout_amount, - state->cur.in_out.scriptPubKey, - &state->cur.in_out.scriptPubKey_len, - prevout_hash)) { + buffer_t serialized_wallet_policy_buf = + buffer_create(serialized_wallet_policy, serialized_wallet_policy_len); + + uint8_t policy_map_descriptor[MAX_DESCRIPTOR_TEMPLATE_LENGTH]; +#ifdef USE_NVRAM_STASH + // we need a temporary array to store the parsed policy in RAM before + // storing it in the NVRAM stash + uint8_t wallet_policy_map_bytes[MAX_WALLET_POLICY_BYTES]; +#else + uint8_t *wallet_policy_map_bytes = st->wallet_policy_map_bytes; +#endif + + int desc_temp_len = read_and_parse_wallet_policy(dc, + &serialized_wallet_policy_buf, + &wallet_header, + policy_map_descriptor, + wallet_policy_map_bytes, + MAX_WALLET_POLICY_BYTES); + if (desc_temp_len < 0) { + PRINTF("Failed to read or parse wallet policy"); SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } - state->inputs_total_value += state->cur.input.prevout_amount; - } - - if (state->cur.input.has_witnessUtxo) { - size_t wit_utxo_scriptPubkey_len; - uint8_t wit_utxo_scriptPubkey[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; - uint64_t wit_utxo_prevout_amount; - - if (0 > get_amount_scriptpubkey_from_psbt_witness(dc, - &state->cur.in_out.map, - &wit_utxo_prevout_amount, - wit_utxo_scriptPubkey, - &wit_utxo_scriptPubkey_len)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - }; - - if (state->cur.input.has_nonWitnessUtxo) { - // we already know the scriptPubKey, but we double check that it matches - if (state->cur.in_out.scriptPubKey_len != wit_utxo_scriptPubkey_len || - wit_utxo_scriptPubkey_len > sizeof(state->cur.in_out.scriptPubKey) || - memcmp(state->cur.in_out.scriptPubKey, - wit_utxo_scriptPubkey, - wit_utxo_scriptPubkey_len) != 0 || - state->cur.input.prevout_amount != wit_utxo_prevout_amount) { - PRINTF( - "scriptPubKey or amount in non-witness utxo doesn't match with witness utxo\n"); +#ifdef USE_NVRAM_STASH + nvm_write((void *) N_nvram_stash.wallet_policy_bytes, + (void *) wallet_policy_map_bytes, + desc_temp_len); + st->wallet_policy_map = (policy_node_t *) N_nvram_stash.wallet_policy_bytes; +#else + st->wallet_policy_map = (policy_node_t *) st->wallet_policy_map_bytes; +#endif + + st->wallet_header_version = wallet_header.version; + memcpy(st->wallet_header_keys_info_merkle_root, + wallet_header.keys_info_merkle_root, + sizeof(wallet_header.keys_info_merkle_root)); + st->wallet_header_n_keys = wallet_header.n_keys; + + if (st->is_wallet_default) { + // No hmac, verify that the policy is indeed a default one + if (!is_wallet_policy_standard(dc, &wallet_header, st->wallet_policy_map)) { + PRINTF("Non-standard policy, and no hmac provided\n"); SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } - } else { - // we extract the scriptPubKey and prevout amount from the witness utxo - state->inputs_total_value += wit_utxo_prevout_amount; - - state->cur.input.prevout_amount = wit_utxo_prevout_amount; - if (wit_utxo_scriptPubkey_len <= sizeof(state->cur.in_out.scriptPubKey)) { - state->cur.in_out.scriptPubKey_len = wit_utxo_scriptPubkey_len; - memcpy(state->cur.in_out.scriptPubKey, - wit_utxo_scriptPubkey, - wit_utxo_scriptPubkey_len); - } else { + + if (wallet_header.name_len != 0) { + PRINTF("Name must be zero-length for a standard wallet policy\n"); SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } + + // unlike in get_wallet_address, we do not check if the address_index is small: + // if funds were already sent there, there is no point in preventing to spend them. } } - dc->next(check_input_owned); -} - -static void check_input_owned(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; + // Swap feature: check that wallet policy is a default one + if (G_swap_state.called_from_swap && !st->is_wallet_default) { + PRINTF("Must be a default wallet policy for swap feature\n"); + SEND_SW(dc, SW_FAIL_SWAP); + finalize_exchange_sign_transaction(false); + } - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + // If it's not a default wallet policy, ask the user for confirmation, and abort if they deny + if (!st->is_wallet_default && !ui_authorize_wallet_spend(dc, wallet_header.name)) { + SEND_SW(dc, SW_DENY); + ui_post_processing_confirm_wallet_spend(dc, false); + return false; + } - int is_internal = is_in_out_internal(dc, - state, - &state->cur.in_out, - true, - state->cur.in_out.has_bip32_derivation); + st->master_key_fingerprint = crypto_get_master_key_fingerprint(); - if (is_internal < 0) { - PRINTF("Error checking if input %d is internal\n", state->cur_input_index); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } else if (is_internal == 0) { - PRINTF("INPUT %d is external\n", state->cur_input_index); - } else { - bitvector_set(state->internal_inputs, state->cur_input_index, 1); - state->internal_inputs_total_value += state->cur.input.prevout_amount; + if (!st->is_wallet_default) { + ui_post_processing_confirm_wallet_spend(dc, true); + } + return true; +} - int segwit_version = - get_segwit_version(state->cur.in_out.scriptPubKey, state->cur.in_out.scriptPubKey_len); +static bool __attribute__((noinline)) +fill_placeholder_info_if_internal(dispatcher_context_t *dc, + sign_psbt_state_t *st, + placeholder_info_t *placeholder_info) { + policy_map_key_info_t key_info; + { + uint8_t key_info_str[MAX_POLICY_KEY_INFO_LEN]; + int key_info_len = call_get_merkle_leaf_element(dc, + st->wallet_header_keys_info_merkle_root, + st->wallet_header_n_keys, + placeholder_info->placeholder.key_index, + key_info_str, + sizeof(key_info_str)); - // For legacy or segwit-v0 inputs, the non-witness utxo must be present - if ((segwit_version == -1 || segwit_version == 0) && !state->cur.input.has_nonWitnessUtxo) { - PRINTF("Non-witness utxo missing for legacy or segwitv0 input\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; + if (key_info_len < 0) { + SEND_SW(dc, SW_BAD_STATE); // should never happen + return false; } - // For all segwit transactions, the witness utxo must be present - if (segwit_version >= 0 && !state->cur.input.has_witnessUtxo) { - PRINTF("Witness utxo missing for segwit input\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; + // Make a sub-buffer for the pubkey info + buffer_t key_info_buffer = buffer_create(key_info_str, key_info_len); + + if (parse_policy_map_key_info(&key_info_buffer, &key_info, st->wallet_header_version) == + -1) { + SEND_SW(dc, SW_BAD_STATE); // should never happen + return false; } } - ++state->cur_input_index; - dc->next(process_input_map); -} - -// If there are external inputs, it is unsafe to sign, therefore we warn the user -static void alert_external_inputs(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; + uint32_t fpr = read_u32_be(key_info.master_key_fingerprint, 0); + if (fpr != st->master_key_fingerprint) { + return false; + } - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + { + // it could be a collision on the fingerprint; we verify that we can actually generate + // the same pubkey + if (0 > get_extended_pubkey_at_path(key_info.master_key_derivation, + key_info.master_key_derivation_len, + BIP32_PUBKEY_VERSION, + &placeholder_info->pubkey)) { + SEND_SW(dc, SW_BAD_STATE); + return false; + } - size_t count_external_inputs = 0; - for (unsigned int i = 0; i < state->n_inputs; i++) { - if (!bitvector_get(state->internal_inputs, i)) { - ++count_external_inputs; + if (memcmp(&key_info.ext_pubkey, + &placeholder_info->pubkey, + sizeof(placeholder_info->pubkey)) != 0) { + return false; } - } - if (count_external_inputs == 0) { - // no external inputs - dc->next(verify_outputs_init); - } else if (count_external_inputs == state->n_inputs) { - // no internal inputs, nothing to sign - PRINTF("No internal inputs. Aborting\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } else { - // Swap feature: no external inputs allowed - if (G_swap_state.called_from_swap) { - PRINTF("External inputs not allowed in swap transactions\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; + placeholder_info->key_derivation_length = key_info.master_key_derivation_len; + for (int i = 0; i < key_info.master_key_derivation_len; i++) { + placeholder_info->key_derivation[i] = key_info.master_key_derivation[i]; } - // some internal and some external inputs, warn the user first - ui_warn_external_inputs(dc, verify_outputs_init); + placeholder_info->fingerprint = read_u32_be(key_info.master_key_fingerprint, 0); } -} -/** OUTPUTS VERIFICATION FLOW - * - * For each output, check if it's a change address. - * Show each output that is not a change address to the user for verification. - */ + return true; +} -// entry point for the outputs verification flow -static void verify_outputs_init(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; +// finds the first placeholder that corresponds to an internal key +static bool find_first_internal_key_placeholder(dispatcher_context_t *dc, + sign_psbt_state_t *st, + placeholder_info_t *placeholder_info) { + placeholder_info->cur_index = 0; - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + // find and parse our registered key info in the wallet + while (true) { + int n_key_placeholders = get_key_placeholder_by_index(st->wallet_policy_map, + placeholder_info->cur_index, + NULL, + &placeholder_info->placeholder); + if (n_key_placeholders < 0) { + SEND_SW(dc, SW_BAD_STATE); // should never happen + return false; + } - state->outputs_total_value = 0; - state->change_outputs_total_value = 0; - state->change_count = 0; + if (placeholder_info->cur_index >= n_key_placeholders) { + // all keys have been processed + break; + } - state->cur_output_index = 0; + if (fill_placeholder_info_if_internal(dc, st, placeholder_info)) { + return true; + } - state->external_outputs_count = 0; + // Not an internal key, move on + ++placeholder_info->cur_index; + } - dc->next(process_output_map); + PRINTF("No internal key found in wallet policy"); + SEND_SW(dc, SW_INCORRECT_DATA); + return false; } +typedef struct { + placeholder_info_t *placeholder_info; + input_info_t *input; +} input_keys_callback_data_t; + /** * Callback to process all the keys of the current input map. * Keeps track if the current input has a witness_utxo and/or a redeemScript. */ -static void output_keys_callback(sign_psbt_state_t *state, buffer_t *data) { +static void input_keys_callback(dispatcher_context_t *dc, + input_keys_callback_data_t *callback_data, + const merkleized_map_commitment_t *map_commitment, + int i, + buffer_t *data) { size_t data_len = data->size - data->offset; if (data_len >= 1) { uint8_t key_type; buffer_read_u8(data, &key_type); - - if ((key_type == PSBT_OUT_BIP32_DERIVATION || key_type == PSBT_OUT_TAP_BIP32_DERIVATION) && - !state->cur.in_out.has_bip32_derivation) { - // The first time that we encounter a PSBT_OUT_BIP32_DERIVATION or - // PSBT_OUT_TAP_BIP32_DERIVATION key, we store the pubkey. - state->cur.in_out.has_bip32_derivation = true; - - // x-only pubkeys for taproot, normal compressed pubkeys otherwise - size_t key_len = (key_type == PSBT_OUT_TAP_BIP32_DERIVATION ? 32 : 33); - - if (!buffer_read_bytes(data, - state->cur.in_out.bip32_derivation_pubkey, - key_len) // read compressed pubkey or x-only pubkey - || buffer_can_read(data, 1) // ...but should not be able to read more - ) { - state->cur.in_out.unexpected_pubkey_error = true; + if (key_type == PSBT_IN_WITNESS_UTXO) { + callback_data->input->has_witnessUtxo = true; + } else if (key_type == PSBT_IN_NON_WITNESS_UTXO) { + callback_data->input->has_nonWitnessUtxo = true; + } else if (key_type == PSBT_IN_REDEEM_SCRIPT) { + callback_data->input->has_redeemScript = true; + } else if (key_type == PSBT_IN_SIGHASH_TYPE) { + callback_data->input->has_sighash_type = true; + } else if ((key_type == PSBT_IN_BIP32_DERIVATION || + key_type == PSBT_IN_TAP_BIP32_DERIVATION) && + !callback_data->input->in_out.placeholder_found) { + if (0 > + read_change_and_index_from_psbt_bip32_derivation(dc, + callback_data->placeholder_info, + &callback_data->input->in_out, + key_type, + data, + map_commitment, + i)) { + callback_data->input->in_out.unexpected_pubkey_error = true; } } } } -static void process_output_map(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; +static bool __attribute__((noinline)) +preprocess_inputs(dispatcher_context_t *dc, + sign_psbt_state_t *st, + uint8_t internal_inputs[static BITVECTOR_REAL_SIZE(MAX_N_INPUTS_CAN_SIGN)]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + memset(internal_inputs, 0, BITVECTOR_REAL_SIZE(MAX_N_INPUTS_CAN_SIGN)); - if (state->cur_output_index >= state->n_outputs) { - // all outputs already processed - dc->next(confirm_transaction); - return; - } + placeholder_info_t placeholder_info; + memset(&placeholder_info, 0, sizeof(placeholder_info)); - // Reset cur struct - memset(&state->cur, 0, sizeof(state->cur)); + if (!find_first_internal_key_placeholder(dc, st, &placeholder_info)) return false; - int res = call_get_merkleized_map_with_callback( - dc, - state->outputs_root, - state->n_outputs, - state->cur_output_index, - make_callback(state, (dispatcher_callback_t) output_keys_callback), - &state->cur.in_out.map); - if (res < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } + // process each input + for (unsigned int cur_input_index = 0; cur_input_index < st->n_inputs; cur_input_index++) { + input_info_t input; + memset(&input, 0, sizeof(input)); - if (state->cur.in_out.unexpected_pubkey_error) { - PRINTF("Unexpected pubkey length\n"); // only compressed pubkeys are supported - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } + input_keys_callback_data_t callback_data = {.input = &input, + .placeholder_info = &placeholder_info}; + int res = call_get_merkleized_map_with_callback( + dc, + (void *) &callback_data, + st->inputs_root, + st->n_inputs, + cur_input_index, + (merkle_tree_elements_callback_t) input_keys_callback, + &input.in_out.map); + if (res < 0) { + PRINTF("Failed to process input map\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } - // read output amount and scriptpubkey + if (input.in_out.unexpected_pubkey_error) { + PRINTF("Unexpected pubkey length\n"); // only compressed pubkeys are supported + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } - uint8_t raw_result[8]; + // 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); + return false; + } - // Read the output's amount - int result_len = call_get_merkleized_map_value(dc, - &state->cur.in_out.map, - (uint8_t[]){PSBT_OUT_AMOUNT}, - 1, - raw_result, - sizeof(raw_result)); - if (result_len != 8) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - uint64_t value = read_u64_le(raw_result, 0); + // validate non-witness utxo (if present) and witness utxo (if present) - state->cur.output.value = value; - state->outputs_total_value += value; + if (input.has_nonWitnessUtxo) { + uint8_t prevout_hash[32]; - // Read the output's scriptPubKey - result_len = call_get_merkleized_map_value(dc, - &state->cur.in_out.map, - (uint8_t[]){PSBT_OUT_SCRIPT}, - 1, - state->cur.in_out.scriptPubKey, - sizeof(state->cur.in_out.scriptPubKey)); + // check if the prevout_hash of the transaction matches the computed one from the + // non-witness utxo + if (0 > call_get_merkleized_map_value(dc, + &input.in_out.map, + (uint8_t[]){PSBT_IN_PREVIOUS_TXID}, + 1, + prevout_hash, + sizeof(prevout_hash))) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } - if (result_len == -1 || result_len > (int) sizeof(state->cur.in_out.scriptPubKey)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } + // request non-witness utxo, and get the prevout's value and scriptpubkey + 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); + return false; + } - state->cur.in_out.scriptPubKey_len = result_len; + st->inputs_total_amount += input.prevout_amount; + } - dc->next(check_output_owned); -} + if (input.has_witnessUtxo) { + size_t wit_utxo_scriptPubkey_len; + uint8_t wit_utxo_scriptPubkey[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; + uint64_t wit_utxo_prevout_amount; -static void check_output_owned(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; + if (0 > get_amount_scriptpubkey_from_psbt_witness(dc, + &input.in_out.map, + &wit_utxo_prevout_amount, + wit_utxo_scriptPubkey, + &wit_utxo_scriptPubkey_len)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + }; + + if (input.has_nonWitnessUtxo) { + // we already know the scriptPubKey, but we double check that it matches + if (input.in_out.scriptPubKey_len != wit_utxo_scriptPubkey_len || + memcmp(input.in_out.scriptPubKey, + wit_utxo_scriptPubkey, + wit_utxo_scriptPubkey_len) != 0 || + input.prevout_amount != wit_utxo_prevout_amount) { + PRINTF( + "scriptPubKey or amount in non-witness utxo doesn't match with witness " + "utxo\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + } else { + // we extract the scriptPubKey and prevout amount from the witness utxo + st->inputs_total_amount += wit_utxo_prevout_amount; - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + input.prevout_amount = wit_utxo_prevout_amount; + input.in_out.scriptPubKey_len = wit_utxo_scriptPubkey_len; + memcpy(input.in_out.scriptPubKey, wit_utxo_scriptPubkey, wit_utxo_scriptPubkey_len); + } + } - int is_internal = is_in_out_internal(dc, - state, - &state->cur.in_out, - false, - state->cur.in_out.has_bip32_derivation); + // check if the input is internal; if not, continue - if (is_internal < 0) { - PRINTF("Error checking if output %d is internal\n", state->cur_output_index); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } else if (is_internal == 0) { - // external output, user needs to validate - ++state->external_outputs_count; + int is_internal = is_in_out_internal(dc, st, &input.in_out, true); + if (is_internal < 0) { + PRINTF("Error checking if input %d is internal\n", cur_input_index); + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } else if (is_internal == 0) { + PRINTF("INPUT %d is external\n", cur_input_index); + continue; + } - dc->next(output_validate_external); - return; - } else { - // valid change address, nothing to show to the user + bitvector_set(internal_inputs, cur_input_index, 1); + + int segwit_version = get_policy_segwit_version(st->wallet_policy_map); + + // For legacy inputs, the non-witness utxo must be present + // and the witness utxo must be absent. + // (This assumption is later relied on when signing). + 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); + return false; + } + } + + // For segwitv0 inputs, the non-witness utxo _should_ be present; we show a warning + // to the user otherwise, but we continue nonetheless on approval + if (segwit_version == 0 && !input.has_nonWitnessUtxo) { + PRINTF("Non-witness utxo missing for segwitv0 input. Will show a warning.\n"); + st->show_missing_nonwitnessutxo_warning = true; + } + + // 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); + return false; + } + + // If any of the internal inputs has a sighash type that is not SIGHASH_DEFAULT or + // SIGHASH_ALL, we show a warning + + if (!input.has_sighash_type) { + continue; + } + + // get the sighash_type + if (4 != call_get_merkleized_map_value_u32_le(dc, + &input.in_out.map, + (uint8_t[]){PSBT_IN_SIGHASH_TYPE}, + 1, + &input.sighash_type)) { + PRINTF("Malformed PSBT_IN_SIGHASH_TYPE for input %d\n", cur_input_index); + + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + if (((segwit_version > 0) && (input.sighash_type == SIGHASH_DEFAULT)) || + (input.sighash_type == SIGHASH_ALL)) { + PRINTF("Sighash type is SIGHASH_DEFAULT or SIGHASH_ALL\n"); + + } else if ((segwit_version >= 0) && + ((input.sighash_type == SIGHASH_NONE) || + (input.sighash_type == SIGHASH_SINGLE) || + (input.sighash_type == (SIGHASH_ANYONECANPAY | SIGHASH_ALL)) || + (input.sighash_type == (SIGHASH_ANYONECANPAY | SIGHASH_NONE)) || + (input.sighash_type == (SIGHASH_ANYONECANPAY | SIGHASH_SINGLE)))) { + PRINTF("Sighash type is non-default, will show a warning.\n"); + st->show_nondefault_sighash_warning = true; + } else { + PRINTF("Unsupported sighash\n"); + SEND_SW(dc, SW_NOT_SUPPORTED); + return false; + } + + 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); + return false; + } + } + + return true; +} + +static bool __attribute__((noinline)) +show_alerts(dispatcher_context_t *dc, + sign_psbt_state_t *st, + const uint8_t internal_inputs[static BITVECTOR_REAL_SIZE(MAX_N_INPUTS_CAN_SIGN)]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + + size_t count_external_inputs = 0; + for (unsigned int i = 0; i < st->n_inputs; i++) { + if (!bitvector_get(internal_inputs, i)) { + ++count_external_inputs; + } + } + + // If there are external inputs, it is unsafe to sign, therefore we warn the user + if (count_external_inputs > 0) { + if (count_external_inputs == st->n_inputs) { + // no internal inputs, nothing to sign + PRINTF("No internal inputs. Aborting\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } else { + // Swap feature: no external inputs allowed + if (G_swap_state.called_from_swap) { + PRINTF("External inputs not allowed in swap transactions\n"); + SEND_SW(dc, SW_FAIL_SWAP); + finalize_exchange_sign_transaction(false); + } + + // some internal and some external inputs, warn the user first + if (!ui_warn_external_inputs(dc)) { + SEND_SW(dc, SW_DENY); + return false; + } + } + } - state->change_outputs_total_value += state->cur.output.value; - ++state->change_count; + // If any segwitv0 input is missing the non-witness-utxo, we warn the user and ask for + // confirmation + if (st->show_missing_nonwitnessutxo_warning && !ui_warn_unverified_segwit_inputs(dc)) { + SEND_SW(dc, SW_DENY); + return false; + } - dc->next(output_next); - return; + // If any input has non-default sighash, we warn the user + if (st->show_nondefault_sighash_warning && !ui_warn_nondefault_sighash(dc)) { + SEND_SW(dc, SW_DENY); + return false; } + + return true; } -static void output_validate_external(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; +typedef struct { + placeholder_info_t *placeholder_info; + output_info_t *output; +} output_keys_callback_data_t; - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); +/** + * Callback to process all the keys of the current input map. + * Keeps track if the current input has a witness_utxo and/or a redeemScript. + */ +static void output_keys_callback(dispatcher_context_t *dc, + output_keys_callback_data_t *callback_data, + const merkleized_map_commitment_t *map_commitment, + int i, + buffer_t *data) { + size_t data_len = data->size - data->offset; + if (data_len >= 1) { + uint8_t key_type; + buffer_read_u8(data, &key_type); + + if ((key_type == PSBT_OUT_BIP32_DERIVATION || key_type == PSBT_OUT_TAP_BIP32_DERIVATION) && + !callback_data->output->in_out.placeholder_found) { + if (0 > + read_change_and_index_from_psbt_bip32_derivation(dc, + callback_data->placeholder_info, + &callback_data->output->in_out, + key_type, + data, + map_commitment, + i)) { + callback_data->output->in_out.unexpected_pubkey_error = true; + } + } + } +} + +static bool __attribute__((noinline)) display_output(dispatcher_context_t *dc, + sign_psbt_state_t *st, + int cur_output_index, + int external_outputs_count, + const output_info_t *output) { + (void) cur_output_index; // show this output's address char output_address[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE)]; - int address_len = get_script_address(state->cur.in_out.scriptPubKey, - state->cur.in_out.scriptPubKey_len, + int address_len = get_script_address(output->in_out.scriptPubKey, + output->in_out.scriptPubKey_len, output_address, sizeof(output_address)); if (address_len < 0) { // script does not have an address; check if OP_RETURN - if (is_opreturn(state->cur.in_out.scriptPubKey, state->cur.in_out.scriptPubKey_len)) { - int res = format_opscript_script(state->cur.in_out.scriptPubKey, - state->cur.in_out.scriptPubKey_len, + if (is_opreturn(output->in_out.scriptPubKey, output->in_out.scriptPubKey_len)) { + int res = format_opscript_script(output->in_out.scriptPubKey, + output->in_out.scriptPubKey_len, output_address); if (res == -1) { - PRINTF("Invalid or unsupported OP_RETURN for output %d\n", state->cur_output_index); + PRINTF("Invalid or unsupported OP_RETURN for output %d\n", cur_output_index); SEND_SW(dc, SW_NOT_SUPPORTED); - return; + return false; } } else { - PRINTF("Unknown or unsupported script type for output %d\n", state->cur_output_index); + PRINTF("Unknown or unsupported script type for output %d\n", cur_output_index); SEND_SW(dc, SW_NOT_SUPPORTED); - return; + return false; } } if (G_swap_state.called_from_swap) { - // Swap feature: do not show the address to the user, but double check it matches the - // request from app-exchange; it must be the only external output (checked elsewhere). - int swap_addr_len = strnlen(G_swap_state.destination_address, - sizeof(G_swap_state.destination_address) - 1); + // Swap feature: do not show the address to the user, but double check it matches + // the request from app-exchange; it must be the only external output (checked + // elsewhere). + int swap_addr_len = strlen(G_swap_state.destination_address); if (swap_addr_len != address_len || 0 != strncmp(G_swap_state.destination_address, output_address, address_len)) { // address did not match PRINTF("Mismatching address for swap\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } else { - // no need for user vaidation during swap - dc->next(output_next); - return; + SEND_SW(dc, SW_FAIL_SWAP); + finalize_exchange_sign_transaction(false); } } else { // Show address to the user - ui_validate_output(dc, - state->external_outputs_count, - output_address, - COIN_COINID_SHORT, - state->cur.output.value, - BITCOIN_DECIMALS, - output_next); - return; + if (!ui_validate_output(dc, + external_outputs_count, + st->outputs.n_external, + output_address, + COIN_COINID_SHORT, + output->value)) { + SEND_SW(dc, SW_DENY); + return false; + } } + return true; } -static void output_next(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - ++state->cur_output_index; - dc->next(process_output_map); -} - -// Performs any final checks if needed, then show the confirmation UI to the user -// (except during swap) -static void confirm_transaction(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - if (state->inputs_total_value < state->outputs_total_value) { - PRINTF("Negative fee is invalid\n"); - // negative fee transaction is invalid - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - if (state->change_count > 10) { - // As the information regarding change outputs is aggregated, we want to prevent the user - // from unknowingly signing a transaction that sends the change to too many (possibly - // unspendable) outputs. - PRINTF("Too many change outputs: %d\n", state->change_count); - SEND_SW(dc, SW_NOT_SUPPORTED); - return; - } - - uint64_t fee = state->inputs_total_value - state->outputs_total_value; +static bool read_outputs(dispatcher_context_t *dc, + sign_psbt_state_t *st, + placeholder_info_t *placeholder_info, + bool dry_run) { + // the counter used when showing outputs to the user, which ignores change outputs + // (0-indexed here, although the UX starts with 1) + int external_outputs_count = 0; + + for (unsigned int cur_output_index = 0; cur_output_index < st->n_outputs; cur_output_index++) { + output_info_t output; + memset(&output, 0, sizeof(output)); + + output_keys_callback_data_t callback_data = {.output = &output, + .placeholder_info = placeholder_info}; + int res = call_get_merkleized_map_with_callback( + dc, + (void *) &callback_data, + st->outputs_root, + st->n_outputs, + cur_output_index, + (merkle_tree_elements_callback_t) output_keys_callback, + &output.in_out.map); - if (G_swap_state.called_from_swap) { - // Swap feature: check total amount and fees are as expected; moreover, only one external - // output - if (state->external_outputs_count != 1) { - PRINTF("Swap transaction must have exactly 1 external output\n"); + if (res < 0) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } - if (fee != G_swap_state.fees) { - PRINTF("Mismatching fee for swap\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - uint64_t spent_amount = state->outputs_total_value - state->change_outputs_total_value; - if (spent_amount != G_swap_state.amount) { - PRINTF("Mismatching spent amount for swap\n"); + if (output.in_out.unexpected_pubkey_error) { + PRINTF("Unexpected pubkey length\n"); // only compressed pubkeys are supported SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } - // No user validation required during swap - dc->next(sign_init); - } else { - // Show final user validation UI - ui_validate_transaction(dc, COIN_COINID_SHORT, fee, BITCOIN_DECIMALS, NULL, sign_init); - } -} - -/** SIGNING FLOW - * - * Iterate over all inputs. For each input that should be signed, compute and sign sighash. - */ -// entry point for the signing flow -static void sign_init(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; + if (!dry_run) { + // Read output amount + uint8_t raw_result[8]; - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - // find and parse our registered key info in the wallet - bool our_key_found = false; - for (unsigned int i = 0; i < state->wallet_header_n_keys; i++) { - uint8_t key_info_str[MAX_POLICY_KEY_INFO_LEN]; - - int key_info_len = call_get_merkle_leaf_element(dc, - state->wallet_header_keys_info_merkle_root, - state->wallet_header_n_keys, - i, - key_info_str, - sizeof(key_info_str)); + // Read the output's amount + int result_len = call_get_merkleized_map_value(dc, + &output.in_out.map, + (uint8_t[]){PSBT_OUT_AMOUNT}, + 1, + raw_result, + sizeof(raw_result)); + if (result_len != 8) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + uint64_t value = read_u64_le(raw_result, 0); - if (key_info_len < 0) { - SEND_SW(dc, SW_BAD_STATE); // should never happen - return; + output.value = value; + st->outputs.total_amount += value; } - // Make a sub-buffer for the pubkey info - buffer_t key_info_buffer = buffer_create(key_info_str, key_info_len); + // Read the output's scriptPubKey + int result_len = call_get_merkleized_map_value(dc, + &output.in_out.map, + (uint8_t[]){PSBT_OUT_SCRIPT}, + 1, + output.in_out.scriptPubKey, + sizeof(output.in_out.scriptPubKey)); - policy_map_key_info_t our_key_info; - if (parse_policy_map_key_info(&key_info_buffer, &our_key_info) == -1) { - SEND_SW(dc, SW_BAD_STATE); // should never happen - return; - } - if (!validate_policy_map_extended_pubkey(&key_info, BIP32_PUBKEY_VERSION)) { + if (result_len == -1 || result_len > (int) sizeof(output.in_out.scriptPubKey)) { SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - uint32_t fpr = read_u32_be(our_key_info.master_key_fingerprint, 0); - if (fpr == state->master_key_fingerprint) { - // it could be a collision on the fingerprint; we verify that we can actually generate - // the same pubkey - char pubkey_derived[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; - int serialized_pubkey_len = - get_serialized_extended_pubkey_at_path(our_key_info.master_key_derivation, - our_key_info.master_key_derivation_len, - BIP32_PUBKEY_VERSION, - pubkey_derived); - if (serialized_pubkey_len == -1) { - SEND_SW(dc, SW_BAD_STATE); - return; - } + return false; + } - if (strncmp(our_key_info.ext_pubkey, pubkey_derived, MAX_SERIALIZED_PUBKEY_LENGTH) == - 0) { - our_key_found = true; + output.in_out.scriptPubKey_len = result_len; - state->our_key_derivation_length = our_key_info.master_key_derivation_len; - for (int i = 0; i < our_key_info.master_key_derivation_len; i++) { - state->our_key_derivation[i] = our_key_info.master_key_derivation[i]; - } + int is_internal = is_in_out_internal(dc, st, &output.in_out, false); - break; - } + if (is_internal < 0) { + PRINTF("Error checking if output %d is internal\n", cur_output_index); + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } else if (is_internal == 0) { + // external output, user needs to validate + ++external_outputs_count; + + if (!dry_run && + !display_output(dc, st, cur_output_index, external_outputs_count, &output)) + return false; + } else if (!dry_run) { + // valid change address, nothing to show to the user + + st->outputs.change_total_amount += output.value; + ++st->outputs.n_change; } } - if (!our_key_found) { - PRINTF("Couldn't find internal key\n"); - SEND_SW( - dc, - SW_BAD_STATE); // should never happen if we only register wallets with an internal key - return; - } - - state->segwit_hashes_computed = false; + st->outputs.n_external = external_outputs_count; - state->cur_input_index = 0; - dc->next(sign_process_input_map); + return true; } -static void sign_process_input_map(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; +static bool __attribute__((noinline)) +process_outputs(dispatcher_context_t *dc, sign_psbt_state_t *st) { + /** OUTPUTS VERIFICATION FLOW + * + * For each output, check if it's a change address. + * Show each output that is not a change address to the user for verification. + */ - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); - // skip external inputs - while (state->cur_input_index < state->n_inputs && - !bitvector_get(state->internal_inputs, state->cur_input_index)) { - PRINTF("Skipping signing external input %d\n", state->cur_input_index); - ++state->cur_input_index; - } + placeholder_info_t placeholder_info; + memset(&placeholder_info, 0, sizeof(placeholder_info)); - if (state->cur_input_index >= state->n_inputs) { - // all inputs already processed - dc->next(finalize); - return; - } + if (!find_first_internal_key_placeholder(dc, st, &placeholder_info)) return false; - // Reset cur struct - memset(&state->cur, 0, sizeof(state->cur)); + memset(&st->outputs, 0, sizeof(st->outputs)); - int res = call_get_merkleized_map_with_callback( - dc, - state->inputs_root, - state->n_inputs, - state->cur_input_index, - make_callback(state, (dispatcher_callback_t) input_keys_callback), - &state->cur.in_out.map); - if (res < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } +#ifdef HAVE_NBGL + // Only on Stax, we need to preprocess all the outputs in order to + // compute the total number of non-change outputs. + // As it's a time-consuming operation, we use avoid doing this useless + // work on other models. - if (!state->cur.input.has_sighash_type) { - state->cur.input.sighash_type = SIGHASH_ALL; - } else { - // Get sighash type - if (4 != call_get_merkleized_map_value_u32_le(dc, - &state->cur.in_out.map, - (uint8_t[]){PSBT_IN_SIGHASH_TYPE}, - 1, - &state->cur.input.sighash_type)) { - PRINTF("Malformed PSBT_IN_SIGHASH_TYPE for input %d\n", state->cur_input_index); + if (!read_outputs(dc, st, &placeholder_info, true)) return false; - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } + if (!G_swap_state.called_from_swap && !ui_transaction_prompt(dc, st->outputs.n_external)) { + SEND_SW(dc, SW_DENY); + return false; } +#endif - // TODO: add support for other sighash flags - if (state->cur.input.sighash_type != SIGHASH_ALL) { - PRINTF("Only SIGHASH_ALL is currently supported\n"); - SEND_SW(dc, SW_NOT_SUPPORTED); - return; - } - - // get path, obtain change and address_index - - int bip32_path_len; - uint32_t bip32_path[MAX_BIP32_PATH_STEPS]; - uint32_t fingerprint; + if (!read_outputs(dc, st, &placeholder_info, false)) return false; - if (state->wallet_policy_map.type == TOKEN_TR) { - // taproot input, use PSBT_IN_TAP_BIP32_DERIVATION - uint8_t key[1 + 32]; - key[0] = PSBT_IN_TAP_BIP32_DERIVATION; - memcpy(key + 1, state->cur.in_out.bip32_derivation_pubkey, sizeof(key) - 1); - - bip32_path_len = get_emptyhashes_fingerprint_and_path(dc, - &state->cur.in_out.map, - key, - sizeof(key), - &fingerprint, - bip32_path); - } else { - // legacy or segwitv0 input, use PSBT_IN_BIP32_DERIVATION - uint8_t key[1 + 33]; - key[0] = PSBT_IN_BIP32_DERIVATION; - memcpy(key + 1, state->cur.in_out.bip32_derivation_pubkey, sizeof(key) - 1); + return true; +} - bip32_path_len = get_fingerprint_and_path(dc, - &state->cur.in_out.map, - key, - sizeof(key), - &fingerprint, - bip32_path); - } +static bool __attribute__((noinline)) +confirm_transaction(dispatcher_context_t *dc, sign_psbt_state_t *st) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); - if (bip32_path_len < 2) { - SEND_SW(dc, SW_BAD_STATE); - return; + if (st->inputs_total_amount < st->outputs.total_amount) { + PRINTF("Negative fee is invalid\n"); + // negative fee transaction is invalid + SEND_SW(dc, SW_INCORRECT_DATA); + return false; } - state->cur.input.change = bip32_path[bip32_path_len - 2]; - state->cur.input.address_index = bip32_path[bip32_path_len - 1]; - - // Sign as segwit input iff it has a witness utxo - if (!state->cur.input.has_witnessUtxo) { - dc->next(sign_legacy); - } else { - dc->next(sign_segwit); + if (st->outputs.n_change > 10) { + // As the information regarding change outputs is aggregated, we want to prevent the user + // from unknowingly signing a transaction that sends the change to too many (possibly + // unspendable) outputs. + PRINTF("Too many change outputs: %d\n", st->outputs.n_change); + SEND_SW(dc, SW_NOT_SUPPORTED); + return false; } -} -static void sign_legacy(dispatcher_context_t *dc) { - // sign legacy P2PKH or P2SH + uint64_t fee = st->inputs_total_amount - st->outputs.total_amount; - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + if (G_swap_state.called_from_swap) { + // Swap feature: there must be only one external output + if (st->outputs.n_external != 1) { + PRINTF("Swap transaction must have exactly 1 external output\n"); + SEND_SW(dc, SW_FAIL_SWAP); + finalize_exchange_sign_transaction(false); + } - // sign_non_witness(non_witness_utxo.vout[psbt.tx.input_[i].prevout.n].scriptPubKey, i) + // Swap feature: check 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); + 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); + finalize_exchange_sign_transaction(false); + } + } else { + // if the value of fees is 10% or more of the amount, and it's more than 10000 + if (10 * fee >= st->inputs_total_amount && st->inputs_total_amount > 10000) { + if (!ui_warn_high_fee(dc)) { + SEND_SW(dc, SW_DENY); + ui_post_processing_confirm_transaction(dc, false); + return false; + } + } - uint64_t tmp; // unused - if (0 > get_amount_scriptpubkey_from_psbt_nonwitness(dc, - &state->cur.in_out.map, - &tmp, - state->cur.in_out.scriptPubKey, - &state->cur.in_out.scriptPubKey_len, - NULL)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; + // Show final user validation UI + bool is_self_transfer = st->outputs.n_external == 0; + if (!ui_validate_transaction(dc, COIN_COINID_SHORT, fee, is_self_transfer)) { + SEND_SW(dc, SW_DENY); + ui_post_processing_confirm_transaction(dc, false); + return false; + } } - dc->next(sign_legacy_compute_sighash); + return true; } -static void sign_legacy_compute_sighash(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); +static bool __attribute__((noinline)) compute_sighash_legacy(dispatcher_context_t *dc, + sign_psbt_state_t *st, + input_info_t *input, + unsigned int cur_input_index, + uint8_t sighash[static 32]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); cx_sha256_t sighash_context; cx_sha256_init(&sighash_context); uint8_t tmp[4]; - write_u32_le(tmp, 0, state->tx_version); + write_u32_le(tmp, 0, st->tx_version); crypto_hash_update(&sighash_context.header, tmp, 4); - crypto_hash_update_varint(&sighash_context.header, state->n_inputs); + crypto_hash_update_varint(&sighash_context.header, st->n_inputs); - for (unsigned int i = 0; i < state->n_inputs; i++) { + for (unsigned int i = 0; i < st->n_inputs; i++) { // get this input's map merkleized_map_commitment_t ith_map; - if (i != state->cur_input_index) { - int res = call_get_merkleized_map(dc, state->inputs_root, state->n_inputs, i, &ith_map); + if (i != cur_input_index) { + int res = call_get_merkleized_map(dc, st->inputs_root, st->n_inputs, i, &ith_map); if (res < 0) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } } else { // Avoid requesting the same map unnecessarily // (might be removed once a caching mechanism is implemented) - memcpy(&ith_map, &state->cur.in_out.map, sizeof(ith_map)); + memcpy(&ith_map, &input->in_out.map, sizeof(input->in_out.map)); } // get prevout hash and output index for the i-th input @@ -1274,7 +1430,7 @@ static void sign_legacy_compute_sighash(dispatcher_context_t *dc) { ith_prevout_hash, 32)) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } crypto_hash_update(&sighash_context.header, ith_prevout_hash, 32); @@ -1287,29 +1443,28 @@ static void sign_legacy_compute_sighash(dispatcher_context_t *dc) { ith_prevout_n_raw, 4)) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } crypto_hash_update(&sighash_context.header, ith_prevout_n_raw, 4); - if (i != state->cur_input_index) { + if (i != cur_input_index) { // empty scriptcode crypto_hash_update_u8(&sighash_context.header, 0x00); } else { - if (!state->cur.input.has_redeemScript) { + if (!input->has_redeemScript) { // P2PKH, the script_code is the prevout's scriptPubKey - crypto_hash_update_varint(&sighash_context.header, - state->cur.in_out.scriptPubKey_len); + crypto_hash_update_varint(&sighash_context.header, input->in_out.scriptPubKey_len); crypto_hash_update(&sighash_context.header, - state->cur.in_out.scriptPubKey, - state->cur.in_out.scriptPubKey_len); + input->in_out.scriptPubKey, + input->in_out.scriptPubKey_len); } else { // P2SH, the script_code is the redeemScript // update sighash_context with the length-prefixed redeem script int redeemScript_len = update_hashes_with_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_REDEEM_SCRIPT}, 1, NULL, @@ -1318,7 +1473,7 @@ static void sign_legacy_compute_sighash(dispatcher_context_t *dc) { if (redeemScript_len < 0) { PRINTF("Error fetching redeemScript\n"); SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } } } @@ -1338,274 +1493,62 @@ static void sign_legacy_compute_sighash(dispatcher_context_t *dc) { } // outputs - crypto_hash_update_varint(&sighash_context.header, state->n_outputs); - if (hash_outputs(dc, &sighash_context.header) == -1) { + crypto_hash_update_varint(&sighash_context.header, st->n_outputs); + if (hash_outputs(dc, st, &sighash_context.header) == -1) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } // nLocktime - write_u32_le(tmp, 0, state->locktime); + write_u32_le(tmp, 0, st->locktime); crypto_hash_update(&sighash_context.header, tmp, 4); // hash type - write_u32_le(tmp, 0, state->cur.input.sighash_type); + write_u32_le(tmp, 0, input->sighash_type); crypto_hash_update(&sighash_context.header, tmp, 4); // compute sighash - crypto_hash_digest(&sighash_context.header, state->sighash, 32); - cx_hash_sha256(state->sighash, 32, state->sighash, 32); + crypto_hash_digest(&sighash_context.header, sighash, 32); + cx_hash_sha256(sighash, 32, sighash, 32); - dc->next(sign_sighash_ecdsa); + return true; } -static void sign_segwit(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); - - int segwit_version; - - { - uint64_t amount; - if (0 > get_amount_scriptpubkey_from_psbt_witness(dc, - &state->cur.in_out.map, - &amount, - state->cur.in_out.scriptPubKey, - &state->cur.in_out.scriptPubKey_len)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - state->inputs_total_value += amount; - - if (state->cur.input.has_redeemScript) { - // Get redeemScript - uint8_t redeemScript[64]; - - int redeemScript_length = - call_get_merkleized_map_value(dc, - &state->cur.in_out.map, - (uint8_t[]){PSBT_IN_REDEEM_SCRIPT}, - 1, - redeemScript, - sizeof(redeemScript)); - if (redeemScript_length < 0) { - PRINTF("Error fetching redeem script\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - uint8_t p2sh_redeemscript[2 + 20 + 1]; - p2sh_redeemscript[0] = 0xa9; - p2sh_redeemscript[1] = 0x14; - crypto_hash160(redeemScript, redeemScript_length, p2sh_redeemscript + 2); - p2sh_redeemscript[22] = 0x87; - - if (state->cur.in_out.scriptPubKey_len != 23 || - memcmp(state->cur.in_out.scriptPubKey, p2sh_redeemscript, 23) != 0) { - PRINTF("witnessUtxo's scriptPubKey does not match redeemScript\n"); - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - if (redeemScript_length <= (int)sizeof(state->cur.input.script)) { - state->cur.input.script_len = redeemScript_length; - memcpy(state->cur.input.script, redeemScript, redeemScript_length); - } else { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - segwit_version = get_segwit_version(redeemScript, redeemScript_length); - } else { - if (state->cur.in_out.scriptPubKey_len <= sizeof(state->cur.input.script)) { - state->cur.input.script_len = state->cur.in_out.scriptPubKey_len; - memcpy(state->cur.input.script, - state->cur.in_out.scriptPubKey, - state->cur.in_out.scriptPubKey_len); - } else { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - segwit_version = get_segwit_version(state->cur.in_out.scriptPubKey, - state->cur.in_out.scriptPubKey_len); - } - - if (segwit_version > 1) { - PRINTF("Segwit version not supported: %d\n", segwit_version); - SEND_SW(dc, SW_NOT_SUPPORTED); - return; - } - } - - // compute all the tx-wide hashes - - if (!state->segwit_hashes_computed) { - { - // compute sha_prevouts and sha_sequences - cx_sha256_t sha_prevouts_context, sha_sequences_context; - - // compute hashPrevouts and hashSequence - cx_sha256_init(&sha_prevouts_context); - cx_sha256_init(&sha_sequences_context); - - for (unsigned int i = 0; i < state->n_inputs; i++) { - // get this input's map - merkleized_map_commitment_t ith_map; - - int res = - call_get_merkleized_map(dc, state->inputs_root, state->n_inputs, i, &ith_map); - if (res < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - // get prevout hash and output index for the i-th input - uint8_t ith_prevout_hash[32]; - if (32 != call_get_merkleized_map_value(dc, - &ith_map, - (uint8_t[]){PSBT_IN_PREVIOUS_TXID}, - 1, - ith_prevout_hash, - 32)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - crypto_hash_update(&sha_prevouts_context.header, ith_prevout_hash, 32); - - uint8_t ith_prevout_n_raw[4]; - if (4 != call_get_merkleized_map_value(dc, - &ith_map, - (uint8_t[]){PSBT_IN_OUTPUT_INDEX}, - 1, - ith_prevout_n_raw, - 4)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - crypto_hash_update(&sha_prevouts_context.header, ith_prevout_n_raw, 4); - - uint8_t ith_nSequence_raw[4]; - if (4 != call_get_merkleized_map_value(dc, - &ith_map, - (uint8_t[]){PSBT_IN_SEQUENCE}, - 1, - ith_nSequence_raw, - 4)) { - // if no PSBT_IN_SEQUENCE is present, we must assume nSequence 0xFFFFFFFF - memset(ith_nSequence_raw, 0xFF, 4); - } - - crypto_hash_update(&sha_sequences_context.header, ith_nSequence_raw, 4); - } - - crypto_hash_digest(&sha_prevouts_context.header, state->hashes.sha_prevouts, 32); - crypto_hash_digest(&sha_sequences_context.header, state->hashes.sha_sequences, 32); - } - - { - // compute sha_outputs - cx_sha256_t sha_outputs_context; - cx_sha256_init(&sha_outputs_context); - - if (hash_outputs(dc, &sha_outputs_context.header) == -1) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - crypto_hash_digest(&sha_outputs_context.header, state->hashes.sha_outputs, 32); - } - - { - // compute sha_amounts and sha_scriptpubkeys - // TODO: could be skipped if there are no segwitv1 inputs to sign - - cx_sha256_t sha_amounts_context, sha_scriptpubkeys_context; - - cx_sha256_init(&sha_amounts_context); - cx_sha256_init(&sha_scriptpubkeys_context); - - for (unsigned int i = 0; i < state->n_inputs; i++) { - // get this input's map - merkleized_map_commitment_t ith_map; - - int res = - call_get_merkleized_map(dc, state->inputs_root, state->n_inputs, i, &ith_map); - if (res < 0) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - uint64_t in_amount; - uint8_t in_scriptPubKey[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; - size_t in_scriptPubKey_len; - - if (0 > get_amount_scriptpubkey_from_psbt(dc, - &ith_map, - &in_amount, - in_scriptPubKey, - &in_scriptPubKey_len)) { - SEND_SW(dc, SW_INCORRECT_DATA); - return; - } - - uint8_t in_amount_le[8]; - write_u64_le(in_amount_le, 0, in_amount); - crypto_hash_update(&sha_amounts_context.header, in_amount_le, 8); - - crypto_hash_update_varint(&sha_scriptpubkeys_context.header, in_scriptPubKey_len); - crypto_hash_update(&sha_scriptpubkeys_context.header, - in_scriptPubKey, - in_scriptPubKey_len); - } - - crypto_hash_digest(&sha_amounts_context.header, state->hashes.sha_amounts, 32); - crypto_hash_digest(&sha_scriptpubkeys_context.header, - state->hashes.sha_scriptpubkeys, - 32); - } - } - state->segwit_hashes_computed = true; - - if (segwit_version == 0) { - dc->next(sign_segwit_v0); - return; - } else if (segwit_version == 1) { - dc->next(sign_segwit_v1); - - return; - } - - SEND_SW(dc, SW_BAD_STATE); // can't happen - return; -} - -static void sign_segwit_v0(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); +static bool __attribute__((noinline)) compute_sighash_segwitv0(dispatcher_context_t *dc, + sign_psbt_state_t *st, + segwit_hashes_t *hashes, + input_info_t *input, + unsigned int cur_input_index, + uint8_t sighash[static 32]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); cx_sha256_t sighash_context; cx_sha256_init(&sighash_context); uint8_t tmp[8]; + uint8_t sighash_byte = (uint8_t) (input->sighash_type & 0xFF); // nVersion - write_u32_le(tmp, 0, state->tx_version); + write_u32_le(tmp, 0, st->tx_version); crypto_hash_update(&sighash_context.header, tmp, 4); { uint8_t dbl_hash[32]; + memset(dbl_hash, 0, 32); // add to hash: hashPrevouts = sha256(sha_prevouts) - cx_hash_sha256(state->hashes.sha_prevouts, 32, dbl_hash, 32); + if (!(sighash_byte & SIGHASH_ANYONECANPAY)) { + cx_hash_sha256(hashes->sha_prevouts, 32, dbl_hash, 32); + } + crypto_hash_update(&sighash_context.header, dbl_hash, 32); + memset(dbl_hash, 0, 32); // add to hash: hashSequence sha256(sha_sequences) - cx_hash_sha256(state->hashes.sha_sequences, 32, dbl_hash, 32); + if (!(sighash_byte & SIGHASH_ANYONECANPAY) && (sighash_byte & 0x1f) != SIGHASH_SINGLE && + (sighash_byte & 0x1f) != SIGHASH_NONE) { + cx_hash_sha256(hashes->sha_sequences, 32, dbl_hash, 32); + } crypto_hash_update(&sighash_context.header, dbl_hash, 32); } @@ -1615,38 +1558,38 @@ static void sign_segwit_v0(dispatcher_context_t *dc) { // get prevout hash and output index for the current input uint8_t prevout_hash[32]; if (32 != call_get_merkleized_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_PREVIOUS_TXID}, 1, prevout_hash, 32)) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } crypto_hash_update(&sighash_context.header, prevout_hash, 32); uint8_t prevout_n_raw[4]; if (4 != call_get_merkleized_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_OUTPUT_INDEX}, 1, prevout_n_raw, 4)) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } crypto_hash_update(&sighash_context.header, prevout_n_raw, 4); } // scriptCode - if (is_p2wpkh(state->cur.input.script, state->cur.input.script_len)) { + if (is_p2wpkh(input->script, input->script_len)) { // P2WPKH(script[2:22]) crypto_hash_update_u32(&sighash_context.header, 0x1976a914); - crypto_hash_update(&sighash_context.header, state->cur.input.script + 2, 20); + crypto_hash_update(&sighash_context.header, input->script + 2, 20); crypto_hash_update_u16(&sighash_context.header, 0x88ac); - } else if (is_p2wsh(state->cur.input.script, state->cur.input.script_len)) { + } else if (is_p2wsh(input->script, input->script_len)) { // P2WSH // update sighash_context.header with the length-prefixed witnessScript, @@ -1655,7 +1598,7 @@ static void sign_segwit_v0(dispatcher_context_t *dc) { cx_sha256_init(&witnessScript_hash_context); int witnessScript_len = update_hashes_with_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_WITNESS_SCRIPT}, 1, &witnessScript_hash_context.header, @@ -1664,25 +1607,24 @@ static void sign_segwit_v0(dispatcher_context_t *dc) { if (witnessScript_len < 0) { PRINTF("Error fetching witnessScript\n"); SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } uint8_t witnessScript_hash[32]; crypto_hash_digest(&witnessScript_hash_context.header, witnessScript_hash, 32); // check that script == P2WSH(witnessScript) - if (state->cur.input.script_len != 2 + 32 || state->cur.input.script[0] != 0x00 || - state->cur.input.script[1] != 0x20 || - memcmp(state->cur.input.script + 2, witnessScript_hash, 32) != 0) { + if (input->script_len != 2 + 32 || input->script[0] != 0x00 || input->script[1] != 0x20 || + memcmp(input->script + 2, witnessScript_hash, 32) != 0) { PRINTF("Mismatching witnessScript\n"); SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } } else { PRINTF("Invalid or unsupported script in segwit transaction\n"); SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } { @@ -1690,14 +1632,14 @@ static void sign_segwit_v0(dispatcher_context_t *dc) { uint8_t witness_utxo[8 + 1 + MAX_PREVOUT_SCRIPTPUBKEY_LEN]; int witness_utxo_len = call_get_merkleized_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_WITNESS_UTXO}, 1, witness_utxo, sizeof(witness_utxo)); if (witness_utxo_len < 8) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } crypto_hash_update(&sighash_context.header, @@ -1709,7 +1651,7 @@ static void sign_segwit_v0(dispatcher_context_t *dc) { { uint8_t nSequence_raw[4]; if (4 != call_get_merkleized_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_SEQUENCE}, 1, nSequence_raw, @@ -1724,101 +1666,133 @@ static void sign_segwit_v0(dispatcher_context_t *dc) { // compute hashOutputs = sha256(sha_outputs) uint8_t hashOutputs[32]; - cx_hash_sha256(state->hashes.sha_outputs, 32, hashOutputs, 32); + memset(hashOutputs, 0, 32); + if ((sighash_byte & 0x1f) != SIGHASH_SINGLE && (sighash_byte & 0x1f) != SIGHASH_NONE) { + cx_hash_sha256(hashes->sha_outputs, 32, hashOutputs, 32); + + } else if ((sighash_byte & 0x1f) == SIGHASH_SINGLE && cur_input_index < st->n_outputs) { + cx_sha256_t sha_output_context; + cx_sha256_init(&sha_output_context); + if (hash_output_n(dc, st, &sha_output_context.header, cur_input_index) == -1) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + crypto_hash_digest(&sha_output_context.header, hashOutputs, 32); + cx_hash_sha256(hashOutputs, 32, hashOutputs, 32); + } crypto_hash_update(&sighash_context.header, hashOutputs, 32); } // nLocktime - write_u32_le(tmp, 0, state->locktime); + write_u32_le(tmp, 0, st->locktime); crypto_hash_update(&sighash_context.header, tmp, 4); // sighash type - write_u32_le(tmp, 0, state->cur.input.sighash_type); + write_u32_le(tmp, 0, input->sighash_type); crypto_hash_update(&sighash_context.header, tmp, 4); // compute sighash - crypto_hash_digest(&sighash_context.header, state->sighash, 32); - cx_hash_sha256(state->sighash, 32, state->sighash, 32); + crypto_hash_digest(&sighash_context.header, sighash, 32); + cx_hash_sha256(sighash, 32, sighash, 32); - dc->next(sign_sighash_ecdsa); + return true; } -static void sign_segwit_v1(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; - - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); +static bool __attribute__((noinline)) compute_sighash_segwitv1(dispatcher_context_t *dc, + sign_psbt_state_t *st, + segwit_hashes_t *hashes, + input_info_t *input, + unsigned int cur_input_index, + placeholder_info_t *placeholder_info, + uint8_t sighash[static 32]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); cx_sha256_t sighash_context; crypto_tr_tagged_hash_init(&sighash_context, BIP0341_sighash_tag, sizeof(BIP0341_sighash_tag)); // the first 0x00 byte is not part of SigMsg crypto_hash_update_u8(&sighash_context.header, 0x00); - uint8_t tmp[32]; + uint8_t tmp[MAX(32, 8 + 1 + MAX_PREVOUT_SCRIPTPUBKEY_LEN)]; // hash type - uint8_t sighash_byte = (uint8_t) (state->cur.input.sighash_type & 0xFF); + uint8_t sighash_byte = (uint8_t) (input->sighash_type & 0xFF); crypto_hash_update_u8(&sighash_context.header, sighash_byte); // nVersion - write_u32_le(tmp, 0, state->tx_version); + write_u32_le(tmp, 0, st->tx_version); crypto_hash_update(&sighash_context.header, tmp, 4); // nLocktime - write_u32_le(tmp, 0, state->locktime); + write_u32_le(tmp, 0, st->locktime); crypto_hash_update(&sighash_context.header, tmp, 4); if ((sighash_byte & 0x80) != SIGHASH_ANYONECANPAY) { - crypto_hash_update(&sighash_context.header, state->hashes.sha_prevouts, 32); - crypto_hash_update(&sighash_context.header, state->hashes.sha_amounts, 32); - crypto_hash_update(&sighash_context.header, state->hashes.sha_scriptpubkeys, 32); - crypto_hash_update(&sighash_context.header, state->hashes.sha_sequences, 32); + crypto_hash_update(&sighash_context.header, hashes->sha_prevouts, 32); + crypto_hash_update(&sighash_context.header, hashes->sha_amounts, 32); + crypto_hash_update(&sighash_context.header, hashes->sha_scriptpubkeys, 32); + crypto_hash_update(&sighash_context.header, hashes->sha_sequences, 32); } if ((sighash_byte & 3) != SIGHASH_NONE && (sighash_byte & 3) != SIGHASH_SINGLE) { - crypto_hash_update(&sighash_context.header, state->hashes.sha_outputs, 32); + crypto_hash_update(&sighash_context.header, hashes->sha_outputs, 32); } - // annex and ext_flags not supported, so spend_type = 0 - crypto_hash_update_u8(&sighash_context.header, 0x00); + // ext_flag + uint8_t ext_flag = placeholder_info->is_tapscript ? 1 : 0; + // annex is not supported + const uint8_t annex_present = 0; + uint8_t spend_type = ext_flag * 2 + annex_present; + crypto_hash_update_u8(&sighash_context.header, spend_type); if ((sighash_byte & 0x80) == SIGHASH_ANYONECANPAY) { // outpoint (hash) if (32 != call_get_merkleized_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_PREVIOUS_TXID}, 1, tmp, 32)) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } crypto_hash_update(&sighash_context.header, tmp, 32); // outpoint (output index) if (4 != call_get_merkleized_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_OUTPUT_INDEX}, 1, tmp, 4)) { SEND_SW(dc, SW_INCORRECT_DATA); - return; + return false; } crypto_hash_update(&sighash_context.header, tmp, 4); + if (8 > call_get_merkleized_map_value(dc, + &input->in_out.map, + (uint8_t[]){PSBT_IN_WITNESS_UTXO}, + 1, + tmp, + 8 + 1 + MAX_PREVOUT_SCRIPTPUBKEY_LEN)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + // amount - write_u64_le(tmp, 0, state->cur.input.prevout_amount); crypto_hash_update(&sighash_context.header, tmp, 8); // scriptPubKey + crypto_hash_update_varint(&sighash_context.header, input->in_out.scriptPubKey_len); + crypto_hash_update(&sighash_context.header, - state->cur.in_out.scriptPubKey, - state->cur.in_out.scriptPubKey_len); + input->in_out.scriptPubKey, + input->in_out.scriptPubKey_len); // nSequence if (4 != call_get_merkleized_map_value(dc, - &state->cur.in_out.map, + &input->in_out.map, (uint8_t[]){PSBT_IN_SEQUENCE}, 1, tmp, @@ -1829,143 +1803,755 @@ static void sign_segwit_v1(dispatcher_context_t *dc) { crypto_hash_update(&sighash_context.header, tmp, 4); } else { // input_index - write_u32_le(tmp, 0, state->cur_input_index); + write_u32_le(tmp, 0, cur_input_index); crypto_hash_update(&sighash_context.header, tmp, 4); } // no annex - // TODO: SIGHASH_SINGLE not implemented - - crypto_hash_digest(&sighash_context.header, state->sighash, 32); - - dc->next(sign_sighash_schnorr); -} + if ((sighash_byte & 3) == SIGHASH_SINGLE) { + // compute sha_output + cx_sha256_t sha_output_context; + cx_sha256_init(&sha_output_context); -// Common for legacy and segwitv0 transactions -static void sign_sighash_ecdsa(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; + if (hash_output_n(dc, st, &sha_output_context.header, cur_input_index) == -1) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + crypto_hash_digest(&sha_output_context.header, tmp, 32); - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + crypto_hash_update(&sighash_context.header, tmp, 32); + } - uint32_t sign_path[MAX_BIP32_PATH_STEPS]; - for (int i = 0; i < state->our_key_derivation_length; i++) { - sign_path[i] = state->our_key_derivation[i]; + if (placeholder_info->is_tapscript) { + // If spending a tapscript, append the Common Signature Message Extension per BIP-0342 + crypto_hash_update(&sighash_context.header, placeholder_info->tapleaf_hash, 32); + crypto_hash_update_u8(&sighash_context.header, 0x00); // key_version + crypto_hash_update_u32(&sighash_context.header, 0xffffffff); // no OP_CODESEPARATOR } - sign_path[state->our_key_derivation_length] = state->cur.input.change; - sign_path[state->our_key_derivation_length + 1] = state->cur.input.address_index; - int sign_path_len = state->our_key_derivation_length + 2; + crypto_hash_digest(&sighash_context.header, sighash, 32); - uint8_t sig[MAX_DER_SIG_LEN]; + return true; +} - int sig_len = - crypto_ecdsa_sign_sha256_hash_with_key(sign_path, sign_path_len, state->sighash, sig, NULL); - if (sig_len < 0) { - // unexpected error when signing - SEND_SW(dc, SW_BAD_STATE); - return; - } +static bool __attribute__((noinline)) yield_signature(dispatcher_context_t *dc, + sign_psbt_state_t *st, + unsigned int cur_input_index, + uint8_t *pubkey, + uint8_t pubkey_len, + uint8_t *tapleaf_hash, + uint8_t *sig, + size_t sig_len) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); // yield signature uint8_t cmd = CCMD_YIELD; dc->add_to_response(&cmd, 1); uint8_t buf[9]; - int input_index_varint_len = varint_write(buf, 0, state->cur_input_index); + int input_index_varint_len = varint_write(buf, 0, cur_input_index); dc->add_to_response(&buf, input_index_varint_len); - dc->add_to_response(&sig, sig_len); - uint8_t sighash_byte = (uint8_t) (state->cur.input.sighash_type & 0xFF); - dc->add_to_response(&sighash_byte, 1); + // for tapscript signatures, we concatenate the (x-only) pubkey with the tapleaf hash + uint8_t augm_pubkey_len = pubkey_len + (tapleaf_hash != NULL ? 32 : 0); + + // the pubkey is not output in version 0 of the protocol + if (st->protocol_version >= 1) { + dc->add_to_response(&augm_pubkey_len, 1); + dc->add_to_response(pubkey, pubkey_len); + + if (tapleaf_hash != NULL) { + dc->add_to_response(tapleaf_hash, 32); + } + } + + dc->add_to_response(sig, sig_len); dc->finalize_response(SW_INTERRUPTED_EXECUTION); if (dc->process_interruption(dc) < 0) { SEND_SW(dc, SW_BAD_STATE); - return; + return false; } + return true; +} + +static bool __attribute__((noinline)) +sign_sighash_ecdsa_and_yield(dispatcher_context_t *dc, + sign_psbt_state_t *st, + placeholder_info_t *placeholder_info, + input_info_t *input, + unsigned int cur_input_index, + uint8_t sighash[static 32]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + + uint32_t sign_path[MAX_BIP32_PATH_STEPS]; + for (int i = 0; i < placeholder_info->key_derivation_length; i++) { + sign_path[i] = placeholder_info->key_derivation[i]; + } + sign_path[placeholder_info->key_derivation_length] = + input->in_out.is_change ? placeholder_info->placeholder.num_second + : placeholder_info->placeholder.num_first; + sign_path[placeholder_info->key_derivation_length + 1] = input->in_out.address_index; + + int sign_path_len = placeholder_info->key_derivation_length + 2; + + uint8_t sig[MAX_DER_SIG_LEN + 1]; // extra byte for the appended sighash-type + + uint8_t pubkey[33]; + + int sig_len = crypto_ecdsa_sign_sha256_hash_with_key(sign_path, + sign_path_len, + sighash, + pubkey, + sig, + NULL); + if (sig_len < 0) { + // unexpected error when signing + SEND_SW(dc, SW_BAD_STATE); + return false; + } + + // append the sighash type byte + uint8_t sighash_byte = (uint8_t) (input->sighash_type & 0xFF); + sig[sig_len++] = sighash_byte; + + if (!yield_signature(dc, st, cur_input_index, pubkey, 33, NULL, sig, sig_len)) return false; - ++state->cur_input_index; - dc->next(sign_process_input_map); + return true; } -// Signing for segwitv1 (taproot) -static void sign_sighash_schnorr(dispatcher_context_t *dc) { - sign_psbt_state_t *state = (sign_psbt_state_t *) &G_command_state; +static bool __attribute__((noinline)) +sign_sighash_schnorr_and_yield(dispatcher_context_t *dc, + sign_psbt_state_t *st, + placeholder_info_t *placeholder_info, + input_info_t *input, + unsigned int cur_input_index, + uint8_t sighash[static 32]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); + if (st->wallet_policy_map->type != TOKEN_TR) { + SEND_SW(dc, SW_BAD_STATE); // should never happen + return false; + } + + uint8_t sig[64 + 1]; // extra byte for the appended sighash-type, possibly + size_t sig_len = 0; + + cx_ecfp_public_key_t pubkey_tweaked; // Pubkey corresponding to the key used for signing + + uint8_t *tapleaf_hash = NULL; + bool error = false; cx_ecfp_private_key_t private_key = {0}; - uint8_t *seckey = private_key.d; // convenience alias (entirely within the private_key struct) - uint8_t chain_code[32] = {0}; + // IMPORTANT: Since we do not use any syscall that might throw an exception, it is safe to avoid + // using the TRY/CATCH block to ensure zeroing sensitive data. - uint32_t sign_path[MAX_BIP32_PATH_STEPS]; - for (int i = 0; i < state->our_key_derivation_length; i++) { - sign_path[i] = state->our_key_derivation[i]; - } - sign_path[state->our_key_derivation_length] = state->cur.input.change; - sign_path[state->our_key_derivation_length + 1] = state->cur.input.address_index; + do { // block executed once, only to allow safely breaking out on error - int sign_path_len = state->our_key_derivation_length + 2; + uint8_t *seckey = + private_key.d; // convenience alias (entirely within the private_key struct) - uint8_t sig[64]; - size_t sig_len; + uint32_t sign_path[MAX_BIP32_PATH_STEPS]; + + for (int i = 0; i < placeholder_info->key_derivation_length; i++) { + sign_path[i] = placeholder_info->key_derivation[i]; + } + sign_path[placeholder_info->key_derivation_length] = + input->in_out.is_change ? placeholder_info->placeholder.num_second + : placeholder_info->placeholder.num_first; + sign_path[placeholder_info->key_derivation_length + 1] = input->in_out.address_index; + + int sign_path_len = placeholder_info->key_derivation_length + 2; + + if (bip32_derive_init_privkey_256(CX_CURVE_256K1, + sign_path, + sign_path_len, + &private_key, + NULL) != CX_OK) { + error = true; + break; + } - bool error = 0 != crypto_derive_private_key(&private_key, chain_code, sign_path, sign_path_len); - error = error || 0 > crypto_tr_tweak_seckey(seckey); + policy_node_tr_t *policy = (policy_node_tr_t *) st->wallet_policy_map; + + if (!placeholder_info->is_tapscript) { + if (isnull_policy_node_tree(&policy->tree)) { + // tweak as specified in BIP-86 and BIP-386 + error = error || 0 != crypto_tr_tweak_seckey(seckey, (uint8_t[]){}, 0, seckey); + } else { + // tweak with the taptree hash, per BIP-341 + // The taptree hash is computed in sign_transaction_input in order to + // reduce stack usage. + error = error || 0 != crypto_tr_tweak_seckey(seckey, input->taptree_hash, 32, seckey); + } + if (error) { + break; + } + } else { + // tapscript, we need to yield the tapleaf hash together with the pubkey + tapleaf_hash = placeholder_info->tapleaf_hash; + } + + // generate corresponding public key + unsigned int err = + cx_ecfp_generate_pair_no_throw(CX_CURVE_256K1, &pubkey_tweaked, &private_key, 1); + if (err != CX_OK) { + error = true; + break; + } + + err = cx_ecschnorr_sign_no_throw(&private_key, + CX_ECSCHNORR_BIP0340 | CX_RND_TRNG, + CX_SHA256, + sighash, + 32, + sig, + &sig_len); + if (err != CX_OK) { + error = true; + } + } while (false); - error = error || CX_OK != cx_ecschnorr_sign_no_throw(&private_key, - CX_ECSCHNORR_BIP0340 | CX_RND_TRNG, - CX_SHA256, - state->sighash, - 32, - sig, - &sig_len); explicit_bzero(&private_key, sizeof(private_key)); if (error) { // unexpected error when signing SEND_SW(dc, SW_BAD_STATE); - return; + return false; } if (sig_len != 64) { PRINTF("SIG LEN: %d\n", sig_len); SEND_SW(dc, SW_BAD_STATE); - return; + return false; } - // yield signature - uint8_t cmd = CCMD_YIELD; - dc->add_to_response(&cmd, 1); - - uint8_t buf[9]; - int input_index_varint_len = varint_write(buf, 0, state->cur_input_index); - dc->add_to_response(&buf, input_index_varint_len); - - dc->add_to_response(&sig, sizeof(sig)); - // only append the sighash type byte if it is non-zero - uint8_t sighash_byte = (uint8_t) (state->cur.input.sighash_type & 0xFF); + uint8_t sighash_byte = (uint8_t) (input->sighash_type & 0xFF); if (sighash_byte != 0x00) { // only add the sighash byte if not 0 - dc->add_to_response(&sighash_byte, 1); + sig[sig_len++] = sighash_byte; } - dc->finalize_response(SW_INTERRUPTED_EXECUTION); - if (dc->process_interruption(dc) < 0) { - SEND_SW(dc, SW_BAD_STATE); - return; + if (!yield_signature(dc, + st, + cur_input_index, + pubkey_tweaked.W + 1, // x-only pubkey, hence take only the x-coordinate + 32, + tapleaf_hash, + sig, + sig_len)) + return false; + + return true; +} + +static bool __attribute__((noinline)) +compute_segwit_hashes(dispatcher_context_t *dc, sign_psbt_state_t *st, segwit_hashes_t *hashes) { + { + // compute sha_prevouts and sha_sequences + cx_sha256_t sha_prevouts_context, sha_sequences_context; + + // compute hashPrevouts and hashSequence + cx_sha256_init(&sha_prevouts_context); + cx_sha256_init(&sha_sequences_context); + + for (unsigned int i = 0; i < st->n_inputs; i++) { + // get this input's map + merkleized_map_commitment_t ith_map; + + int res = call_get_merkleized_map(dc, st->inputs_root, st->n_inputs, i, &ith_map); + if (res < 0) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + // get prevout hash and output index for the i-th input + uint8_t ith_prevout_hash[32]; + if (32 != call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_IN_PREVIOUS_TXID}, + 1, + ith_prevout_hash, + 32)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + crypto_hash_update(&sha_prevouts_context.header, ith_prevout_hash, 32); + + uint8_t ith_prevout_n_raw[4]; + if (4 != call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_IN_OUTPUT_INDEX}, + 1, + ith_prevout_n_raw, + 4)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + crypto_hash_update(&sha_prevouts_context.header, ith_prevout_n_raw, 4); + + uint8_t ith_nSequence_raw[4]; + if (4 != call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_IN_SEQUENCE}, + 1, + ith_nSequence_raw, + 4)) { + // if no PSBT_IN_SEQUENCE is present, we must assume nSequence 0xFFFFFFFF + memset(ith_nSequence_raw, 0xFF, 4); + } + + crypto_hash_update(&sha_sequences_context.header, ith_nSequence_raw, 4); + } + + crypto_hash_digest(&sha_prevouts_context.header, hashes->sha_prevouts, 32); + crypto_hash_digest(&sha_sequences_context.header, hashes->sha_sequences, 32); } - ++state->cur_input_index; - dc->next(sign_process_input_map); + { + // compute sha_outputs + cx_sha256_t sha_outputs_context; + cx_sha256_init(&sha_outputs_context); + + if (hash_outputs(dc, st, &sha_outputs_context.header) == -1) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + crypto_hash_digest(&sha_outputs_context.header, hashes->sha_outputs, 32); + } + + { + // compute sha_amounts and sha_scriptpubkeys + // TODO: could be skipped if there are no segwitv1 inputs to sign + + cx_sha256_t sha_amounts_context, sha_scriptpubkeys_context; + + cx_sha256_init(&sha_amounts_context); + cx_sha256_init(&sha_scriptpubkeys_context); + + for (unsigned int i = 0; i < st->n_inputs; i++) { + // get this input's map + merkleized_map_commitment_t ith_map; + + int res = call_get_merkleized_map(dc, st->inputs_root, st->n_inputs, i, &ith_map); + if (res < 0) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + uint64_t in_amount; + uint8_t in_scriptPubKey[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; + size_t in_scriptPubKey_len; + + if (0 > get_amount_scriptpubkey_from_psbt(dc, + &ith_map, + &in_amount, + in_scriptPubKey, + &in_scriptPubKey_len)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + uint8_t in_amount_le[8]; + write_u64_le(in_amount_le, 0, in_amount); + crypto_hash_update(&sha_amounts_context.header, in_amount_le, 8); + + crypto_hash_update_varint(&sha_scriptpubkeys_context.header, in_scriptPubKey_len); + crypto_hash_update(&sha_scriptpubkeys_context.header, + in_scriptPubKey, + in_scriptPubKey_len); + } + + crypto_hash_digest(&sha_amounts_context.header, hashes->sha_amounts, 32); + crypto_hash_digest(&sha_scriptpubkeys_context.header, hashes->sha_scriptpubkeys, 32); + } + + return true; +} + +static bool __attribute__((noinline)) sign_transaction_input(dispatcher_context_t *dc, + sign_psbt_state_t *st, + segwit_hashes_t *hashes, + placeholder_info_t *placeholder_info, + input_info_t *input, + unsigned int cur_input_index) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + + // if the psbt does not specify the sighash flag for this input, the default + // changes depending on the type of spend; therefore, we set it later. + if (input->has_sighash_type) { + // Get sighash type + if (4 != call_get_merkleized_map_value_u32_le(dc, + &input->in_out.map, + (uint8_t[]){PSBT_IN_SIGHASH_TYPE}, + 1, + &input->sighash_type)) { + PRINTF("Malformed PSBT_IN_SIGHASH_TYPE for input %d\n", cur_input_index); + + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + } + + // Sign as segwit input iff it has a witness utxo + if (!input->has_witnessUtxo) { + // sign legacy P2PKH or P2SH + + // sign_non_witness(non_witness_utxo.vout[psbt.tx.input_[i].prevout.n].scriptPubKey, i) + + uint64_t tmp; // unused + if (0 > get_amount_scriptpubkey_from_psbt_nonwitness(dc, + &input->in_out.map, + &tmp, + input->in_out.scriptPubKey, + &input->in_out.scriptPubKey_len, + NULL)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + if (!input->has_sighash_type) { + // legacy input default to SIGHASH_ALL + input->sighash_type = SIGHASH_ALL; + } + + uint8_t sighash[32]; + if (!compute_sighash_legacy(dc, st, input, cur_input_index, sighash)) return false; + + if (!sign_sighash_ecdsa_and_yield(dc, + st, + placeholder_info, + input, + cur_input_index, + sighash)) + return false; + } else { + { + uint64_t amount; + if (0 > get_amount_scriptpubkey_from_psbt_witness(dc, + &input->in_out.map, + &amount, + input->in_out.scriptPubKey, + &input->in_out.scriptPubKey_len)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + if (input->has_redeemScript) { + // Get redeemScript + // The redeemScript cannot be longer than standard scriptPubKeys for + // wrapped segwit transactions that we support + uint8_t redeemScript[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; + + int redeemScript_length = + call_get_merkleized_map_value(dc, + &input->in_out.map, + (uint8_t[]){PSBT_IN_REDEEM_SCRIPT}, + 1, + redeemScript, + sizeof(redeemScript)); + if (redeemScript_length < 0) { + PRINTF("Error fetching redeem script\n"); + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + uint8_t p2sh_redeemscript[2 + 20 + 1]; + p2sh_redeemscript[0] = 0xa9; + p2sh_redeemscript[1] = 0x14; + crypto_hash160(redeemScript, redeemScript_length, p2sh_redeemscript + 2); + p2sh_redeemscript[22] = 0x87; + + 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); + return false; + } + + input->script_len = redeemScript_length; + memcpy(input->script, redeemScript, redeemScript_length); + } else { + input->script_len = input->in_out.scriptPubKey_len; + memcpy(input->script, input->in_out.scriptPubKey, input->in_out.scriptPubKey_len); + } + } + + int segwit_version = get_policy_segwit_version(st->wallet_policy_map); + uint8_t sighash[32]; + if (segwit_version == 0) { + if (!input->has_sighash_type) { + // segwitv0 inputs default to SIGHASH_ALL + input->sighash_type = SIGHASH_ALL; + } + + if (!compute_sighash_segwitv0(dc, st, hashes, input, cur_input_index, sighash)) + return false; + + if (!sign_sighash_ecdsa_and_yield(dc, + st, + placeholder_info, + input, + cur_input_index, + sighash)) + return false; + } else if (segwit_version == 1) { + if (!input->has_sighash_type) { + // segwitv0 inputs default to SIGHASH_DEFAULT + input->sighash_type = SIGHASH_DEFAULT; + } + + if (!compute_sighash_segwitv1(dc, + st, + hashes, + input, + cur_input_index, + placeholder_info, + sighash)) + return false; + + policy_node_tr_t *policy = (policy_node_tr_t *) st->wallet_policy_map; + if (!placeholder_info->is_tapscript && !isnull_policy_node_tree(&policy->tree)) { + // keypath spend, we compute the taptree hash so that we find it ready + // later in sign_sighash_schnorr_and_yield (which has less available stack). + if (0 > compute_taptree_hash( + dc, + &(wallet_derivation_info_t){ + .address_index = input->in_out.address_index, + .change = input->in_out.is_change ? 1 : 0, + .keys_merkle_root = st->wallet_header_keys_info_merkle_root, + .n_keys = st->wallet_header_n_keys, + .wallet_version = st->wallet_header_version}, + r_policy_node_tree(&policy->tree), + input->taptree_hash)) { + PRINTF("Error while computing taptree hash\n"); + SEND_SW(dc, SW_BAD_STATE); + return false; + } + } + + if (!sign_sighash_schnorr_and_yield(dc, + st, + placeholder_info, + input, + cur_input_index, + sighash)) + return false; + + } else { + SEND_SW(dc, SW_BAD_STATE); // can't happen + return false; + } + } + return true; +} + +static bool __attribute__((noinline)) +fill_taproot_placeholder_info(dispatcher_context_t *dc, + sign_psbt_state_t *st, + const input_info_t *input, + const policy_node_t *tapleaf_ptr, + placeholder_info_t *placeholder_info) { + cx_sha256_t hash_context; + crypto_tr_tapleaf_hash_init(&hash_context); + + // we compute the tapscript once just to compute its length + // this avoids having to store it + int tapscript_len = get_wallet_internal_script_hash( + dc, + tapleaf_ptr, + &(wallet_derivation_info_t){.wallet_version = st->wallet_header_version, + .keys_merkle_root = st->wallet_header_keys_info_merkle_root, + .n_keys = st->wallet_header_n_keys, + .change = input->in_out.is_change, + .address_index = input->in_out.address_index}, + WRAPPED_SCRIPT_TYPE_TAPSCRIPT, + NULL); + if (tapscript_len < 0) { + PRINTF("Failed to compute tapleaf script\n"); + return false; + } + + crypto_hash_update_u8(&hash_context.header, 0xC0); + crypto_hash_update_varint(&hash_context.header, tapscript_len); + + // we compute it again to get add the actual script code to the hash computation + if (0 > + get_wallet_internal_script_hash( + dc, + tapleaf_ptr, + &(wallet_derivation_info_t){.wallet_version = st->wallet_header_version, + .keys_merkle_root = st->wallet_header_keys_info_merkle_root, + .n_keys = st->wallet_header_n_keys, + .change = input->in_out.is_change, + .address_index = input->in_out.address_index}, + WRAPPED_SCRIPT_TYPE_TAPSCRIPT, + &hash_context.header)) { + return false; // should never happen! + } + crypto_hash_digest(&hash_context.header, placeholder_info->tapleaf_hash, 32); + + return true; +} + +static bool __attribute__((noinline)) +sign_transaction(dispatcher_context_t *dc, + sign_psbt_state_t *st, + const uint8_t internal_inputs[static BITVECTOR_REAL_SIZE(MAX_N_INPUTS_CAN_SIGN)]) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + + int placeholder_index = 0; + + segwit_hashes_t hashes; + + // compute all the tx-wide hashes + // while this is redundant for legacy transactions, we do it here in order to + // avoid doing it in places that have more stack limitations + if (!compute_segwit_hashes(dc, st, &hashes)) return false; + + // Iterate over all the placeholders that correspond to keys owned by us + while (true) { + placeholder_info_t placeholder_info; + memset(&placeholder_info, 0, sizeof(placeholder_info)); + + const policy_node_t *tapleaf_ptr = NULL; + int n_key_placeholders = get_key_placeholder_by_index(st->wallet_policy_map, + placeholder_index, + &tapleaf_ptr, + &placeholder_info.placeholder); + + if (n_key_placeholders < 0) { + SEND_SW(dc, SW_BAD_STATE); // should never happen + if (!G_swap_state.called_from_swap) { + ui_post_processing_confirm_transaction(dc, false); + } + return false; + } + + if (placeholder_index >= n_key_placeholders) { + // all placeholders were processed + break; + } + + if (tapleaf_ptr != NULL) { + // get_key_placeholder_by_index returns the pointer to the tapleaf only if the key being + // spent is indeed in a tapleaf + placeholder_info.is_tapscript = true; + } + + if (fill_placeholder_info_if_internal(dc, st, &placeholder_info) == true) { + for (unsigned int i = 0; i < st->n_inputs; i++) + if (bitvector_get(internal_inputs, i)) { + input_info_t input; + memset(&input, 0, sizeof(input)); + + input_keys_callback_data_t callback_data = { + .input = &input, + .placeholder_info = &placeholder_info}; + int res = call_get_merkleized_map_with_callback( + dc, + (void *) &callback_data, + st->inputs_root, + st->n_inputs, + i, + (merkle_tree_elements_callback_t) input_keys_callback, + &input.in_out.map); + if (res < 0) { + SEND_SW(dc, SW_INCORRECT_DATA); + if (!G_swap_state.called_from_swap) { + ui_post_processing_confirm_transaction(dc, false); + } + return false; + } + + if (tapleaf_ptr != NULL && !fill_taproot_placeholder_info(dc, + st, + &input, + tapleaf_ptr, + &placeholder_info)) + return false; + + if (!sign_transaction_input(dc, st, &hashes, &placeholder_info, &input, i)) { + if (!G_swap_state.called_from_swap) { + ui_post_processing_confirm_transaction(dc, false); + } + + // we do not send a status word, since sign_transaction_input + // already does it on failure + return false; + } + } + } + + ++placeholder_index; + } + + if (!G_swap_state.called_from_swap) { + ui_post_processing_confirm_transaction(dc, true); + } + return true; } -static void finalize(dispatcher_context_t *dc) { - LOG_PROCESSOR(dc, __FILE__, __LINE__, __func__); +void handler_sign_psbt(dispatcher_context_t *dc, uint8_t protocol_version) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + + sign_psbt_state_t st; + memset(&st, 0, sizeof(st)); + + st.protocol_version = protocol_version; + + // read APDU inputs, intialize global state and read global PSBT map + if (!init_global_state(dc, &st)) return; + + // bitmap to keep track of which inputs are internal + uint8_t internal_inputs[BITVECTOR_REAL_SIZE(MAX_N_INPUTS_CAN_SIGN)]; + memset(internal_inputs, 0, sizeof(internal_inputs)); + + /** Inputs verification flow + * + * Go though all the inputs: + * - verify the non_witness_utxo + * - compute value spent + * - detect internal inputs that should be signed, and if there are external inputs or unusual + * sighashes + */ + if (!preprocess_inputs(dc, &st, internal_inputs)) return; + + /** INPUT VERIFICATION ALERTS + * + * Show warnings and allow users to abort in any of the following conditions: + * - pre-taproot transaction with unverified inputs (missing non-witness-utxo) + * - external inputs + * - non-default sighash types + */ + if (!show_alerts(dc, &st, internal_inputs)) return; + + /** OUTPUTS VERIFICATION FLOW + * + * For each output, check if it's a change address. + * Show each output that is not a change address to the user for verification. + */ + if (!process_outputs(dc, &st)) return; + + /** TRANSACTION CONFIRMATION + * + * Show summary info to the user (transaction fees), ask for final confirmation + */ + if (!confirm_transaction(dc, &st)) return; + + /** SIGNING FLOW + * + * For each internal placeholder, and for each internal input, sign using the + * appropriate algorithm. + */ + if (!sign_transaction(dc, &st, internal_inputs)) return; // Only if called from swap, the app should terminate after sending the response if (G_swap_state.called_from_swap) { @@ -1975,4 +2561,4 @@ static void finalize(dispatcher_context_t *dc) { SEND_SW(dc, SW_OK); } -#endif // !defined(HAVE_LIQUID) +#endif // !defined(HAVE_LIQUID) \ No newline at end of file diff --git a/src/handler/sign_psbt.h b/src/handler/sign_psbt.h deleted file mode 100644 index e6374c2c2..000000000 --- a/src/handler/sign_psbt.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#if !defined(HAVE_LIQUID) - -#include "../boilerplate/dispatcher.h" -#include "../constants.h" -#include "../common/bitvector.h" -#include "../common/merkle.h" -#include "../common/wallet.h" - -#define MAX_N_INPUTS_CAN_SIGN 512 - -// common info that applies to either the current input or the current output -typedef struct { - merkleized_map_commitment_t map; - - bool unexpected_pubkey_error; // Set to true if the pubkey in the keydata of - // PSBT_{IN,OUT}_BIP32_DERIVATION or - // PSBT_{IN,OUT}_TAP_BIP32_DERIVATION is not the correct length. - - bool has_bip32_derivation; - uint8_t - bip32_derivation_pubkey[33]; // the pubkey of the first PSBT_{IN,OUT}_BIP32_DERIVATION or - // PSBT_{IN,OUT}_TAP_BIP32_DERIVATION key seen. - // Could be 33 (legacy or segwitv0) or 32 bytes long - // (taproot), based on the script type. - - // For an output, its scriptPubKey - // for an input, the prevout's scriptPubKey (either from the non-witness-utxo, or from the - // witness-utxo) - - uint8_t scriptPubKey[MAX_OUTPUT_SCRIPTPUBKEY_LEN]; - size_t scriptPubKey_len; -} in_out_info_t; - -typedef struct { - bool has_witnessUtxo; - bool has_nonWitnessUtxo; - bool has_redeemScript; - bool has_sighash_type; - - uint64_t prevout_amount; // the value of the prevout of the current input - - uint8_t prevout_scriptpubkey[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; - size_t prevout_scriptpubkey_len; - - // the script used when signing, either from the witness utxo or the redeem script - uint8_t script[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; - size_t script_len; - - uint32_t sighash_type; - - int change; - int address_index; -} input_info_t; - -typedef struct { - uint64_t value; -} output_info_t; - -typedef struct { - machine_context_t ctx; - - uint32_t tx_version; - uint32_t locktime; - - unsigned int n_inputs; - uint8_t inputs_root[32]; // merkle root of the vector of input maps commitments - unsigned int n_outputs; - uint8_t outputs_root[32]; // merkle root of the vector of output maps commitments - - bool is_wallet_canonical; - int address_type; // only relevant for canonical wallets - int bip44_purpose; // only relevant for canonical wallets - - uint8_t wallet_header_keys_info_merkle_root[32]; - size_t wallet_header_n_keys; - union { - uint8_t wallet_policy_map_bytes[MAX_POLICY_MAP_BYTES]; - policy_node_t wallet_policy_map; - }; - - uint32_t master_key_fingerprint; - - // bitmap to track of which inputs are internal - uint8_t internal_inputs[BITVECTOR_REAL_SIZE(MAX_N_INPUTS_CAN_SIGN)]; - - union { - unsigned int cur_input_index; - unsigned int cur_output_index; - }; - - struct { - in_out_info_t in_out; - union { - input_info_t input; - output_info_t output; - }; - } cur; - - uint8_t sighash[32]; - - struct { - uint8_t sha_prevouts[32]; - uint8_t sha_amounts[32]; - uint8_t sha_scriptpubkeys[32]; - uint8_t sha_sequences[32]; - uint8_t sha_outputs[32]; - } hashes; - bool segwit_hashes_computed; - - uint64_t inputs_total_value; - uint64_t outputs_total_value; - - uint64_t internal_inputs_total_value; - - uint64_t change_outputs_total_value; - - int external_outputs_count; // count of external outputs that are shown to the user - int change_count; // count of outputs compatible with change outputs - - int our_key_derivation_length; - uint32_t our_key_derivation[MAX_BIP32_PATH_STEPS]; -} sign_psbt_state_t; - -void handler_sign_psbt(dispatcher_context_t *dispatcher_context); - -#endif // !defined(HAVE_LIQUID) diff --git a/src/handler/sign_psbt/compare_wallet_script_at_path.c b/src/handler/sign_psbt/compare_wallet_script_at_path.c index a50ee1e25..08f2c2f8d 100644 --- a/src/handler/sign_psbt/compare_wallet_script_at_path.c +++ b/src/handler/sign_psbt/compare_wallet_script_at_path.c @@ -12,24 +12,24 @@ int compare_wallet_script_at_path(dispatcher_context_t *dispatcher_context, uint32_t change, uint32_t address_index, const policy_node_t *policy, + int wallet_version, const uint8_t keys_merkle_root[static 32], uint32_t n_keys, const uint8_t expected_script[], size_t expected_script_len) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); // derive wallet's scriptPubKey, check if it matches the expected one uint8_t wallet_script[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; - buffer_t wallet_script_buf = buffer_create(wallet_script, sizeof(wallet_script)); - - int wallet_script_len = call_get_wallet_script(dispatcher_context, - policy, - keys_merkle_root, - n_keys, - change, - address_index, - &wallet_script_buf, - NULL); + int wallet_script_len = + get_wallet_script(dispatcher_context, + policy, + &(wallet_derivation_info_t){.wallet_version = wallet_version, + .keys_merkle_root = keys_merkle_root, + .n_keys = n_keys, + .change = change, + .address_index = address_index}, + wallet_script); if (wallet_script_len < 0) { PRINTF("Failed to get wallet script\n"); return -1; // shouldn't happen diff --git a/src/handler/sign_psbt/compare_wallet_script_at_path.h b/src/handler/sign_psbt/compare_wallet_script_at_path.h index 028c1b86c..36f50665d 100644 --- a/src/handler/sign_psbt/compare_wallet_script_at_path.h +++ b/src/handler/sign_psbt/compare_wallet_script_at_path.h @@ -11,6 +11,7 @@ int compare_wallet_script_at_path(dispatcher_context_t *dispatcher_context, uint32_t change, uint32_t address_index, const policy_node_t *policy, + int wallet_version, const uint8_t keys_merkle_root[static 32], uint32_t n_keys, const uint8_t expected_script[], diff --git a/src/handler/sign_psbt/extract_bip32_derivation.c b/src/handler/sign_psbt/extract_bip32_derivation.c new file mode 100644 index 000000000..c03487650 --- /dev/null +++ b/src/handler/sign_psbt/extract_bip32_derivation.c @@ -0,0 +1,120 @@ +#include +#include + +#include "./extract_bip32_derivation.h" + +#include "../lib/stream_merkle_leaf_element.h" + +#include "../../common/psbt.h" +#include "../../common/read.h" +#include "../../common/varint.h" + +typedef struct { + int psbt_key_type; + uint8_t *out; + int total_data_length; + int out_data_length; // set to -1 before it's computed on the first call of + // fpt_der_data_callback + int result; +} fpt_der_callback_data_t; + +static void fpt_der_data_len_callback(size_t data_length, void *callback_state) { + ((fpt_der_callback_data_t *) callback_state)->total_data_length = data_length; +} + +static void fpt_der_data_callback(buffer_t *data, void *callback_state) { + fpt_der_callback_data_t *cs = (fpt_der_callback_data_t *) callback_state; + + if (cs->result < 0) return; // an error already happened, ignore the rest + + // on the first call, compute the length the fingerprint + derivation part of the message. + // - if non-taproot, then it's the entire message; + // - if taproot, it's the message after the hashes are removed. + if (cs->out_data_length == -1) { + bool is_tap = cs->psbt_key_type == PSBT_IN_TAP_BIP32_DERIVATION || + cs->psbt_key_type == PSBT_OUT_TAP_BIP32_DERIVATION; + + if (!is_tap) { + cs->out_data_length = cs->total_data_length; + } else { + uint64_t n_hashes; + if ((!buffer_read_varint(data, &n_hashes)) || + (cs->total_data_length < varint_size(n_hashes) + 32 * (int) n_hashes)) { + PRINTF("Unexpected: initial callback message too short\n"); + cs->result = -1; + return; + } + + int out_data_length = + cs->total_data_length - varint_size(n_hashes) - 32 * (int) n_hashes; + + if (out_data_length > 4 * (1 + MAX_BIP32_PATH_STEPS)) { + PRINTF("BIP32 derivation longer than supported in psbt derivation\n"); + cs->result = -1; + return; + } + cs->out_data_length = out_data_length; + } + } + + // then, keep exactly the last cs->out_data_length streamed bytes; as they might be streamed + // across multiple messages, we need to handle it appropriately + + if (data->size >= (size_t) cs->out_data_length) { + // only keep the last suffix of length cs->out_data_length + // discard any pre-existing data from previous calls + buffer_seek_end(data, cs->out_data_length); + buffer_read_bytes(data, cs->out, cs->out_data_length); + } else { + buffer_seek_set(data, 0); + // We need to concatenate the new data we are reading with any previously read data. + // Since we can only read data->size bytes, only the last d = out_data_length - data->size + // previous bytes are kept; they move from position out_data_length - d + 1 to position 0. + int d = cs->out_data_length - data->size; + memmove(cs->out, &cs->out[cs->out_data_length - d + 1], d); + // starting at position d, we read the entire data + buffer_read_bytes(data, &cs->out[d], data->size); + } +} + +int extract_bip32_derivation(dispatcher_context_t *dc, + int psbt_key_type, + const uint8_t values_root[static 32], + uint32_t merkle_tree_size, + int index, + uint32_t out[static 1 + MAX_BIP32_PATH_STEPS]) { + fpt_der_callback_data_t callback_state; + + // we could recycle out instead of creating a new array, but we rather keep the code + // clean, as this is not used in memory-critical parts. + uint8_t out_bytes[4 * (1 + MAX_BIP32_PATH_STEPS)]; + + callback_state.psbt_key_type = psbt_key_type; + callback_state.out = out_bytes; + callback_state.out_data_length = -1; + callback_state.result = 0; + + int len = call_stream_merkle_leaf_element(dc, + values_root, + merkle_tree_size, + index, + fpt_der_data_len_callback, + fpt_der_data_callback, + &callback_state); + + if (len < 0 || callback_state.result < 0 || callback_state.out_data_length < 4 || + callback_state.out_data_length % 4 != 0) { + PRINTF("Unexpected error while reading a BIP32 derivation\n"); + return -1; + } + + for (int i = 0; i < callback_state.out_data_length / 4; i++) { + if (i == 0) { + out[i] = read_u32_be(out_bytes, 4 * i); + } else { + out[i] = read_u32_le(out_bytes, 4 * i); + } + } + + return (callback_state.out_data_length / 4) - 1; +} \ No newline at end of file diff --git a/src/handler/sign_psbt/extract_bip32_derivation.h b/src/handler/sign_psbt/extract_bip32_derivation.h new file mode 100644 index 000000000..2fbda4210 --- /dev/null +++ b/src/handler/sign_psbt/extract_bip32_derivation.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../../boilerplate/dispatcher.h" +#include "../../common/bip32.h" + +/** + * Convenience function to extract the BIP32 derivation part from a PSBT field key type + * PSBT_{IN,OUT}_BIP32_DERIVATION or PSBT_{IN,OUT}_TAP_BIP32_DERIVATION. + * This is needed because the tapscript versions can be very large, so it needs to + * be parsed while streaming it. + */ +int extract_bip32_derivation(dispatcher_context_t *dc, + int psbt_key_type, + const uint8_t values_root[static 32], + uint32_t merkle_tree_size, + int index, + uint32_t out[static 1 + MAX_BIP32_PATH_STEPS]); \ No newline at end of file diff --git a/src/handler/sign_psbt/get_fingerprint_and_path.c b/src/handler/sign_psbt/get_fingerprint_and_path.c deleted file mode 100644 index fcb287680..000000000 --- a/src/handler/sign_psbt/get_fingerprint_and_path.c +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include - -#include "get_fingerprint_and_path.h" - -#include "../lib/get_merkleized_map_value.h" - -#include "../../common/read.h" - -int get_fingerprint_and_path(dispatcher_context_t *dispatcher_context, - const merkleized_map_commitment_t *map, - const uint8_t *key, - int key_len, - uint32_t *out_fingerprint, - uint32_t out_bip32_path[static MAX_BIP32_PATH_STEPS]) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); - - uint8_t fpt_der[4 + 4 * MAX_BIP32_PATH_STEPS]; - - int len = call_get_merkleized_map_value(dispatcher_context, - map, - key, - key_len, - fpt_der, - sizeof(fpt_der)); - - if (len < 4 || len % 4 != 0) { - return -1; - } - - int bip32_path_len = (len - 4) / 4; - - if (bip32_path_len > MAX_BIP32_PATH_STEPS) { - return -1; - } - - *out_fingerprint = read_u32_le(fpt_der, 0); - - uint8_t *derivation_path = fpt_der + 4; - for (int i = 0; i < bip32_path_len; i++) { - out_bip32_path[i] = read_u32_le(derivation_path, 4 * i); - } - - return bip32_path_len; -} - -int get_emptyhashes_fingerprint_and_path(dispatcher_context_t *dispatcher_context, - const merkleized_map_commitment_t *map, - const uint8_t *key, - int key_len, - uint32_t *out_fingerprint, - uint32_t out_bip32_path[static MAX_BIP32_PATH_STEPS]) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); - - uint8_t hasheslen_fpt_der[1 + 4 + 4 * MAX_BIP32_PATH_STEPS]; - - int len = call_get_merkleized_map_value(dispatcher_context, - map, - key, - key_len, - hasheslen_fpt_der, - sizeof(hasheslen_fpt_der)); - - if (len < 1 + 4 || (len - 1) % 4 != 0) { - return -1; - } - - if (hasheslen_fpt_der[0] != 0) { - // this function only handles the case when hashes_len == 0 - // (always the case for key path spending) - PRINTF("Unexpected hashes_len != 0"); - - return -1; - } - - int bip32_path_len = (len - 1 - 4) / 4; - - if (bip32_path_len > MAX_BIP32_PATH_STEPS) { - return -1; - } - - *out_fingerprint = read_u32_le(hasheslen_fpt_der, 1); - - uint8_t *derivation_path = hasheslen_fpt_der + 1 + 4; - for (int i = 0; i < bip32_path_len; i++) { - out_bip32_path[i] = read_u32_le(derivation_path, 4 * i); - } - - return bip32_path_len; -} \ No newline at end of file diff --git a/src/handler/sign_psbt/get_fingerprint_and_path.h b/src/handler/sign_psbt/get_fingerprint_and_path.h deleted file mode 100644 index 7426f292f..000000000 --- a/src/handler/sign_psbt/get_fingerprint_and_path.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "../../boilerplate/dispatcher.h" -#include "../../common/merkle.h" -#include "../../common/wallet.h" - -/** - * Used to read PSBT_IN_BIP32_DERIVATION or PSBT_OUT_BIP32_DERIVATION entries from a PSBT map. - * Returns the length of the BIP32 path on success, a negative number on failure. - * - * TODO: more precise docs - */ -int get_fingerprint_and_path(dispatcher_context_t *dispatcher_context, - const merkleized_map_commitment_t *map, - const uint8_t *key, - int key_len, - uint32_t *out_fingerprint, - uint32_t out_bip32_path[static MAX_BIP32_PATH_STEPS]); - -/** - * Used to read PSBT_IN_TAP_BIP32_DERIVATION or PSBT_OUT_TAP_BIP32_DERIVATION entries from a PSBT - * map; fails if the hashes_len is not 0 (only useful for keypath spending). - * Returns the length of the BIP32 path on success, a negative number on failure. - * - * TODO: more precise docs - */ -int get_emptyhashes_fingerprint_and_path(dispatcher_context_t *dispatcher_context, - const merkleized_map_commitment_t *map, - const uint8_t *key, - int key_len, - uint32_t *out_fingerprint, - uint32_t out_bip32_path[static MAX_BIP32_PATH_STEPS]); diff --git a/src/handler/sign_psbt/is_in_out_internal.c b/src/handler/sign_psbt/is_in_out_internal.c deleted file mode 100644 index 6baca3d94..000000000 --- a/src/handler/sign_psbt/is_in_out_internal.c +++ /dev/null @@ -1,112 +0,0 @@ -#include - -#include "is_in_out_internal.h" -#include "compare_wallet_script_at_path.h" -#include "get_fingerprint_and_path.h" - -#include "../../common/bip32.h" -#include "../../common/psbt.h" -#include "../../common/script.h" -#include "../../constants.h" - -int is_in_out_internal(dispatcher_context_t *dispatcher_context, - const transaction_signer_state_t *state, - const in_out_info_t *in_out_info, - bool is_input, - bool has_bip32_derivation) { - if (!has_bip32_derivation) { - PRINTF("No BIP32 derivation\n"); - return 0; - } -#ifdef HAVE_LIQUID - if (!state->wallet_policy_map_unwrapped) { - PRINTF("Wallet policy doesn't exist\n"); - return -1; - } -#endif - - // get path, obtain change and address_index, - int bip32_path_len; - uint32_t bip32_path[MAX_BIP32_PATH_STEPS]; - uint32_t fingerprint; - - int script_type = get_script_type(in_out_info->scriptPubKey, in_out_info->scriptPubKey_len); - if (script_type == -1) { - // OP_RETURN outputs would return -1 despite being valid; but for those, there shouldn't be - // any BIP32 derivation in the PSBT, so no special case is needed here. - - PRINTF("Invalid script type\n"); - return -1; - } else if (script_type == SCRIPT_TYPE_UNKNOWN_SEGWIT) { - // An unknown but valid segwit script type, definitely external. - return 0; - } else if (script_type == SCRIPT_TYPE_P2TR) { - // taproot output, use PSBT_{IN,OUT}_TAP_BIP32_DERIVATION - uint8_t key[1 + 32]; - key[0] = is_input ? PSBT_IN_TAP_BIP32_DERIVATION : PSBT_OUT_TAP_BIP32_DERIVATION; - memcpy(key + 1, in_out_info->bip32_derivation_pubkey, sizeof(key) - 1); - - bip32_path_len = get_emptyhashes_fingerprint_and_path(dispatcher_context, - &in_out_info->map, - key, - sizeof(key), - &fingerprint, - bip32_path); - } else { - // legacy or segwitv0 output, use PSBT_OUT_BIP32_DERIVATION - uint8_t key[1 + 33]; - key[0] = is_input ? PSBT_IN_BIP32_DERIVATION : PSBT_OUT_BIP32_DERIVATION; - memcpy(key + 1, in_out_info->bip32_derivation_pubkey, sizeof(key) - 1); - - bip32_path_len = get_fingerprint_and_path(dispatcher_context, - &in_out_info->map, - key, - sizeof(key), - &fingerprint, - bip32_path); - } - - if (bip32_path_len < 0) { - PRINTF("Could not get BIP32 path\n"); - return -1; - } - - // As per wallet policy assumptions, the path must have change and address index - if (bip32_path_len < 2) { - PRINTF("BIP32 path too short\n"); - return 0; - } - uint32_t change = bip32_path[bip32_path_len - 2]; - uint32_t address_index = bip32_path[bip32_path_len - 1]; - - if (!is_input && change != 1) { - // unlike for inputs, change must be 1 for this output to be considered internal - return 0; - } - - if (state->is_wallet_canonical) { - // for canonical wallets, the path must be exactly as expected for a change output - uint32_t coin_types[2] = {BIP44_COIN_TYPE, BIP44_COIN_TYPE_2}; - if (!is_address_path_standard(bip32_path, - bip32_path_len, - state->bip44_purpose, - coin_types, - 2, - is_input ? -1 : 1)) { - return 0; - } - } - - return compare_wallet_script_at_path(dispatcher_context, - change, - address_index, -#ifdef HAVE_LIQUID - state->wallet_policy_map_unwrapped, -#else - &state->wallet_policy_map, -#endif - state->wallet_header_keys_info_merkle_root, - state->wallet_header_n_keys, - in_out_info->scriptPubKey, - in_out_info->scriptPubKey_len); -} \ No newline at end of file diff --git a/src/handler/sign_psbt/is_in_out_internal.h b/src/handler/sign_psbt/is_in_out_internal.h deleted file mode 100644 index 965c39b42..000000000 --- a/src/handler/sign_psbt/is_in_out_internal.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "../sign_psbt.h" -#include "../liquid_sign_pset.h" -#include "../../common/wallet.h" - -#ifdef HAVE_LIQUID -typedef sign_pset_state_t transaction_signer_state_t; -#else -typedef sign_psbt_state_t transaction_signer_state_t; -#endif - -/** - * Verifies if a certain input/output is internal (that is, controlled by the wallet being used for - * signing). This uses the state of sign_psbt and is not meant as a general-purpose function; - * rather, it avoids some substantial code duplication and removes complexity from sign_psbt. - * - * @return 1 if the given input/output is internal; 0 if external; -1 on error. - */ -int is_in_out_internal(dispatcher_context_t *dispatcher_context, - const transaction_signer_state_t *state, - const in_out_info_t *in_out_info, - bool is_input, - bool has_bip32_derivation); diff --git a/src/handler/sign_psbt/update_hashes_with_map_value.c b/src/handler/sign_psbt/update_hashes_with_map_value.c index 6cc10b79f..6f473ad1a 100644 --- a/src/handler/sign_psbt/update_hashes_with_map_value.c +++ b/src/handler/sign_psbt/update_hashes_with_map_value.c @@ -37,7 +37,7 @@ int update_hashes_with_map_value(dispatcher_context_t *dispatcher_context, int key_len, cx_hash_t *hash_unprefixed, cx_hash_t *hash_prefixed) { - LOG_PROCESSOR(dispatcher_context, __FILE__, __LINE__, __func__); + LOG_PROCESSOR(__FILE__, __LINE__, __func__); callback_state_t cb_state = {.hash_unprefixed = hash_unprefixed, .hash_prefixed = hash_prefixed}; diff --git a/src/handler/sign_psbt/update_hashes_with_map_value.h b/src/handler/sign_psbt/update_hashes_with_map_value.h index bc81d298a..e40b15854 100644 --- a/src/handler/sign_psbt/update_hashes_with_map_value.h +++ b/src/handler/sign_psbt/update_hashes_with_map_value.h @@ -4,7 +4,7 @@ #include "../../common/merkle.h" /** - * Streams the requested preimage from a merkleized map, updating the given has contexts + * Streams the requested preimage from a merkleized map, updating the given hash contexts * appropriately. Both hash_unprefixed and hash_prefixed are optional, but if not NULL, it is * responsibility of the caller to ensure that they are initialized. * diff --git a/src/liquid/blech32.h b/src/liquid/blech32.h index 299b95a48..5776d633d 100644 --- a/src/liquid/blech32.h +++ b/src/liquid/blech32.h @@ -1,4 +1,5 @@ #pragma once +#ifdef HAVE_LIQUID #include // size_t #include // uint*_t @@ -54,3 +55,5 @@ WARN_UNUSED_RESULT int blech32_addr_decode(uint8_t *witver, size_t *witdata_len, const char *hrp, const char *addr); + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/liquid/liquid.c b/src/liquid/liquid.c index 5351e8a79..08c4c98c0 100644 --- a/src/liquid/liquid.c +++ b/src/liquid/liquid.c @@ -1,3 +1,5 @@ +#ifdef HAVE_LIQUID + #include #include #include "crypto.h" @@ -7,8 +9,6 @@ #include "../common/script.h" #include "tests.h" -#ifdef HAVE_LIQUID - #ifdef SKIP_FOR_CMOCKA // disable problematic macros when compiling unit tests with CMOCKA #define PRINTF(...) diff --git a/src/liquid/liquid.h b/src/liquid/liquid.h index 65bb75f99..9b857e6db 100644 --- a/src/liquid/liquid.h +++ b/src/liquid/liquid.h @@ -1,4 +1,5 @@ #pragma once +#ifdef HAVE_LIQUID #include // size_t #include // uint*_t @@ -252,3 +253,5 @@ bool liquid_is_master_blinding_key_ours(const uint8_t mbk[static 32]); static inline bool liquid_policy_is_blinded(const policy_node_t *policy) { return policy && (TOKEN_CT == policy->type); } + +#endif // HAVE_LIQUID diff --git a/src/liquid/liquid_asset_metadata.c b/src/liquid/liquid_asset_metadata.c index 9e87274ca..c55ca0fb0 100644 --- a/src/liquid/liquid_asset_metadata.c +++ b/src/liquid/liquid_asset_metadata.c @@ -1,3 +1,5 @@ +#ifdef HAVE_LIQUID + #include #include @@ -416,3 +418,4 @@ asset_metadata_status_t liquid_get_asset_metadata_by_leaf_index( } #endif // SKIP_FOR_CMOCKA +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/liquid/liquid_asset_metadata.h b/src/liquid/liquid_asset_metadata.h index 8cc3e7600..be393279f 100644 --- a/src/liquid/liquid_asset_metadata.h +++ b/src/liquid/liquid_asset_metadata.h @@ -1,4 +1,5 @@ #pragma once +#ifdef HAVE_LIQUID #include #include @@ -115,3 +116,4 @@ WARN_UNUSED_RESULT asset_metadata_status_t liquid_get_asset_metadata_by_leaf_ind asset_info_ext_t *ext_asset_info); #endif // SKIP_FOR_CMOCKA +#endif // HAVE_LIQUID diff --git a/src/liquid/liquid_assets.h b/src/liquid/liquid_assets.h index 4941e96a3..d7a462345 100644 --- a/src/liquid/liquid_assets.h +++ b/src/liquid/liquid_assets.h @@ -1,7 +1,5 @@ #pragma once - -#if defined HAVE_LIQUID && !defined(_LIQUID_ASSETS_H__) -#define _LIQUID_ASSETS_H__ +#ifdef HAVE_LIQUID #include #include @@ -121,5 +119,4 @@ WARN_UNUSED_RESULT bool liquid_compute_asset_tag(const uint8_t contract_hash[sta void liquid_format_asset_tag(const uint8_t asset_tag[static LIQUID_ASSET_TAG_LEN], char out[static LIQUID_ASSET_TAG_HEX_LEN + 1]); -#endif - +#endif // HAVE_LIQUID diff --git a/src/liquid/liquid_hash_wrappers.h b/src/liquid/liquid_hash_wrappers.h index fad9b5506..53de8651c 100644 --- a/src/liquid/liquid_hash_wrappers.h +++ b/src/liquid/liquid_hash_wrappers.h @@ -1,4 +1,5 @@ #pragma once +#ifdef HAVE_LIQUID #include // size_t #include // uint*_t @@ -120,3 +121,5 @@ WARN_UNUSED_RESULT static inline bool hash_update_u32_le(cx_hash_t *hash_context WARN_UNUSED_RESULT static inline bool hash_digest(cx_hash_t *hash_context, uint8_t *out, size_t out_len) { return CX_OK == cx_hash_no_throw(hash_context, CX_LAST, NULL, 0, out, out_len); } + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/liquid/liquid_proofs.h b/src/liquid/liquid_proofs.h index 6ab86059c..aad21190c 100644 --- a/src/liquid/liquid_proofs.h +++ b/src/liquid/liquid_proofs.h @@ -3,6 +3,7 @@ */ #pragma once +#ifdef HAVE_LIQUID #include // size_t #include // uint*_t @@ -90,3 +91,5 @@ WARN_UNUSED_RESULT bool liquid_surjectionproof_verify_single(const uint8_t *proo */ WARN_UNUSED_RESULT bool liquid_generator_generate(uint8_t gen[static LIQUID_GENERATOR_LEN], const uint8_t seed32_reversed[static 32]); + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/liquid/liquid_wallet.c b/src/liquid/liquid_wallet.c new file mode 100644 index 000000000..8d92e7699 --- /dev/null +++ b/src/liquid/liquid_wallet.c @@ -0,0 +1,630 @@ +#ifdef HAVE_LIQUID + +#include +#include +#include + +#include "../common/base58.h" +#include "../common/bip32.h" +#include "../common/buffer.h" +#include "../common/script.h" +#include "../common/segwit_addr.h" +#include "../common/wallet.h" + +#include "../cxram_stash.h" + +#include "../boilerplate/sw.h" + +#include "../debug-helpers/debug.h" + +#ifndef SKIP_FOR_CMOCKA +#include "../crypto.h" +#else +// disable problematic macros when compiling unit tests with CMOCKA +#define PRINTF(...) +#define PIC(x) (x) +#endif + + +/// Maximum length of blinding key returned token prefix in characters +#define TOKEN_PREFIX_LEN 7 + +/// Bits specifying used characters +typedef enum { + // BASIC CATEGORIES ///////////////////////////////////////////// + /// Numbers 0...9 + CHARSET_NUM = 1 << 0, + /// Lowercase latin letters a...f + CHARSET_ALPHA_AF_LOW = 1 << 1, + /// Lowercase latin letters g...z + CHARSET_ALPHA_GZ_LOW = 1 << 2, + /// Uppercase latin letters a...f + CHARSET_ALPHA_AF_UP = 1 << 3, + /// Uppercase latin letters g...z + CHARSET_ALPHA_GZ_UP = 1 << 4, + /// Brackets () + CHARSET_BRACKETS = 1 << 5, + /// Other characters + CHARSET_OTHER = 1 << 6, + + // COMBINATIONS OT TRAITS /////////////////////////////////////// + /// Lowercase hexadecimal numbers + CHARSET_HEX_LOW = (CHARSET_NUM|CHARSET_ALPHA_AF_LOW), + /// Lowercase latin letters + CHARSET_ALPHA_LOW = (CHARSET_ALPHA_AF_LOW|CHARSET_ALPHA_GZ_LOW), + /// Uppercase latin letters + CHARSET_ALPHA_UP = (CHARSET_ALPHA_AF_UP|CHARSET_ALPHA_GZ_UP), + /// Latin letters of any case + CHARSET_ALPHA = (CHARSET_ALPHA_LOW|CHARSET_ALPHA_UP), + /// Alphanumeric: numbers and latin letters of any case + CHARSET_ALPHANUM = (CHARSET_NUM|CHARSET_ALPHA), + /// Alphanumeric: numbers and lowercase latin letters + CHARSET_ALPHANUM_LOW = (CHARSET_NUM|CHARSET_ALPHA_LOW), + /// Alphanumeric: numbers and uppercase latin letters + CHARSET_ALPHANUM_UP = (CHARSET_NUM|CHARSET_ALPHA_UP), +} charset_t; + +/// Token scan result +typedef struct { + /// Detected token length + size_t token_len; + /// Charset detected, a combination of `charset_t` flags + uint32_t charset; + /// Token prefix string, null terminated. Containins up to TOKEN_PREFIX_LEN first + /// characters of the token. + char prefix[TOKEN_PREFIX_LEN + 1]; +} token_scan_result_t; + +/// Wildcard signature +typedef struct { + /// Wildcard numeric identifier. + policy_map_key_wildcard_id_t id; + /// Wildcard represented as a text string. + const char *str; +} wildcard_signature_t; + +// TODO: consider removing +/// Table of wildcard signatures +const wildcard_signature_t WILDCARD_SIGNATURES[] = { + { .id = KEY_WILDCARD_NONE, .str = "" }, + { .id = KEY_WILDCARD_ANY, .str = "/**" }, + { .id = KEY_WILDCARD_STANDARD_CHAINS, .str = "/<0;1>/*" }, + { .id = KEY_WILDCARD_EXTERNAL_CHAIN, .str = "/0/*" }, + { .id = KEY_WILDCARD_INTERNAL_CHAIN, .str = "/1/*" } +}; +/// Number of records in the table of wildcard signatures +static const size_t N_WILDCARD_SIGNATURES = + sizeof(WILDCARD_SIGNATURES) / sizeof(WILDCARD_SIGNATURES[0]); + +/** + * Scans a single token in the buffer while keeping its position. + * + * @param[in] buffer + * Input buffer with a token to scan, position is preserved. + * @param[in] separator + * A separator character on which scan process is stopped. + * @param[out] result + * Pointer to structure instance receiving scan results. + * + * @return true if sucessfull, false in case of error + */ +static bool scan_token(buffer_t *buffer, + char separator, + token_scan_result_t *result) { + buffer_snapshot_t in_buf_snapshot = buffer_snapshot(buffer); + memset(result, 0, sizeof(token_scan_result_t)); + + char c; + while (buffer_peek(buffer, (uint8_t*)&c) && c != separator) { + if (++result->token_len < sizeof(result->prefix)) { + result->prefix[result->token_len - 1] = c; + } + + if (c >= '0' && c <= '9') { + result->charset |= CHARSET_NUM; + } else if (c >= 'a' && c <= 'f') { + result->charset |= CHARSET_ALPHA_AF_LOW; + } else if (c >= 'g' && c <= 'z') { + result->charset |= CHARSET_ALPHA_GZ_LOW; + } else if (c >= 'A' && c <= 'F') { + result->charset |= CHARSET_ALPHA_AF_UP; + } else if (c >= 'G' && c <= 'Z') { + result->charset |= CHARSET_ALPHA_GZ_UP; + } else if (c == '(' || c == ')') { + result->charset |= CHARSET_BRACKETS; + } else { + result->charset |= CHARSET_OTHER; + } + buffer_seek_cur(buffer, 1); + } + + buffer_restore(buffer, in_buf_snapshot); + return !!result->token_len; +} + +/** + * Reads lowercase hexadecimal data bytes from buffer. + * + * @param[in,out] buffer + * Input buffer. + * @param[out] out + * Pointer to output buffer. It is the responsibility of the caller to make sure that the output + * buffer is not smaller than the value in variable pointed by `out_len`. + * @param[in] out_len + * Maximum number of bytes to read, must be no greater than INT_MAX. + * @param[in] terminator + * Terminating character used to stop reading input data. Set to -1 if this feature is not needed. + * + * @return length of outputted data in bytes or -1 in case of error + */ +static int read_lowercase_hex_data(buffer_t *buffer, + uint8_t *out, + size_t out_len, + int terminator) { + size_t out_idx = 0; + uint8_t c; + char num[2]; + + if (out_len > INT_MAX) { + return -1; + } + + while (buffer_peek(buffer, &c) && c != terminator && out_idx < out_len) { + if (!buffer_read_bytes(buffer, (uint8_t *) num, 2)) { + return -1; + } + if (!is_lowercase_hex(num[0]) || !is_lowercase_hex(num[1])) { + return -1; + } + out[out_idx++] = lowercase_hex_to_int(num[0]) << 4 | lowercase_hex_to_int(num[1]); + } + return (int)out_idx; +} + +/** + * Finds the numeric wildcard identifier corresponding to a given wildcard string. + * + * @param[in] wildcard_str + * Wildcard represented as a text string. + * + * @return a non-negative wildcard identifier or -1 if not found + */ +static int find_wildcard(const char *wildcard_str) { + for (size_t i = 0; i < N_WILDCARD_SIGNATURES; ++i) { + const char *curr_str = (const char *) PIC(WILDCARD_SIGNATURES[i].str); + if (0 == strncmp(curr_str, wildcard_str, MAX_POLICY_MAP_KEY_WILDCARD_LEN)) { + return (int) PIC(WILDCARD_SIGNATURES[i].id); + } + } + return -1; +} + +/** + * Prototype for function implementing blinding key parser. + * + * This function should parse a BLINDING_KEY expression enclosed in ct() tag as specified in + * ELIP: 150 and ELIP 151 from the `in_buf` buffer, aallocating the nodes and variables in + * `out_buf`. + * + * @param[in,out] ctx + * Script parser context. + * @param[in] token_len + * Size of key token in characters. + * + * @return 0 if successful, a negative number on error. + */ +typedef int (*blinding_key_parser_t)(script_parser_ctx_t *ctx, size_t token_len); + +/** + * Parses slip77() expression within BLINDING_KEY context. + * + * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. + * + * @param[in,out] ctx + * Script parser context. + * @param[in] token_len + * Size of key token in characters. + * + * @return 0 if successful, a negative number on error. + */ +static int parse_ct_slip77(script_parser_ctx_t *ctx, size_t token_len) { + UNUSED(token_len); + + policy_node_blinding_privkey_t *node = (policy_node_blinding_privkey_t *) + buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_privkey_t), true); + if (NULL == node) { + return -1; + } + node->type = TOKEN_SLIP77; + + bool ok = buffer_skip_data(ctx->in_buf, (const uint8_t*) "slip77(", sizeof("slip77(") - 1); + ok = ok && sizeof(node->privkey) == + read_lowercase_hex_data(ctx->in_buf, node->privkey, sizeof(node->privkey), ')'); + ok = ok && buffer_skip_data(ctx->in_buf, (const uint8_t*) ")", 1); + + return ok ? 0 : -1; +} + +/** + * Parses hexadecimal public key expression within BLINDING_KEY context. + * + * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. + * + * @param[in,out] ctx + * Script parser context. + * @param[in] token_len + * Size of key token in characters. + * + * @return 0 if successful, a negative number on error. + */ +static int parse_ct_hex_pubkey(script_parser_ctx_t *ctx, size_t token_len) { + UNUSED(token_len); + + policy_node_blinding_pubkey_t *node = (policy_node_blinding_pubkey_t *) + buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_pubkey_t), true); + if (NULL == node) { + return -1; + } + node->type = TOKEN_HEX_PUB; + + bool ok = sizeof(node->pubkey) == + read_lowercase_hex_data(ctx->in_buf, node->pubkey, sizeof(node->pubkey), ','); + + return ok && (0x02 == node->pubkey[0] || 0x03 == node->pubkey[0]) ? 0 : -1; +} + +/** + * Parses hexadecimal private key expression within BLINDING_KEY context. + * + * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. + * + * @param[in,out] ctx + * Script parser context. + * @param[in] token_len + * Size of key token in characters. + * + * @return 0 if successful, a negative number on error. + */ +static int parse_ct_hex_privkey(script_parser_ctx_t *ctx, size_t token_len) { + UNUSED(token_len); + + policy_node_blinding_privkey_t *node = (policy_node_blinding_privkey_t *) + buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_privkey_t), true); + if (NULL == node) { + return -1; + } + node->type = TOKEN_HEX_PRV; + + bool ok = sizeof(node->privkey) == + read_lowercase_hex_data(ctx->in_buf, node->privkey, sizeof(node->privkey), ','); + + return ok ? 0 : -1; +} + +/** + * Parses xpub expression within BLINDING_KEY context. + * + * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. + * + * @param[in,out] ctx + * Script parser context. + * @param[in] token_len + * Size of key token in characters. + * + * @return 0 if successful, a negative number on error. + */ +static int parse_ct_xpub(script_parser_ctx_t *ctx, size_t token_len) { + serialized_extended_pubkey_check_t pubkey_check; + const serialized_extended_pubkey_t *pubkey = &pubkey_check.serialized_extended_pubkey; + + if (!buffer_can_read(ctx->in_buf, token_len)) { + return -1; + } + if (sizeof(pubkey_check) != base58_decode((char*) buffer_get_cur(ctx->in_buf), + token_len, + (uint8_t *) &pubkey_check, + sizeof(pubkey_check))) { + return -1; + } + + uint8_t checksum[4]; + crypto_get_checksum((uint8_t *)&pubkey_check.serialized_extended_pubkey, + sizeof(pubkey_check.serialized_extended_pubkey), + checksum); + if (!memeq(checksum, pubkey_check.checksum, sizeof(checksum))) { + return -1; + } + if (read_u32_be(pubkey->version, 0) != ctx->bip32_pubkey_version || + !(0x02 == pubkey->compressed_pubkey[0] || 0x03 == pubkey->compressed_pubkey[0])) { + return -1; + } + + policy_node_blinding_pubkey_t *node = (policy_node_blinding_pubkey_t *) + buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_pubkey_t), true); + if (NULL == node) { + return -1; + } + node->type = TOKEN_XPUB; + memcpy(node->pubkey, pubkey->compressed_pubkey, sizeof(node->pubkey)); + + return buffer_seek_cur(ctx->in_buf, token_len) ? 0 : -1; +} + +/** + * Parses xprv expression within BLINDING_KEY context. + * + * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. + * + * @param[in,out] ctx + * Script parser context. + * @param[in] token_len + * Size of key token in characters. + * + * @return 0 if successful, a negative number on error. + */ +static int parse_ct_xprv(script_parser_ctx_t *ctx, size_t token_len) { + serialized_extended_privkey_check_t privkey_check; + const serialized_extended_privkey_t *privkey = &privkey_check.serialized_extended_privkey; + + if (!buffer_can_read(ctx->in_buf, token_len)) { + return -1; + } + if (sizeof(privkey_check) != base58_decode((char*) buffer_get_cur(ctx->in_buf), + token_len, + (uint8_t *) &privkey_check, + sizeof(privkey_check))) { + return -1; + } + + uint8_t checksum[4]; + crypto_get_checksum((uint8_t *)&privkey_check.serialized_extended_privkey, + sizeof(privkey_check.serialized_extended_privkey), + checksum); + if (!memeq(checksum, privkey_check.checksum, sizeof(checksum))) { + return -1; + } + if (read_u32_be(privkey->version, 0) != ctx->bip32_privkey_version || + 0 != privkey->null_prefix) { + return -1; + } + + policy_node_blinding_privkey_t *node = (policy_node_blinding_privkey_t *) + buffer_alloc(ctx->out_buf, sizeof(policy_node_blinding_privkey_t), true); + if (NULL == node) { + return -1; + } + node->type = TOKEN_XPRV; + memcpy(node->privkey, privkey->privkey, sizeof(node->privkey)); + + return buffer_seek_cur(ctx->in_buf, token_len) ? 0 : -1; +} + +/** + * Parses elip151 expression within BLINDING_KEY context. + * + * Corresponds to `blinding_key_parser_t` type, refer to its description for more details. + * + * @param[in,out] ctx + * Script parser context. + * @param[in] token_len + * Size of key token in characters. + * + * @return 0 if successful, a negative number on error. + */ +static int parse_ct_elip151(script_parser_ctx_t *ctx, size_t token_len) { + UNUSED(token_len); + + if (!buffer_skip_data(ctx->in_buf, (const uint8_t*) "elip151", sizeof("elip151") - 1)) { + return -1; + } + + policy_node_t *node = (policy_node_t *) + buffer_alloc(ctx->out_buf, sizeof(policy_node_t), true); + + if (node) { + node->type = TOKEN_ELIP151; + node->node_data = NULL; + return 0; + } + return -1; +} + +/// Blinding key signature +typedef struct { + size_t min_len; ///< Minimum allowed length + size_t max_len; ///< Maximum allowed length + uint32_t charset; ///< Allowed charset + blinding_key_parser_t parser; ///< Pinter to a function parsing a BLINDING_KEY expression. + char prefix[TOKEN_PREFIX_LEN + 1]; ///< Token prefix +} blinding_key_signature_t; + +/// Table of known blinding key signatures +static const blinding_key_signature_t BLINDING_KEY_SIGNATURES[] = { + { + .prefix = "slip77", + .min_len = 72, + .max_len = 72, + .charset = CHARSET_ALPHANUM_LOW|CHARSET_BRACKETS, + .parser = parse_ct_slip77 + }, + { + .prefix = "xpub", + .min_len = 111, + .max_len = 112, + .charset = CHARSET_ALPHANUM, + .parser = parse_ct_xpub + }, + { + .prefix = "xprv", + .min_len = 111, + .max_len = 112, + .charset = CHARSET_ALPHANUM, + .parser = parse_ct_xprv + }, + { + .prefix = "elip151", + .min_len = 7, + .max_len = 7, + .charset = CHARSET_ALPHANUM_LOW, + .parser = parse_ct_elip151 + }, + { + .prefix = "", + .min_len = 64, + .max_len = 64, + .charset = CHARSET_HEX_LOW, + .parser = parse_ct_hex_privkey + }, + { + .prefix = "", + .min_len = 66, + .max_len = 66, + .charset = CHARSET_HEX_LOW, + .parser = parse_ct_hex_pubkey + } +}; +/// Number of records in the table of known blinding key signatures +static const size_t N_BLINDING_KEY_SIGNATURES = + sizeof(BLINDING_KEY_SIGNATURES) / sizeof(BLINDING_KEY_SIGNATURES[0]); + +/** + * Looks through the table of blinding key signatures and returns corresponding + * parsing function. + * + * @param[in] scan_result + * Results of token scan used to find blinding key type by its signature. + * + * @return pointer to function parsing identified type of blinding key or NULL if not found. + */ +blinding_key_parser_t find_blinding_key_parser(const token_scan_result_t *scan_result) { + for (size_t i = 0; i < N_BLINDING_KEY_SIGNATURES; ++i) { + uint32_t expected_charset = (uint32_t) PIC(BLINDING_KEY_SIGNATURES[i].charset); + const char *expected_prefix = (const char *) PIC(BLINDING_KEY_SIGNATURES[i].prefix); + if (0 == (scan_result->charset & ~expected_charset) && + scan_result->token_len >= (size_t) PIC(BLINDING_KEY_SIGNATURES[i].min_len) && + scan_result->token_len <= (size_t) PIC(BLINDING_KEY_SIGNATURES[i].max_len) && + 0 == strncmp(expected_prefix, + scan_result->prefix, + strnlen(expected_prefix, TOKEN_PREFIX_LEN))) { + return (blinding_key_parser_t) PIC(BLINDING_KEY_SIGNATURES[i].parser); + } + } + + return NULL; +} + +/** + * Internal function parsing blinding key script inside ct() descriptor. + * + * Parses a BLINDING_KEY expression as specified in ELIP: 150 from the in_buf + * buffer, allocating the node and variables in out_buf.The initial pointer in + * out_buf will contain the node of the BLINDING_KEY. + * + * @param[in,out] ctx + * Script parser context. + * + * @return 0 if successful, a negative number on error. + */ +static int parse_blinding_key_script(script_parser_ctx_t *ctx) { + token_scan_result_t scan_result; + if (!scan_token(ctx->in_buf, ',', &scan_result)) { + return -1; + } + + blinding_key_parser_t key_parser = find_blinding_key_parser(&scan_result); + if (key_parser) { + return (*key_parser)(ctx, scan_result.token_len); + } + return -1; +} + + +// TODO: make it a function +#if 0 + +#ifdef HAVE_LIQUID + case TOKEN_CT: { + if (depth != 0) { + return -16; // can only be top-level + } + + policy_node_ct_t *node = + (policy_node_ct_t *) buffer_alloc(ctx->out_buf, sizeof(policy_node_ct_t), true); + if (node == NULL) { + return -17; + } + node->type = token; + + inner_context_flags |= CONTEXT_WITHIN_CT; + + // the master blinding key script is recursively parsed (if successful) in the current + // location of the output buffer + node->mbk_script = (policy_node_t *) buffer_get_cur_aligned(ctx->out_buf); + if (NULL == node->mbk_script || 0 > parse_blinding_key_script(ctx)) { + // failed while parsing internal script + return -18; + } + + // scripts must be separated by comma + if (!buffer_read_u8(ctx->in_buf, (uint8_t *) &c) || c != ',') { + PRINTF("Unexpected char: %c. Was expecting: ,\n", c); + return -19; + } + + // the internal script is recursively parsed (if successful) in the current location of + // the output buffer + int res2 = 0; + node->script = (policy_node_t *) buffer_get_cur_aligned(ctx->out_buf); + if (NULL == node->script || (res2 = parse_script(ctx, depth + 1, inner_context_flags)) < 0) { + // failed while parsing internal script + return res2 * 100 - 20; + } + break; + } +#endif // HAVE_LIQUID + +#endif + +bool policy_is_multisig(const policy_node_t *policy) { + const policy_node_t *node = policy; + + while(node != NULL) { + switch(node->type) + { + case TOKEN_CT: + node = ((policy_node_ct_t *)node)->script; + break; + + case TOKEN_SH: + case TOKEN_WSH: + node = ((policy_node_with_script_t *)node)->script; + break; + + case TOKEN_MULTI: + case TOKEN_SORTEDMULTI: + return true; + + // TOKEN_PKH, TOKEN_WPKH, TOKEN_TR + // TODO: add Taproot multisig when it will be supported project-wise + default: + return false; + } + } + + return false; +} + +// TODO: consider removing +bool validate_policy_map_extended_pubkey(const policy_map_key_info_t *key_info, + uint32_t bip32_pubkey_version) { + int status = validate_serialized_extended_pubkey( + key_info->ext_pubkey, + key_info->master_key_derivation, + key_info->has_key_origin ? key_info->master_key_derivation_len : -1, + bip32_pubkey_version + ); + + return EXTENDED_PUBKEY_VALID == status; +} + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/liquid/liquid_wallet.h b/src/liquid/liquid_wallet.h new file mode 100644 index 000000000..99d475e8d --- /dev/null +++ b/src/liquid/liquid_wallet.h @@ -0,0 +1,84 @@ +#pragma once +#ifdef HAVE_LIQUID + +#include "../common/wallet.h" + +// TODO: remove +#ifdef HAVE_LIQUID +/// Maximum supported number of keys for a policy map. +#define MAX_POLICY_MAP_COSIGNERS 7 +#else +/// Maximum supported number of keys for a policy map. +#define MAX_POLICY_MAP_COSIGNERS 5 +#endif + + +// TODO: replace with MAX_PUBKEYS_PER_MULTISIG +/// Maximum supported number of keys for a policy map. +#define MAX_POLICY_MAP_KEYS MAX_POLICY_MAP_COSIGNERS + +// TODO: consider removing +/// Public key wildcards defining the rules for child key derivation. +typedef enum { + /// No wildcard. + KEY_WILDCARD_NONE = 0, + /// Any derivation is allowed: `/**`. + KEY_WILDCARD_ANY = 1, + /// Internal or external chain with an arbitrary address index: `/<0;1>/*`. + KEY_WILDCARD_STANDARD_CHAINS, + /// External chain with an arbitrary address index: `/0/*`. + KEY_WILDCARD_EXTERNAL_CHAIN, + /// Internal chain (change) with an arbitrary address index: `/1/*`. + KEY_WILDCARD_INTERNAL_CHAIN, +} policy_map_key_wildcard_id_t; + +/// Policy node ct() +typedef struct { + /// Type of this policy node + struct policy_node_s base; // type is TOKEN_MULTI or TOKEN_SORTEDMULTI; // == TOKEN_CT + /// Master blinding key script, typically slip77() + policy_node_t *mbk_script; + /// Inner script + policy_node_t *script; +} policy_node_ct_t; + +/// Policy node containing ELIP 150 blinding public key +typedef struct { + /// Type of this policy node, one of: TOKEN_HEX_PUB, TOKEN_XPUB + PolicyNodeType type; + /// Compressed public key + uint8_t pubkey[33]; +} policy_node_blinding_pubkey_t; + +/// Policy node containing ELIP 150 blinding private key +typedef struct { + /// Type of this policy node, one of: TOKEN_SLIP77, TOKEN_HEX_PRV, TOKEN_XPRV + PolicyNodeType type; + /// Private key + uint8_t privkey[32]; +} policy_node_blinding_privkey_t; + +/** + * Checks if the policy specifies a multisignature wallet. + * + * @param[in] policy + * Pointer to wallet's top-level policy node. + * + * @return true if the wallet is multisig, false otherwise. + */ +bool policy_is_multisig(const policy_node_t *policy); + +/** + * Validates the public key stored in key information for a policy map wallet. + * + * @param[in] key_info + * Key information. + * @param bip32_pubkey_version + * Version prefix to use for the public key. + * + * @return true if key is valid, false otherwise. + */ +bool validate_policy_map_extended_pubkey(const policy_map_key_info_t *key_info, + uint32_t bip32_pubkey_version); + +#endif // HAVE_LIQUID \ No newline at end of file diff --git a/src/main.c b/src/main.c index 560cdd871..8dc5880ad 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,15 +31,24 @@ #include "boilerplate/constants.h" #include "boilerplate/dispatcher.h" +#include "debug-helpers/debug.h" + +#include "handler/handlers.h" #include "commands.h" +#include "common/wallet.h" + // common declarations between legacy and new code; will refactor it out later #include "swap/swap_lib_calls.h" #include "swap/swap_globals.h" #include "swap/handle_swap_sign_transaction.h" #include "swap/handle_get_printable_amount.h" #include "swap/handle_check_address.h" -#include "main.h" + +#ifdef HAVE_NBGL +#include "nbgl_use_case.h" +#endif + #include "tests.h" #ifdef HAVE_BOLOS_APP_STACK_CANARY @@ -50,11 +59,8 @@ uint8_t G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; ux_state_t G_ux; bolos_ux_params_t G_ux_params; -command_state_t G_command_state; dispatcher_context_t G_dispatcher_context; -uint8_t G_app_mode; - // clang-format off const command_descriptor_t COMMAND_DESCRIPTORS[] = { { @@ -125,20 +131,6 @@ void app_main() { return; } - // if not Bitcoin or Bitcoin-testnet, we only support the legacy APDUS. - // to be removed once the apps are split - if (BIP32_PUBKEY_VERSION != 0x0488B21E && - BIP32_PUBKEY_VERSION != 0x043587CF) { - io_send_sw(SW_CLA_NOT_SUPPORTED); - return; - } - - if (G_app_mode != APP_MODE_NEW) { - explicit_bzero(&G_command_state, sizeof(G_command_state)); - - G_app_mode = APP_MODE_NEW; - } - // Reset structured APDU command memset(&cmd, 0, sizeof(cmd)); // Parse APDU command from G_io_apdu_buffer @@ -150,23 +142,30 @@ void app_main() { LOG_APDU(&cmd); - if (G_swap_state.called_from_swap && - (cmd.ins != SIGN_PSBT && cmd.ins != GET_MASTER_FINGERPRINT)) { - PRINTF("Only SIGN_PSBT and GET_MASTER_FINGERPRINT can be called during swap\n"); - io_send_sw(SW_INS_NOT_SUPPORTED); - return; + if (G_swap_state.called_from_swap) { + if (cmd.cla != CLA_APP) { + io_send_sw(SW_CLA_NOT_SUPPORTED); + continue; + } + if (cmd.ins != GET_EXTENDED_PUBKEY && cmd.ins != GET_WALLET_ADDRESS && + cmd.ins != SIGN_PSBT && cmd.ins != GET_MASTER_FINGERPRINT) { + PRINTF( + "Only GET_EXTENDED_PUBKEY, GET_WALLET_ADDRESS, SIGN_PSBT and " + "GET_MASTER_FINGERPRINT can be called during swap\n"); + io_send_sw(SW_INS_NOT_SUPPORTED); + continue; + } } // Dispatch structured APDU command to handler apdu_dispatcher(COMMAND_DESCRIPTORS, sizeof(COMMAND_DESCRIPTORS) / sizeof(COMMAND_DESCRIPTORS[0]), - (machine_context_t *) &G_command_state, - sizeof(G_command_state), ui_menu_main, &cmd); if (G_swap_state.called_from_swap && G_swap_state.should_exit) { - os_sched_exit(0); + // Bitcoin app will keep listening as long as it does not receive a valid TX + finalize_exchange_sign_transaction(true); } } } @@ -187,7 +186,13 @@ void app_exit() { static void initialize_app_globals() { io_reset_timeouts(); - memset(&G_swap_state, 0, sizeof(G_swap_state)); + + // We only zero the called_from_swap and should_exit fields and not the entire G_swap_state, as + // we need the globals initialization to happen _after_ calling copy_transaction_parameters when + // processing a SIGN_TRANSACTION request from the swap app (which initializes the other fields + // of G_swap_state). + G_swap_state.called_from_swap = false; + G_swap_state.should_exit = false; } /** @@ -205,19 +210,16 @@ void coin_main() { // assumptions on the length of data structures _Static_assert(sizeof(cx_sha256_t) <= 108, "cx_sha256_t too large"); - _Static_assert(sizeof(policy_map_key_info_t) <= 148, "policy_map_key_info_t too large"); - + _Static_assert(sizeof(policy_map_key_info_t) <= 156, "policy_map_key_info_t too large"); // we assume in display.c that the ticker size is at most 5 characters (+ null) _Static_assert(sizeof(COIN_COINID_SHORT) <= 6, "COIN_COINID_SHORT too large"); - G_app_mode = APP_MODE_UNINITIALIZED; - #if defined(HAVE_PRINT_STACK_POINTER) && defined(HAVE_BOLOS_APP_STACK_CANARY) PRINTF("STACK CANARY ADDRESS: %08x\n", &app_stack_canary); #endif #ifdef HAVE_SEMIHOSTED_PRINTF - PRINTF("APDU State size: %d\n", sizeof(command_state_t)); + PRINTF("\nApplication \"%s\" started\n", APPNAME); #endif // Reset dispatcher state @@ -237,10 +239,10 @@ void coin_main() { TRY { io_seproxyhal_init(); -#ifdef TARGET_NANOX +#ifdef HAVE_BLE // grab the current plane mode setting G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); -#endif // TARGET_NANOX +#endif // HAVE_BLE USB_power(0); USB_power(1); @@ -271,42 +273,46 @@ void coin_main() { app_exit(); } -static void swap_library_main_helper(struct libargs_s *args) { +static void swap_library_main_helper(libargs_t *args) { PRINTF("Inside a library \n"); switch (args->command) { case CHECK_ADDRESS: // ensure result is zero if an exception is thrown args->check_address->result = 0; - args->check_address->result = - handle_check_address(args->check_address); + args->check_address->result = handle_check_address(args->check_address); break; - case SIGN_TRANSACTION: + case SIGN_TRANSACTION: { + // copying arguments (pointing to globals) to context *before* + // calling `initialize_app_globals` as it could override them + const bool args_are_copied = copy_transaction_parameters(args->create_transaction); initialize_app_globals(); - if (copy_transaction_parameters(args->create_transaction)) { + if (args_are_copied) { // never returns - G_app_mode = APP_MODE_UNINITIALIZED; G_swap_state.called_from_swap = 1; io_seproxyhal_init(); UX_INIT(); +#ifdef HAVE_BAGL ux_stack_push(); +#elif defined(HAVE_NBGL) + nbgl_useCaseSpinner("Signing"); +#endif // HAVE_BAGL USB_power(0); USB_power(1); // ui_idle(); PRINTF("USB power ON/OFF\n"); -#ifdef TARGET_NANOX +#ifdef HAVE_BLE // grab the current plane mode setting G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); -#endif // TARGET_NANOX -#ifdef HAVE_BLE BLE_power(0, NULL); - BLE_power(1, "Nano X"); + BLE_power(1, NULL); #endif // HAVE_BLE app_main(); } break; + } case GET_PRINTABLE_AMOUNT: // ensure result is zero if an exception is thrown (compatibility breaking, disabled // until LL is ready) @@ -319,7 +325,7 @@ static void swap_library_main_helper(struct libargs_s *args) { } } -void swap_library_main(struct libargs_s *args) { +void swap_library_main(libargs_t *args) { bool end = false; /* This loop ensures that swap_library_main_helper and os_lib_end are called * within a try context, even if an exception is thrown */ @@ -347,23 +353,19 @@ __attribute__((section(".boot"))) int main(int arg0) { os_boot(); if (!arg0) { - // Bitcoin application launched from dashboard + // Application launched from dashboard coin_main(); return 0; } - struct libargs_s *args = (struct libargs_s *) arg0; - if (args->id != 0x100 || args->command == RUN_APPLICATION) { + // Application launched as library (for swap support) + libargs_t *args = (libargs_t *) arg0; + if (args->id != 0x100) { app_exit(); return 0; } -#if !defined(HAVE_LIQUID) || defined(LIQUID_HAS_SWAP) - // Called as Bitcoin library during swap swap_library_main(args); -#else - app_exit(); -#endif return 0; } diff --git a/src/main.h b/src/main.h deleted file mode 100644 index c3b761172..000000000 --- a/src/main.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#define APP_MODE_UNINITIALIZED 0 // state before any APDU is executed -#define APP_MODE_LEGACY 1 // state when the app is running legacy APDUs -#define APP_MODE_NEW 2 // state when the app is running new APDUs - -/** - * Keeps track whether the app is running in "legacy" or "new" mode. - */ -extern uint8_t G_app_mode; diff --git a/src/swap/btchip_bcd.c b/src/swap/btchip_bcd.c index 529900c38..cc7a69f5e 100644 --- a/src/swap/btchip_bcd.c +++ b/src/swap/btchip_bcd.c @@ -69,9 +69,8 @@ unsigned char btchip_convert_hex_amount_to_displayable_no_globals(const unsigned workOffset = offset; for (i = 0; i < LOOP2; i++) { unsigned char allZero = 1; - unsigned char j2; - for (j2 = i; j2 < LOOP2; j2++) { - if (scratch[workOffset + j2] != 0) { + for (j = i; j < LOOP2; j++) { + if (scratch[workOffset + j] != 0) { allZero = 0; break; } diff --git a/src/swap/handle_check_address.c b/src/swap/handle_check_address.c index 4df20bd3f..1c954ec5d 100644 --- a/src/swap/handle_check_address.c +++ b/src/swap/handle_check_address.c @@ -8,6 +8,8 @@ #include "../common/segwit_addr.h" #include "../crypto.h" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // constants previously defined in btchip_apdu_get_wallet_public_key.h #define P1_NO_DISPLAY 0x00 #define P1_DISPLAY 0x01 @@ -68,7 +70,11 @@ bool get_address_from_compressed_public_key(unsigned char format, uint8_t tweaked_key[32]; uint8_t parity; - if (0 != crypto_tr_tweak_pubkey(compressed_pub_key + 1, &parity, tweaked_key)) { + if (0 > crypto_tr_tweak_pubkey(compressed_pub_key + 1, + (uint8_t[]){}, + 0, + &parity, + tweaked_key)) { return false; } @@ -84,6 +90,11 @@ bool get_address_from_compressed_public_key(unsigned char format, return true; } +static int os_strcmp(const char* s1, const char* s2) { + size_t size = strlen(s1) + 1; + return memcmp(s1, s2, size); +} + int handle_check_address(check_address_parameters_t* params) { unsigned char compressed_public_key[33]; PRINTF("Params on the address %d\n", (unsigned int) params); @@ -118,10 +129,10 @@ int handle_check_address(check_address_parameters_t* params) { PRINTF("Can't create address from given public key\n"); return 0; } - if (strncmp(address, params->address_to_check, sizeof(address)) != 0) { + if (os_strcmp(address, params->address_to_check) != 0) { PRINTF("Addresses don't match\n"); return 0; } PRINTF("Addresses match\n"); return 1; -} +} \ No newline at end of file diff --git a/src/swap/handle_get_printable_amount.c b/src/swap/handle_get_printable_amount.c index 484b36bf9..2891083b0 100644 --- a/src/swap/handle_get_printable_amount.c +++ b/src/swap/handle_get_printable_amount.c @@ -1,4 +1,5 @@ #include +#include #include "handle_get_printable_amount.h" diff --git a/src/swap/handle_get_printable_amount.h b/src/swap/handle_get_printable_amount.h index ad6faffec..a1a23f361 100644 --- a/src/swap/handle_get_printable_amount.h +++ b/src/swap/handle_get_printable_amount.h @@ -2,4 +2,4 @@ #include "swap_lib_calls.h" -int handle_get_printable_amount(get_printable_amount_parameters_t* get_printable_amount_params); +int handle_get_printable_amount(get_printable_amount_parameters_t* get_printable_amount_params); \ No newline at end of file diff --git a/src/swap/handle_swap_sign_transaction.c b/src/swap/handle_swap_sign_transaction.c index 0a2ca36fa..9333e27a9 100644 --- a/src/swap/handle_swap_sign_transaction.c +++ b/src/swap/handle_swap_sign_transaction.c @@ -3,14 +3,17 @@ #include "ux.h" #include "usbd_core.h" #include "os_io_seproxyhal.h" +#include "os.h" #include "handle_swap_sign_transaction.h" -#include "../main.h" #include "../globals.h" #include "../swap/swap_globals.h" #include "../common/read.h" +// Save the BSS address where we will write the return value when finished +static uint8_t* G_swap_sign_return_value_address; + bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params) { char destination_address[65]; uint8_t amount[8]; @@ -42,6 +45,9 @@ bool copy_transaction_parameters(create_transaction_parameters_t* sign_transacti sign_transaction_params->fee_amount, sign_transaction_params->fee_amount_length); + os_explicit_zero_BSS_segment(); + G_swap_sign_return_value_address = &sign_transaction_params->result; + G_swap_state.amount = read_u64_be(amount, 0); G_swap_state.fees = read_u64_be(fees, 0); memcpy(G_swap_state.destination_address, @@ -49,3 +55,8 @@ bool copy_transaction_parameters(create_transaction_parameters_t* sign_transacti sizeof(G_swap_state.destination_address)); return true; } + +void __attribute__((noreturn)) finalize_exchange_sign_transaction(bool is_success) { + *G_swap_sign_return_value_address = is_success; + os_lib_end(); +} diff --git a/src/swap/handle_swap_sign_transaction.h b/src/swap/handle_swap_sign_transaction.h index d961b94cf..bbd82b24b 100644 --- a/src/swap/handle_swap_sign_transaction.h +++ b/src/swap/handle_swap_sign_transaction.h @@ -3,3 +3,5 @@ #include "swap_lib_calls.h" bool copy_transaction_parameters(create_transaction_parameters_t* sign_transaction_params); + +void __attribute__((noreturn)) finalize_exchange_sign_transaction(bool is_success); diff --git a/src/swap/swap_lib_calls.h b/src/swap/swap_lib_calls.h index baf6b197f..dc88417ae 100644 --- a/src/swap/swap_lib_calls.h +++ b/src/swap/swap_lib_calls.h @@ -1,5 +1,11 @@ #pragma once +/* This file is the shared API between Exchange and the apps started in Library mode for Exchange + * + * DO NOT MODIFY THIS FILE IN APPLICATIONS OTHER THAN EXCHANGE + * On modification in Exchange, forward the changes to all applications supporting Exchange + */ + #include "stdbool.h" #include "stdint.h" @@ -11,17 +17,27 @@ #define GET_PRINTABLE_AMOUNT 4 +/* + * Amounts are stored as bytes, with a max size of 16 (see protobuf + * specifications). Max 16B integer is 340282366920938463463374607431768211455 + * in decimal, which is a 32-long char string. + * The printable amount also contains spaces, the ticker symbol (with variable + * size, up to 12 in Ethereum for instance) and a terminating null byte, so 50 + * bytes total should be a fair maximum. + */ +#define MAX_PRINTABLE_AMOUNT_SIZE 50 + // structure that should be send to specific coin application to get address typedef struct check_address_parameters_s { // IN - unsigned char* coin_configuration; - unsigned char coin_configuration_length; + uint8_t *coin_configuration; + uint8_t coin_configuration_length; // serialized path, segwit, version prefix, hash used, dictionary etc. // fields and serialization format depends on spesific coin app - unsigned char* address_parameters; - unsigned char address_parameters_length; - char* address_to_check; - char* extra_id_to_check; + uint8_t *address_parameters; + uint8_t address_parameters_length; + char *address_to_check; + char *extra_id_to_check; // OUT int result; } check_address_parameters_t; @@ -29,28 +45,30 @@ typedef struct check_address_parameters_s { // structure that should be send to specific coin application to get printable amount typedef struct get_printable_amount_parameters_s { // IN - unsigned char* coin_configuration; - unsigned char coin_configuration_length; - unsigned char* amount; - unsigned char amount_length; + uint8_t *coin_configuration; + uint8_t coin_configuration_length; + uint8_t *amount; + uint8_t amount_length; bool is_fee; // OUT - char printable_amount[30]; - // int result; + char printable_amount[MAX_PRINTABLE_AMOUNT_SIZE]; } get_printable_amount_parameters_t; typedef struct create_transaction_parameters_s { - unsigned char* coin_configuration; - unsigned char coin_configuration_length; - unsigned char* amount; - unsigned char amount_length; - unsigned char* fee_amount; - unsigned char fee_amount_length; - char* destination_address; - char* destination_address_extra_id; + // IN + uint8_t *coin_configuration; + uint8_t coin_configuration_length; + uint8_t *amount; + uint8_t amount_length; + uint8_t *fee_amount; + uint8_t fee_amount_length; + char *destination_address; + char *destination_address_extra_id; + // OUT + uint8_t result; } create_transaction_parameters_t; -struct libargs_s { +typedef struct libargs_s { unsigned int id; unsigned int command; unsigned int unused; @@ -59,4 +77,4 @@ struct libargs_s { create_transaction_parameters_t *create_transaction; get_printable_amount_parameters_t *get_printable_amount; }; -}; +} libargs_t; diff --git a/src/ui/bagl_custom_streaming.c b/src/ui/bagl_custom_streaming.c new file mode 100644 index 000000000..1734daeee --- /dev/null +++ b/src/ui/bagl_custom_streaming.c @@ -0,0 +1,265 @@ + +/******************************************************************************* + * Ledger Nano S - Secure firmware + * (c) 2022 Ledger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ********************************************************************************/ + +// Inspired from ledger-secure-sdk/lib_ux/src/ux_layout_paging.c + +#ifdef HAVE_BAGL + +#include "os_helpers.h" +#include "os_math.h" +#include "os_pic.h" +#include "os_print.h" +#include "os_utils.h" +#include "ux.h" +#include +#include "os.h" +#include "ux_layout_common.h" +#include "display.h" + +const bagl_element_t *ux_layout_paging_prepro_common_streaming(const bagl_element_t *element, + const char *title, + const char *text) { + // copy element before any mod + memmove(&G_ux.tmp_element, element, sizeof(bagl_element_t)); + + switch (element->component.userid) { + case 0x01: + // no step before AND no pages before + if (ux_flow_is_first() && G_ux.layout_paging.current == 0) { + return NULL; + } + break; + + case 0x02: + if (ux_flow_is_last() && G_ux.layout_paging.current == G_ux.layout_paging.count - 1) { + return NULL; + } + break; + + case 0x10: + // We set the boldness of the text. + // display + if (title) { + SPRINTF(G_ux.string_buffer, "%s", STRPIC(title)); + } else { + SPRINTF(G_ux.string_buffer, + "%d/%d", + G_ux.layout_paging.current + 1, + G_ux.layout_paging.count); + } + + G_ux.tmp_element.component.font_id = + ((G_ux.layout_paging.format & PAGING_FORMAT_BN) == PAGING_FORMAT_BN) + ? (BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER) + : (BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER); + G_ux.tmp_element.text = G_ux.string_buffer; + break; + + case 0x11: + case 0x12: + case 0x13: { + unsigned int lineidx = (element->component.userid & 0xF) - 1; + if (lineidx < UX_LAYOUT_PAGING_LINE_COUNT && G_ux.layout_paging.lengths[lineidx]) { + SPRINTF(G_ux.string_buffer, + "%.*s", + // avoid overflow + MIN(sizeof(G_ux.string_buffer) - 1, G_ux.layout_paging.lengths[lineidx]), + (text ? STRPIC(text) : G_ux.externalText) + + G_ux.layout_paging.offsets[lineidx]); + G_ux.tmp_element.text = G_ux.string_buffer; + + G_ux.tmp_element.component.font_id = + ((G_ux.layout_paging.format & PAGING_FORMAT_NB) == PAGING_FORMAT_NB) + ? (BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER) + : (BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER); + } + break; + } + } + return &G_ux.tmp_element; +} + +static void ux_layout_paging_next(ux_layout_paging_redisplay_t redisplay) { + if (G_ux.layout_paging.current == G_ux.layout_paging.count - 1) { + ux_flow_next(); + } else { + // display next page, count the number of char to fit in the next page + G_ux.layout_paging.current++; + redisplay(G_ux.stack_count - 1); + } +} + +static void ux_layout_paging_prev(ux_layout_paging_redisplay_t redisplay) { + if (G_ux.layout_paging.current == 0) { + ux_flow_prev(); + } else { + // display previous page, count the number of char to fit in the previous page + G_ux.layout_paging.current--; + redisplay(G_ux.stack_count - 1); + } +} + +STATIC_IF_NOT_INDEXED unsigned int ux_layout_paging_button_callback_common_streaming( + unsigned int button_mask, + unsigned int button_mask_counter, + ux_layout_paging_redisplay_t redisplay) { + UNUSED(button_mask_counter); + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + if (G_ux.layout_paging.current == 0) { + decrease_streaming_index(); + ux_flow_validate(); + } else { + ux_layout_paging_prev(redisplay); + } + break; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + if (G_ux.layout_paging.count == 0 || + G_ux.layout_paging.count - 1 == G_ux.layout_paging.current) { + increase_streaming_index(); + ux_flow_validate(); + } else { + ux_layout_paging_next(redisplay); + } + break; + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + increase_streaming_index(); + ux_flow_validate(); + break; + } + return 0; +} + +static const bagl_element_t *ux_layout_paging_prepro_by_addr(const bagl_element_t *element) { + // don't display if null + const void *params = ux_stack_get_current_step_params(); + if (NULL == params) { + return NULL; + } + const char *title; + const char *text; + +#if defined(HAVE_INDEXED_STRINGS) + UX_LOC_STRINGS_INDEX index = ((const ux_loc_layout_params_t *) params)->index; + title = get_ux_loc_string(index); + text = get_ux_loc_string(index + 1); +#else // defined(HAVE_INDEXED_STRINGS) + title = ((const ux_layout_paging_params_t *) params)->title; + text = ((const ux_layout_paging_params_t *) params)->text; +#endif // defined(HAVE_INDEXED_STRINGS) + return ux_layout_paging_prepro_common_streaming(element, title, text); +} + +void ux_layout_paging_init_common_streaming(unsigned int stack_slot, + const char *text, + ux_layout_paging_redisplay_t redisplay) { + bagl_font_id_e font_id; + + // At this very moment, we don't want to get rid of the format, but keep + // the one which has just been set (in case of direction backward or forward). + unsigned int backup_format = G_ux.layout_paging.format; + + // depending flow browsing direction, select the correct page to display + switch (ux_flow_direction()) { + case FLOW_DIRECTION_BACKWARD: + ux_layout_paging_reset(); + // ask the paging to start at the last page. + // This step must be performed after the 'ux_layout_paging_reset' call, + // thus we cannot mutualize the call with the one in the 'forward' case. + G_ux.layout_paging.current = -1UL; + break; + case FLOW_DIRECTION_FORWARD: + // open the first page + ux_layout_paging_reset(); + break; + case FLOW_DIRECTION_START: + // shall already be at the first page + break; + } + + G_ux.layout_paging.format = backup_format; + + // store params + ux_stack_init(stack_slot); + + // compute number of chars to display from the params complete string + if ((text == NULL) && (G_ux.externalText == NULL)) { + text = ""; // empty string to avoid disrupting the ux flow. + } + + // Use the correct font, to be able to compute correctly text width: + if (G_ux.layout_paging.format & PAGING_FORMAT_NB) { + font_id = BAGL_FONT_OPEN_SANS_EXTRABOLD_11px; + } else { + font_id = BAGL_FONT_OPEN_SANS_REGULAR_11px; + } + + // count total number of pages + G_ux.layout_paging.count = + ux_layout_paging_compute(text, -1UL, &G_ux.layout_paging, font_id); // at least one page + + // perform displaying the last page as requested (-1UL in prevstep hook does this) + if (G_ux.layout_paging.count && G_ux.layout_paging.current > G_ux.layout_paging.count - 1UL) { + G_ux.layout_paging.current = G_ux.layout_paging.count - 1; + } + + redisplay(stack_slot); +} + +void ux_layout_paging_redisplay_by_addr_streaming(unsigned int stack_slot); +STATIC_IF_NOT_INDEXED unsigned int ux_layout_paging_button_callback_by_addr_streaming( + unsigned int button_mask, + unsigned int button_mask_counter) { + return ux_layout_paging_button_callback_common_streaming( + button_mask, + button_mask_counter, + ux_layout_paging_redisplay_by_addr_streaming); +} + +void ux_layout_paging_redisplay_common(unsigned int stack_slot, + const char *text, + button_push_callback_t button_callback, + bagl_element_callback_t prepro); + +void ux_layout_paging_redisplay_by_addr_streaming(unsigned int stack_slot) { + const char *text; + const void *params = ux_stack_get_current_step_params(); + if (NULL == params) { + return; + } +#if defined(HAVE_INDEXED_STRINGS) + text = get_ux_loc_string(((const ux_loc_layout_params_t *) params)->index + 1); +#else // defined(HAVE_INDEXED_STRINGS) + text = ((const ux_layout_paging_params_t *) params)->text; +#endif // defined(HAVE_INDEXED_STRINGS) + ux_layout_paging_redisplay_common(stack_slot, + text, + ux_layout_paging_button_callback_by_addr_streaming, + ux_layout_paging_prepro_by_addr); +} + +void ux_layout_custom_init(unsigned int stack_slot) { + G_ux.layout_paging.format = PAGING_FORMAT_BN; + const ux_layout_paging_params_t *params = + (const ux_layout_paging_params_t *) ux_stack_get_step_params(stack_slot); + ux_layout_paging_init_common_streaming(stack_slot, + params->text, + ux_layout_paging_redisplay_by_addr_streaming); +} + +#endif // HAVE_BAGL diff --git a/src/ui/display.c b/src/ui/display.c index 3d089de7f..86a99776e 100644 --- a/src/ui/display.c +++ b/src/ui/display.c @@ -10,104 +10,17 @@ #include "ux.h" #include "./display.h" -#include "./display_utils.h" -#include "../constants.h" -#include "../globals.h" -#include "../boilerplate/io.h" -#include "../boilerplate/sw.h" -#include "../common/bip32.h" -#include "../common/format.h" -#include "../common/script.h" -#include "../constants.h" -#include "../liquid/liquid_assets.h" // These globals are a workaround for a limitation of the UX library that // does not allow to pass proper callbacks and context. -// the processor to call after the user approval, for UI flows that require it -static command_processor_t g_next_processor; +extern bool G_was_processing_screen_shown; -extern dispatcher_context_t G_dispatcher_context; +static bool g_ux_flow_ended; +static bool g_ux_flow_response; +static int g_current_streaming_index; -// TODO: hard to keep track of what globals are used in the same flows -// (especially since the same flow step can be shared in different flows) - -typedef struct { - char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; -} ui_path_state_t; - -typedef struct { - char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; - char pubkey[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; -} ui_path_and_pubkey_state_t; - -typedef struct { - char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; - char address[MAX_ADDRESS_LENGTH_STR + 1]; -} ui_path_and_address_state_t; - -typedef struct { - char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; - char hash_hex[64 + 1]; -} ui_path_and_hash_state_t; - -typedef struct { - char wallet_name[MAX_WALLET_NAME_LENGTH + 1]; - char policy_map[MAX_POLICY_MAP_STR_LENGTH]; - char address[MAX_ADDRESS_LENGTH_STR + 1]; -} ui_wallet_state_t; - -typedef struct { - char pubkey[MAX_POLICY_KEY_INFO_LEN + 1]; - char signer_index[sizeof("Key @999 ")]; -} ui_cosigner_pubkey_and_index_state_t; - -typedef struct { - char index[sizeof("output #999")]; - char address_or_description[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE)]; - char amount[MAX_AMOUNT_LENGTH + 1]; -#ifdef HAVE_LIQUID - char tag_hex[LIQUID_ASSET_TAG_HEX_LEN + 1]; - char token_ticker[sizeof("Of asset ") + MAX_ASSET_TICKER_LENGTH]; -#endif -} ui_validate_output_state_t; - -typedef struct { - char transaction_type[MAX_TRANSACTION_TYPE_LEN + 1]; - char fee[MAX_AMOUNT_LENGTH + 1]; -} ui_validate_transaction_state_t; - -#ifdef HAVE_LIQUID -typedef struct { - char tag_hex[LIQUID_ASSET_TAG_HEX_LEN + 1]; -} ui_asset_state_t; -#endif - -#ifdef HAVE_LIQUID -typedef struct { - char tag_hex[LIQUID_ASSET_TAG_HEX_LEN + 1]; - char ticker[MAX_ASSET_TICKER_LENGTH + 1]; - char name[MAX_ASSET_NAME_LENGTH + 1]; - char domain[MAX_ASSET_DOMAIN_LENGTH + 1]; -} ui_validate_asset_state_t; -#endif - -/** - * Union of all the states for each of the UI screens, in order to save memory. - */ -typedef union { - ui_path_and_pubkey_state_t path_and_pubkey; - ui_path_and_address_state_t path_and_address; - ui_path_and_hash_state_t path_and_hash; - ui_wallet_state_t wallet; - ui_cosigner_pubkey_and_index_state_t cosigner_pubkey_and_index; - ui_validate_output_state_t validate_output; - ui_validate_transaction_state_t validate_transaction; -#ifdef HAVE_LIQUID - ui_asset_state_t asset; - ui_validate_asset_state_t validate_asset; -#endif -} ui_state_t; +extern dispatcher_context_t G_dispatcher_context; ui_state_t g_ui_state; @@ -115,804 +28,303 @@ void send_deny_sw(dispatcher_context_t *dc) { SEND_SW(dc, SW_DENY); } -void continue_after_approval(bool approved) { - if (approved) { - G_dispatcher_context.next(g_next_processor); - } else { - G_dispatcher_context.next(send_deny_sw); - } - G_dispatcher_context.run(); -} - -/* - STATELESS STEPS - As these steps do not access per-step globals (except possibly a callback), they can be used in - any flow. -*/ - -// Step with icon and text for pubkey -UX_STEP_NOCB(ux_display_confirm_pubkey_step, pn, {&C_icon_eye, "Confirm public key"}); - -// Step with icon and text for address -UX_STEP_NOCB(ux_display_confirm_address_step, pn, {&C_icon_eye, "Confirm receive address"}); - -// Step with icon and text for a suspicious address -UX_STEP_NOCB(ux_display_unusual_derivation_path_step, - pnn, - { - &C_icon_warning, - "The derivation", - "path is unusual", - }); - -// Step with icon and text to caution the user to reject if unsure -UX_STEP_CB(ux_display_reject_if_not_sure_step, - pnn, - continue_after_approval(false), - { - &C_icon_crossmark, - "Reject if you're", - "not sure", - }); - -// Step with approve button -UX_STEP_CB(ux_display_approve_step, - pb, - continue_after_approval(true), - { - &C_icon_validate_14, - "Approve", - }); - -// Step with continue button -UX_STEP_CB(ux_display_continue_step, - pb, - continue_after_approval(true), - { - &C_icon_validate_14, - "Continue", - }); - -// Step with reject button -UX_STEP_CB(ux_display_reject_step, - pb, - continue_after_approval(false), - { - &C_icon_crossmark, - "Reject", - }); - -/* - STATEFUL STEPS - These can only be used in the context of specific flows, as they access a common shared space - for strings. -*/ - -// PATH/PUBKEY or PATH/ADDRESS - -// Step with title/text for BIP32 path -UX_STEP_NOCB(ux_display_path_step, - bnnn_paging, - { - .title = "Path", - .text = g_ui_state.path_and_pubkey.bip32_path_str, - }); - -// Step with title/text for pubkey -UX_STEP_NOCB(ux_display_pubkey_step, - bnnn_paging, - { - .title = "Public key", - .text = g_ui_state.path_and_pubkey.pubkey, - }); - -// Step with title/text for address -UX_STEP_NOCB(ux_display_address_step, - bnnn_paging, - { - .title = "Address", - .text = g_ui_state.path_and_address.address, - }); - -// Step with icon and text with name of a wallet being registered -UX_STEP_NOCB(ux_display_wallet_header_name_step, - pnn, - { - &C_icon_wallet, - "Register wallet", - g_ui_state.wallet.wallet_name, - }); - -// Step with description of a policy wallet -UX_STEP_NOCB(ux_display_wallet_policy_map_type_step, - bnnn_paging, - { - .title = "Policy map:", // TODO: simplify for known multisig policies - .text = g_ui_state.wallet.policy_map, - }); - -// Step with index and xpub of a cosigner of a policy_map wallet -UX_STEP_NOCB(ux_display_wallet_policy_map_cosigner_pubkey_step, - bnnn_paging, - { - .title = g_ui_state.cosigner_pubkey_and_index.signer_index, - .text = g_ui_state.cosigner_pubkey_and_index.pubkey, - }); - -// Step with icon and text with name of a wallet being registered -UX_STEP_NOCB(ux_display_receive_in_wallet_step, - pnn, - { - &C_icon_wallet, - "Receive in:", - g_ui_state.wallet.wallet_name, - }); - -// Step with title/text for address, used when showing a wallet receive address -UX_STEP_NOCB(ux_display_wallet_address_step, - bnnn_paging, - { - .title = "Address", - .text = g_ui_state.wallet.address, - }); - -// Step with icon and text with name of a wallet to spend from -UX_STEP_NOCB(ux_display_spend_from_wallet_step, - pnn, - { - &C_icon_wallet, - "Spend from:", - g_ui_state.wallet.wallet_name, - }); - -// Step with warning icon and text explaining that there are external inputs -UX_STEP_NOCB(ux_display_warning_external_inputs_step, - pnn, - { - &C_icon_warning, - "There are", - "external inputs", - }); - -// Step with eye icon and "Review" and the output index -UX_STEP_NOCB(ux_review_step, - pnn, - { - &C_icon_eye, - "Review", - g_ui_state.validate_output.index, - }); - -// Step with "Amount" and an output amount -UX_STEP_NOCB(ux_display_reissuance_token_step, - bnnn_paging, - { - .title = "Reissuance token", - .text = g_ui_state.validate_output.token_ticker, - }); - -// Step with "Amount" and an output amount -UX_STEP_NOCB(ux_validate_amount_step, - bnnn_paging, - { - .title = "Amount", - .text = g_ui_state.validate_output.amount, - }); - -// Step with "Address" and a paginated address -UX_STEP_NOCB(ux_validate_address_step, - bnnn_paging, - { - .title = "Address", - .text = g_ui_state.validate_output.address_or_description, - }); - -UX_STEP_NOCB(ux_confirm_transaction_step, - pnn, - { - &C_icon_eye, - "Confirm", - g_ui_state.validate_transaction.transaction_type, - }); - -UX_STEP_NOCB(ux_confirm_transaction_fees_step, - bnnn_paging, - { - .title = "Fees", - .text = g_ui_state.validate_transaction.fee, - }); -UX_STEP_CB(ux_accept_and_send_step, - pbb, - continue_after_approval(true), - {&C_icon_validate_14, "Accept", "and send"}); - -////////////////////////////////////////////////////////////////////// -UX_STEP_NOCB(ux_sign_message_step, - pnn, - { - &C_icon_certificate, - "Sign", - "message", - }); - -UX_STEP_NOCB(ux_message_sign_display_path_step, - bnnn_paging, - { - .title = "Path", - .text = g_ui_state.path_and_hash.bip32_path_str, - }); - -UX_STEP_NOCB(ux_message_hash_step, - bnnn_paging, - { - .title = "Message hash", - .text = g_ui_state.path_and_hash.hash_hex, - }); - -UX_STEP_CB(ux_sign_message_accept_new, - pbb, - continue_after_approval(true), - {&C_icon_validate_14, "Sign", "message"}); - -////////////////////////////////////////////////////////////////////// -#ifdef HAVE_LIQUID -// Step with warning icon and text explaining that asset is unknown -UX_STEP_NOCB(ux_display_warning_unknown_asset_step, - pnn, - { - &C_icon_warning, - "The asset", - "is unknown", - }); - -UX_STEP_NOCB(ux_asset_tag_step, - bnnn_paging, - { - .title = "Asset tag", - .text = g_ui_state.asset.tag_hex, - }); - -UX_STEP_NOCB(ux_output_asset_tag_step, - bnnn_paging, - { - .title = "Asset tag", - .text = g_ui_state.validate_output.tag_hex, - }); - -// Step with icon and text with name of a wallet being registered -UX_STEP_NOCB(ux_va_confirm_asset_step, - pnn, - { - &C_icon_eye, - "Confirm asset", - g_ui_state.validate_asset.ticker, - }); - -UX_STEP_NOCB(ux_va_review_asset_tag_step, - bnnn_paging, - { - .title = "Asset tag", - .text = g_ui_state.validate_asset.tag_hex, - }); - -UX_STEP_NOCB(ux_va_review_asset_name_step, - bnnn_paging, - { - .title = "Asset name", - .text = g_ui_state.validate_asset.name, - }); - -UX_STEP_NOCB(ux_va_review_asset_domain_step, - bnnn_paging, - { - .title = "Asset domain", - .text = g_ui_state.validate_asset.domain, - }); -#endif // HAVE_LIQUID - -// FLOW to display BIP32 path and a message hash to sign: -// #1 screen: certificate icon + "Sign message" -// #2 screen: display BIP32 Path -// #3 screen: display message hash -// #4 screen: "Sign message" and approve button -// #5 screen: reject button -UX_FLOW(ux_sign_message_flow, - &ux_sign_message_step, - &ux_message_sign_display_path_step, - &ux_message_hash_step, - &ux_sign_message_accept_new, - &ux_display_reject_step); - -// FLOW to display BIP32 path and pubkey: -// #1 screen: eye icon + "Confirm Pubkey" -// #2 screen: display BIP32 Path -// #3 screen: display pubkey -// #4 screen: approve button -// #5 screen: reject button -UX_FLOW(ux_display_pubkey_flow, - &ux_display_confirm_pubkey_step, - &ux_display_path_step, - &ux_display_pubkey_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display BIP32 path and pubkey, for a non-standard path: -// #1 screen: warning icon + "The derivation path is unusual" -// #2 screen: crossmark icon + "Reject if not sure" (user can reject here) -// #3 screen: eye icon + "Confirm Pubkey" -// #4 screen: display BIP32 Path -// #5 screen: display pubkey -// #6 screen: approve button -// #7 screen: reject button -UX_FLOW(ux_display_pubkey_suspicious_flow, - &ux_display_unusual_derivation_path_step, - &ux_display_confirm_pubkey_step, - &ux_display_path_step, - &ux_display_reject_if_not_sure_step, - &ux_display_pubkey_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display a receive address, for a standard path: -// #1 screen: eye icon + "Confirm Address" -// #2 screen: display address -// #3 screen: approve button -// #4 screen: reject button -UX_FLOW(ux_display_address_flow, - &ux_display_confirm_address_step, - &ux_display_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display a receive address, for a non-standard path: -// #1 screen: warning icon + "The derivation path is unusual" -// #2 screen: display BIP32 Path -// #3 screen: crossmark icon + "Reject if not sure" (user can reject here) -// #4 screen: eye icon + "Confirm Address" -// #5 screen: display address -// #6 screen: approve button -// #7 screen: reject button -UX_FLOW(ux_display_address_suspicious_flow, - &ux_display_unusual_derivation_path_step, - &ux_display_path_step, - &ux_display_reject_if_not_sure_step, - &ux_display_confirm_address_step, - &ux_display_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to warn the user if a change output has an unusual derivation path -// (e.g. account index or address index too large): -// #1 screen: warning icon + "The derivation path is unusual" -// #2 screen: display BIP32 Path -// #3 screen: crossmark icon + "Reject if not sure" (user can reject here) -// #4 screen: approve button -// #5 screen: reject button -UX_FLOW(ux_display_unusual_derivation_path_flow, - &ux_display_unusual_derivation_path_step, - &ux_display_path_step, - &ux_display_reject_if_not_sure_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display the header of a policy map wallet: -// #1 screen: eye icon + "Register wallet" and the wallet name -// #2 screen: display policy map (paginated) -// #3 screen: approve button -// #4 screen: reject button -UX_FLOW(ux_display_policy_map_header_flow, - &ux_display_wallet_header_name_step, - &ux_display_wallet_policy_map_type_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display the header of a policy_map wallet: -// #1 screen: Cosigner index and pubkey (paginated) -// #2 screen: approve button -// #3 screen: reject button -UX_FLOW(ux_display_policy_map_cosigner_pubkey_flow, - &ux_display_wallet_policy_map_cosigner_pubkey_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display the name and an address of a registered wallet: -// #1 screen: wallet name -// #2 screen: wallet address (paginated) -// #3 screen: approve button -// #4 screen: reject button -UX_FLOW(ux_display_wallet_name_address_flow, - &ux_display_receive_in_wallet_step, - &ux_display_wallet_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display an address of a canonical wallet: -// #1 screen: wallet address (paginated) -// #2 screen: approve button -// #3 screen: reject button -UX_FLOW(ux_display_canonical_wallet_address_flow, - &ux_display_wallet_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to display a registered wallet and authorize spending: -// #1 screen: wallet name -// #2 screen: approve button -// #3 screen: reject button -UX_FLOW(ux_display_wallet_for_spending_flow, - &ux_display_spend_from_wallet_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to warn about external inputs -// #1 screen: warning icon + "There are external inputs" -// #2 screen: crossmark icon + "Reject if not sure" (user can reject here) -// #3 screen: "continue" button -UX_FLOW(ux_display_warning_external_inputs_flow, - &ux_display_warning_external_inputs_step, - &ux_display_reject_if_not_sure_step, - &ux_display_continue_step); - -// FLOW to validate a single output -// #1 screen: eye icon + "Review" + index of output to validate -// #2 screen: output amount -// #3 screen: output address (paginated) -// #4 screen: approve button -// #5 screen: reject button -UX_FLOW(ux_display_output_address_amount_flow, - &ux_review_step, - &ux_validate_amount_step, - &ux_validate_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to validate a single output -// #1 screen: eye icon + "Review" + index of output to validate -// #2 screen: reissuance token notice and asset ticker -// #3 screen: output amount -// #4 screen: output address (paginated) -// #5 screen: approve button -// #6 screen: reject button -UX_FLOW(ux_display_output_address_token_amount_flow, - &ux_review_step, - &ux_display_reissuance_token_step, - &ux_validate_amount_step, - &ux_validate_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// Finalize see the transaction fees and finally accept signing -// #1 screen: eye icon + "Confirm Transaction" -// #2 screen: fee amount -// #3 screen: "Accept and send", with approve button -// #4 screen: reject button -UX_FLOW(ux_accept_transaction_flow, - &ux_confirm_transaction_step, - &ux_confirm_transaction_fees_step, - &ux_accept_and_send_step, - &ux_display_reject_step); - -#ifdef HAVE_LIQUID -// FLOW to warn about unknown asset -// #1 screen: warning icon + "The asset is unknown" -// #2 screen: asset tag -// #3 screen: crossmark icon + "Reject if not sure" (user can reject here) -// #4 screen: "continue" button -UX_FLOW(ux_display_warning_unknown_asset_flow, - &ux_display_warning_unknown_asset_step, - &ux_asset_tag_step, - &ux_display_reject_if_not_sure_step, - &ux_display_continue_step); - -// FLOW to validate a single output -// #1 screen: eye icon + "Review" + index of output to validate -// #2 screen: output amount -// #3 screen: asset tag -// #4 screen: output address (paginated) -// #5 screen: approve button -// #6 screen: reject button -UX_FLOW(ux_display_output_address_amount_asset_flow, - &ux_review_step, - &ux_validate_amount_step, - &ux_output_asset_tag_step, - &ux_validate_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to validate a single output -// #1 screen: eye icon + "Review" + index of output to validate -// #2 screen: reissuance token notice and asset ticker -// #3 screen: output amount -// #4 screen: asset tag -// #5 screen: output address (paginated) -// #6 screen: approve button -// #7 screen: reject button -UX_FLOW(ux_display_output_address_token_amount_asset_flow, - &ux_review_step, - &ux_display_reissuance_token_step, - &ux_validate_amount_step, - &ux_output_asset_tag_step, - &ux_validate_address_step, - &ux_display_approve_step, - &ux_display_reject_step); - -// FLOW to validate an asset -// #1 screen: eye icon + "Confirm asset" + asset ticker -// #2 screen: asset tag (paginated) -// #3 screen: asset name (paginated) -// #4 screen: asset domain (paginated) -// #5 screen: approve button -// #6 screen: reject button -UX_FLOW(ux_validate_asset_flow, - &ux_va_confirm_asset_step, - &ux_va_review_asset_tag_step, - &ux_va_review_asset_name_step, - &ux_va_review_asset_domain_step, - &ux_display_approve_step, - &ux_display_reject_step); -#endif - -void ui_display_pubkey(dispatcher_context_t *context, - const char *bip32_path_str, - bool is_path_suspicious, - const char *pubkey, - command_processor_t on_success) { - context->pause(); +void set_ux_flow_response(bool approved) { + g_ux_flow_ended = true; + g_ux_flow_response = approved; +} - ui_path_and_pubkey_state_t *state = (ui_path_and_pubkey_state_t *) &g_ui_state; +uint8_t get_streaming_index(void) { + return g_current_streaming_index; +} - strlcpy(state->bip32_path_str, bip32_path_str, sizeof(state->bip32_path_str)); - strlcpy(state->pubkey, pubkey, sizeof(state->pubkey)); +void reset_streaming_index(void) { + PRINTF("Reset streaming index\n"); + g_current_streaming_index = 0; +} - g_next_processor = on_success; +void increase_streaming_index(void) { + PRINTF("Increase streaming index\n"); + g_current_streaming_index += 1; +} - if (!is_path_suspicious) { - ux_flow_init(0, ux_display_pubkey_flow, NULL); - } else { - ux_flow_init(0, ux_display_pubkey_suspicious_flow, NULL); +void decrease_streaming_index(void) { + PRINTF("Decrease streaming index\n"); + if (g_current_streaming_index > 0) { + g_current_streaming_index -= 1; } } -void ui_display_message_hash(dispatcher_context_t *context, - const char *bip32_path_str, - const char *message_hash, - command_processor_t on_success) { - context->pause(); +// Process UI events until the current flow terminates; does not handle any APDU exchange +// This method also sets the UI state as "dirty" according to the input parameter +// so that the dispatcher refreshes resets the UI at the end of the command handler. +// Returns true/false depending if the user accepted in the corresponding UX flow. +static bool io_ui_process(dispatcher_context_t *context, bool set_dirty) { + G_was_processing_screen_shown = false; - ui_path_and_hash_state_t *state = (ui_path_and_hash_state_t *) &g_ui_state; + g_ux_flow_ended = false; - strlcpy(state->bip32_path_str, bip32_path_str, sizeof(state->bip32_path_str)); - strlcpy(state->hash_hex, message_hash, sizeof(state->hash_hex)); + if (set_dirty) { + context->set_ui_dirty(); + } - g_next_processor = on_success; + // We are not waiting for the client's input, nor we are doing computations on the device + io_clear_processing_timeout(); - ux_flow_init(0, ux_sign_message_flow, NULL); -} + io_seproxyhal_general_status(); + do { + io_seproxyhal_spi_recv(G_io_seproxyhal_spi_buffer, sizeof(G_io_seproxyhal_spi_buffer), 0); + io_seproxyhal_handle_event(); + io_seproxyhal_general_status(); + } while (io_seproxyhal_spi_is_status_sent() && !g_ux_flow_ended); -void ui_display_address(dispatcher_context_t *context, - const char *address, - bool is_path_suspicious, - const char *path_str, - command_processor_t on_success) { - context->pause(); + // We're back at work, we want to show the "Processing..." screen when appropriate + io_start_processing_timeout(); - ui_path_and_address_state_t *state = (ui_path_and_address_state_t *) &g_ui_state; + return g_ux_flow_response; +} - strlcpy(state->address, address, sizeof(state->address)); +bool ui_display_pubkey(dispatcher_context_t *context, + const char *bip32_path_str, + bool is_path_suspicious, + const char *pubkey) { + ui_path_and_pubkey_state_t *state = (ui_path_and_pubkey_state_t *) &g_ui_state; - g_next_processor = on_success; + strncpy(state->bip32_path_str, bip32_path_str, sizeof(state->bip32_path_str)); + strncpy(state->pubkey, pubkey, sizeof(state->pubkey)); if (!is_path_suspicious) { - ux_flow_init(0, ux_display_address_flow, NULL); + ui_display_pubkey_flow(); } else { - strlcpy(state->bip32_path_str, path_str, sizeof(state->bip32_path_str)); - ux_flow_init(0, ux_display_address_suspicious_flow, NULL); + ui_display_pubkey_suspicious_flow(); } + + return io_ui_process(context, true); +} + +bool ui_display_path_and_message_content(dispatcher_context_t *context, + const char *path_str, + const char *message_content, + uint8_t pageCount) { + ui_path_and_message_state_t *state = (ui_path_and_message_state_t *) &g_ui_state; + strncpy(state->bip32_path_str, path_str, sizeof(state->bip32_path_str)); + strncpy(state->message, message_content, sizeof(state->message)); + + ui_sign_message_content_flow(pageCount); + + return io_ui_process(context, true); } -void ui_display_wallet_header(dispatcher_context_t *context, - const policy_map_wallet_header_t *wallet_header, - command_processor_t on_success) { - context->pause(); +bool ui_display_message_path_hash_and_confirm(dispatcher_context_t *context, + const char *path_str, + const char *message_hash) { + ui_path_and_message_state_t *state = (ui_path_and_message_state_t *) &g_ui_state; + strncpy(state->bip32_path_str, path_str, sizeof(state->bip32_path_str)); + strncpy(state->message, message_hash, sizeof(state->message)); + ui_sign_message_path_hash_and_confirm_flow(); + + return io_ui_process(context, true); +} + +bool ui_display_message_confirm(dispatcher_context_t *context) { + (void) context; + ui_sign_message_confirm_flow(); + + return io_ui_process(context, true); +} + +bool ui_display_register_wallet(dispatcher_context_t *context, + const policy_map_wallet_header_t *wallet_header, + const char *policy_descriptor) { ui_wallet_state_t *state = (ui_wallet_state_t *) &g_ui_state; - strlcpy(state->wallet_name, wallet_header->name, sizeof(wallet_header->name)); - strlcpy(state->policy_map, wallet_header->policy_map, sizeof(wallet_header->policy_map)); + strncpy(state->wallet_name, wallet_header->name, sizeof(state->wallet_name)); + state->wallet_name[wallet_header->name_len] = 0; + strncpy(state->descriptor_template, policy_descriptor, sizeof(state->descriptor_template)); + state->descriptor_template[wallet_header->descriptor_template_len] = 0; - g_next_processor = on_success; + ui_display_register_wallet_flow(); - ux_flow_init(0, ux_display_policy_map_header_flow, NULL); + return io_ui_process(context, true); } -void ui_display_policy_map_cosigner_pubkey(dispatcher_context_t *context, +bool ui_display_policy_map_cosigner_pubkey(dispatcher_context_t *context, const char *pubkey, uint8_t cosigner_index, uint8_t n_keys, - bool is_internal, - command_processor_t on_success) { + key_type_e key_type) { (void) (n_keys); - context->pause(); - ui_cosigner_pubkey_and_index_state_t *state = (ui_cosigner_pubkey_and_index_state_t *) &g_ui_state; - strlcpy(state->pubkey, pubkey, sizeof(state->pubkey)); + strncpy(state->pubkey, pubkey, sizeof(state->pubkey)); - if (is_internal) { + if (key_type == PUBKEY_TYPE_INTERNAL) { + snprintf(state->signer_index, sizeof(state->signer_index), "Key @%u, ours", cosigner_index); + } else if (key_type == PUBKEY_TYPE_EXTERNAL) { snprintf(state->signer_index, sizeof(state->signer_index), - "Key @%u ", - cosigner_index + 1); - } else { + "Key @%u, theirs", + cosigner_index); + } else if (key_type == PUBKEY_TYPE_UNSPENDABLE) { snprintf(state->signer_index, sizeof(state->signer_index), - "Key @%u ", - cosigner_index + 1); + "Key @%u, dummy", + cosigner_index); + } else { + LEDGER_ASSERT(false, "Unreachable code"); } + ui_display_policy_map_cosigner_pubkey_flow(); - g_next_processor = on_success; - - ux_flow_init(0, ux_display_policy_map_cosigner_pubkey_flow, NULL); + return io_ui_process(context, true); } -void ui_display_wallet_address(dispatcher_context_t *context, +bool ui_display_wallet_address(dispatcher_context_t *context, const char *wallet_name, - const char *address, - command_processor_t on_success) { - context->pause(); - + const char *address) { ui_wallet_state_t *state = (ui_wallet_state_t *) &g_ui_state; - strlcpy(state->address, address, sizeof(state->address)); - g_next_processor = on_success; + strncpy(state->address, address, sizeof(state->address)); if (wallet_name == NULL) { - ux_flow_init(0, ux_display_canonical_wallet_address_flow, NULL); + ui_display_default_wallet_address_flow(); } else { - strlcpy(state->wallet_name, wallet_name, sizeof(state->wallet_name)); - ux_flow_init(0, ux_display_wallet_name_address_flow, NULL); + strncpy(state->wallet_name, wallet_name, sizeof(state->wallet_name)); + ui_display_receive_in_wallet_flow(); } -} - -void ui_display_unusual_path(dispatcher_context_t *context, - const char *bip32_path_str, - command_processor_t on_success) { - context->pause(); - - ui_path_state_t *state = (ui_path_state_t *) &g_ui_state; - - g_next_processor = on_success; - strlcpy(state->bip32_path_str, bip32_path_str, sizeof(state->bip32_path_str)); - ux_flow_init(0, ux_display_unusual_derivation_path_flow, NULL); + return io_ui_process(context, true); } -void ui_authorize_wallet_spend(dispatcher_context_t *context, - const char *wallet_name, - command_processor_t on_success) { - context->pause(); - +bool ui_authorize_wallet_spend(dispatcher_context_t *context, const char *wallet_name) { ui_wallet_state_t *state = (ui_wallet_state_t *) &g_ui_state; - strlcpy(state->wallet_name, wallet_name, sizeof(state->wallet_name)); + strncpy(state->wallet_name, wallet_name, sizeof(state->wallet_name)); + ui_display_spend_from_wallet_flow(); - g_next_processor = on_success; + return io_ui_process(context, true); +} - ux_flow_init(0, ux_display_wallet_for_spending_flow, NULL); +bool ui_warn_external_inputs(dispatcher_context_t *context) { + ui_display_warning_external_inputs_flow(); + return io_ui_process(context, true); } -void ui_warn_external_inputs(dispatcher_context_t *context, command_processor_t on_success) { - (void) (context); +bool ui_warn_unverified_segwit_inputs(dispatcher_context_t *context) { + ui_display_unverified_segwit_inputs_flows(); + return io_ui_process(context, true); +} - g_next_processor = on_success; +bool ui_warn_nondefault_sighash(dispatcher_context_t *context) { + ui_display_nondefault_sighash_flow(); + return io_ui_process(context, true); +} - ux_flow_init(0, ux_display_warning_external_inputs_flow, NULL); +bool ui_transaction_prompt(dispatcher_context_t *context, const int external_outputs_total_count) { + ui_display_transaction_prompt(external_outputs_total_count); + return io_ui_process(context, true); } -void ui_validate_output(dispatcher_context_t *context, +bool ui_validate_output(dispatcher_context_t *context, int index, + int total_count, const char *address_or_description, const char *coin_name, - uint64_t amount, - uint8_t decimals, -#ifdef HAVE_LIQUID - const uint8_t asset_tag[static 32], - bool display_asset_tag, - bool asset_is_reissuance_token, -#endif - command_processor_t on_success) { - context->pause(); - + uint64_t amount) { ui_validate_output_state_t *state = (ui_validate_output_state_t *) &g_ui_state; - snprintf(state->index, sizeof(state->index), "output #%d", index); - strlcpy(state->address_or_description, + strncpy(state->address_or_description, address_or_description, sizeof(state->address_or_description)); + format_sats_amount(coin_name, amount, state->amount); - g_next_processor = on_success; - -#ifdef HAVE_LIQUID - if (asset_is_reissuance_token) { - format_amount("token", amount, decimals, state->amount); - snprintf(state->token_ticker, sizeof(state->token_ticker), "of asset %s", coin_name); + if (total_count == 1) { + ui_display_output_address_amount_no_index_flow(index); } else { - format_amount(coin_name, amount, decimals, state->amount); + ui_display_output_address_amount_flow(index); } - if (display_asset_tag) { - liquid_format_asset_tag(asset_tag, state->tag_hex); - ux_flow_init(0, asset_is_reissuance_token ? - ux_display_output_address_token_amount_asset_flow : - ux_display_output_address_amount_asset_flow, - NULL); - } else { - ux_flow_init(0, asset_is_reissuance_token ? - ux_display_output_address_token_amount_flow : - ux_display_output_address_amount_flow, - NULL); - } -#else - format_amount(coin_name, amount, decimals, state->amount); - ux_flow_init(0, ux_display_output_address_amount_flow, NULL); -#endif + return io_ui_process(context, true); +} + +bool ui_warn_high_fee(dispatcher_context_t *context) { + ui_warn_high_fee_flow(); + + return io_ui_process(context, true); } -void ui_validate_transaction(dispatcher_context_t *context, +bool ui_validate_transaction(dispatcher_context_t *context, const char *coin_name, uint64_t fee, - uint8_t decimals, - const char *transaction_type, - command_processor_t on_success) { - context->pause(); - + bool is_self_transfer) { ui_validate_transaction_state_t *state = (ui_validate_transaction_state_t *) &g_ui_state; - g_next_processor = on_success; + format_sats_amount(coin_name, fee, state->fee); - format_amount(coin_name, fee, decimals, state->fee); + ui_accept_transaction_flow(is_self_transfer); - strlcpy(state->transaction_type, - (NULL != transaction_type) ? transaction_type : "transaction", - sizeof(state->transaction_type)); + return io_ui_process(context, true); +} - ux_flow_init(0, ux_accept_transaction_flow, NULL); +#ifdef HAVE_BAGL +bool ui_post_processing_confirm_wallet_registration(dispatcher_context_t *context, bool success) { + (void) context; + (void) success; + return true; } -#ifdef HAVE_LIQUID -void ui_warn_unknown_asset(dispatcher_context_t *context, - const uint8_t asset_tag[static 32], - const command_processor_t on_success) { - context->pause(); +bool ui_post_processing_confirm_wallet_spend(dispatcher_context_t *context, bool success) { + (void) context; + (void) success; + return true; +} - ui_asset_state_t *state = (ui_asset_state_t *) &g_ui_state; +bool ui_post_processing_confirm_transaction(dispatcher_context_t *context, bool success) { + (void) context; + (void) success; + return true; +} + +bool ui_post_processing_confirm_message(dispatcher_context_t *context, bool success) { + (void) context; + (void) success; + return true; +} - liquid_format_asset_tag(asset_tag, state->tag_hex); +void ui_pre_processing_message(void) { + return; +} +#endif // HAVE_BAGL - g_next_processor = on_success; +#ifdef HAVE_NBGL +bool ui_post_processing_confirm_wallet_registration(dispatcher_context_t *context, bool success) { + (void) context; + ui_display_post_processing_confirm_wallet_registation(success); - ux_flow_init(0, ux_display_warning_unknown_asset_flow, NULL); + return true; } -void ui_validate_asset(dispatcher_context_t *context, - const uint8_t asset_tag[static 32], - const asset_info_ext_t *asset_info, - const command_processor_t on_success) { +bool ui_post_processing_confirm_wallet_spend(dispatcher_context_t *context, bool success) { + ui_display_post_processing_confirm_wallet_spend(success); - context->pause(); + return io_ui_process(context, success); +} - ui_validate_asset_state_t *state = (ui_validate_asset_state_t *) &g_ui_state; +bool ui_post_processing_confirm_transaction(dispatcher_context_t *context, bool success) { + ui_display_post_processing_confirm_transaction(success); - liquid_format_asset_tag(asset_tag, state->tag_hex); - strlcpy(state->ticker, asset_info->info.ticker, sizeof(state->ticker)); - strlcpy(state->name, asset_info->name, sizeof(state->name)); - strlcpy(state->domain, asset_info->domain, sizeof(state->domain)); + return io_ui_process(context, success); +} - g_next_processor = on_success; +bool ui_post_processing_confirm_message(dispatcher_context_t *context, bool success) { + (void) context; + ui_display_post_processing_confirm_message(success); - ux_flow_init(0, ux_validate_asset_flow, NULL); + return true; } -#endif +void ui_pre_processing_message(void) { + ui_set_display_prompt(); +} +#endif // HAVE_NBGL diff --git a/src/ui/display.h b/src/ui/display.h index 64faeb009..7cb2176c7 100644 --- a/src/ui/display.h +++ b/src/ui/display.h @@ -4,7 +4,82 @@ #include "../boilerplate/dispatcher.h" #include "../common/wallet.h" -#include "../liquid/liquid_assets.h" +#include "./display.h" +#include "./display_utils.h" +#include "../constants.h" +#include "../globals.h" +#include "../boilerplate/io.h" +#include "../boilerplate/sw.h" +#include "../common/bip32.h" +#include "../common/format.h" +#include "../common/script.h" +#include "../constants.h" + +#define MESSAGE_CHUNK_SIZE 64 // Protocol specific +#define MESSAGE_CHUNK_PER_DISPLAY 2 // This could be changed depending on screen sizes +#define MESSAGE_MAX_DISPLAY_SIZE \ + (MESSAGE_CHUNK_SIZE * MESSAGE_CHUNK_PER_DISPLAY + 2 * sizeof("...") - 1) + +// TODO: hard to keep track of what globals are used in the same flows +// (especially since the same flow step can be shared in different flows) + +typedef struct { + char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; +} ui_path_state_t; + +typedef struct { + char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; + char pubkey[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; +} ui_path_and_pubkey_state_t; + +typedef struct { + char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; + char address[MAX_ADDRESS_LENGTH_STR + 1]; +} ui_path_and_address_state_t; + +typedef struct { + char bip32_path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1]; + char message[MESSAGE_MAX_DISPLAY_SIZE]; +} ui_path_and_message_state_t; + +typedef struct { + char wallet_name[MAX_WALLET_NAME_LENGTH + 1]; + + // no flows show together both a policy map and an address, therefore we share memory + union { + char descriptor_template[MAX_DESCRIPTOR_TEMPLATE_LENGTH + 1]; + char address[MAX_ADDRESS_LENGTH_STR + 1]; + }; +} ui_wallet_state_t; + +typedef struct { + char pubkey[MAX_POLICY_KEY_INFO_LEN + 1]; + char signer_index[sizeof("Key @999 ")]; +} ui_cosigner_pubkey_and_index_state_t; + +typedef struct { + char index[sizeof("output #999")]; + char address_or_description[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE)]; + char amount[MAX_AMOUNT_LENGTH + 1]; +} ui_validate_output_state_t; + +typedef struct { + char fee[MAX_AMOUNT_LENGTH + 1]; +} ui_validate_transaction_state_t; + +/** + * Union of all the states for each of the UI screens, in order to save memory. + */ +typedef union { + ui_path_and_pubkey_state_t path_and_pubkey; + ui_path_and_address_state_t path_and_address; + ui_path_and_message_state_t path_and_message; + ui_wallet_state_t wallet; + ui_cosigner_pubkey_and_index_state_t cosigner_pubkey_and_index; + ui_validate_output_state_t validate_output; + ui_validate_transaction_state_t validate_transaction; +} ui_state_t; +extern ui_state_t g_ui_state; /** * Callback to reuse action with approve/reject in step FLOW. @@ -14,80 +89,134 @@ typedef void (*action_validate_cb)(dispatcher_context_t *dispatcher_context, boo /** * Display the derivation path and pubkey, and asks the confirmation to export. * - * @return 0 if success, negative integer otherwise. - * TODO: document params + * TODO: docs */ -void ui_display_pubkey(dispatcher_context_t *context, +bool ui_display_pubkey(dispatcher_context_t *context, const char *bip32_path_str, bool is_path_suspicious, - const char *pubkey, - command_processor_t on_success); + const char *pubkey); + +bool ui_display_path_and_message_content(dispatcher_context_t *context, + const char *path_str, + const char *message_content, + uint8_t pageCount); + +bool ui_display_message_path_hash_and_confirm(dispatcher_context_t *context, + const char *path_str, + const char *message_hash); -// TODO: docs -void ui_display_message_hash(dispatcher_context_t *context, - const char *bip32_path_str, - const char *message_hash, - command_processor_t on_success); +bool ui_display_message_confirm(dispatcher_context_t *context); -void ui_display_address(dispatcher_context_t *dispatcher_context, +bool ui_display_address(dispatcher_context_t *dispatcher_context, const char *address, bool is_path_suspicious, - const char *bip32_path_str, - command_processor_t on_success); + const char *bip32_path_str); -void ui_display_wallet_header(dispatcher_context_t *context, - const policy_map_wallet_header_t *wallet_header, - command_processor_t on_success); +bool ui_display_register_wallet(dispatcher_context_t *context, + const policy_map_wallet_header_t *wallet_header, + const char *policy_descriptor); -void ui_display_policy_map_cosigner_pubkey(dispatcher_context_t *dispatcher_context, +typedef enum { + PUBKEY_TYPE_INTERNAL = 0, // a key controlled by the wallet policy + PUBKEY_TYPE_EXTERNAL = 1, // a key not controlled by the wallet policy + PUBKEY_TYPE_UNSPENDABLE = 2 // the provably unspendable public key defined in BIP-341 +} key_type_e; + +bool ui_display_policy_map_cosigner_pubkey(dispatcher_context_t *dispatcher_context, const char *pubkey, uint8_t cosigner_index, uint8_t n_keys, - bool is_internal, - command_processor_t on_success); + key_type_e key_type); -void ui_display_wallet_address(dispatcher_context_t *context, +bool ui_display_wallet_address(dispatcher_context_t *context, const char *wallet_name, - const char *address, - command_processor_t on_success); + const char *address); -void ui_display_unusual_path(dispatcher_context_t *context, - const char *bip32_path_str, - command_processor_t on_success); +bool ui_display_unusual_path(dispatcher_context_t *context, const char *bip32_path_str); -void ui_authorize_wallet_spend(dispatcher_context_t *context, - const char *wallet_name, - command_processor_t on_success); +bool ui_authorize_wallet_spend(dispatcher_context_t *context, const char *wallet_name); + +bool ui_warn_external_inputs(dispatcher_context_t *context); -void ui_warn_external_inputs(dispatcher_context_t *context, command_processor_t on_success); +bool ui_warn_unverified_segwit_inputs(dispatcher_context_t *context); -void ui_validate_output(dispatcher_context_t *context, +bool ui_warn_nondefault_sighash(dispatcher_context_t *context); + +bool ui_validate_output(dispatcher_context_t *context, int index, + int total_count, const char *address_or_description, const char *coin_name, - uint64_t amount, - uint8_t decimals, -#ifdef HAVE_LIQUID - const uint8_t asset_tag[static 32], - bool display_asset_tag, - bool asset_is_reissuance_token, -#endif - command_processor_t on_success); + uint64_t amount); -void ui_validate_transaction(dispatcher_context_t *context, +bool ui_warn_high_fee(dispatcher_context_t *context); + +bool ui_validate_transaction(dispatcher_context_t *context, const char *coin_name, uint64_t fee, - uint8_t decimals, - const char *transaction_type, - command_processor_t on_success); - -#ifdef HAVE_LIQUID -void ui_warn_unknown_asset(dispatcher_context_t *context, - const uint8_t asset_tag[static 32], - const command_processor_t on_success); - -void ui_validate_asset(dispatcher_context_t *context, - const uint8_t asset_tag[static 32], - const asset_info_ext_t *asset_info, - const command_processor_t on_success); + bool is_self_transfer); + +void set_ux_flow_response(bool approved); + +void ui_display_pubkey_flow(void); + +void ui_display_pubkey_suspicious_flow(void); + +void ui_sign_message_path_hash_and_confirm_flow(void); + +void ui_sign_message_content_flow(uint8_t pageCount); + +void ui_sign_message_confirm_flow(void); + +void ui_display_register_wallet_flow(void); + +void ui_display_policy_map_cosigner_pubkey_flow(void); + +void ui_display_receive_in_wallet_flow(void); + +void ui_display_default_wallet_address_flow(void); + +void ui_display_spend_from_wallet_flow(void); + +void ui_display_warning_external_inputs_flow(void); + +void ui_display_unverified_segwit_inputs_flows(void); + +void ui_display_nondefault_sighash_flow(void); + +void ui_display_output_address_amount_flow(int index); + +void ui_display_output_address_amount_no_index_flow(int index); + +void ui_warn_high_fee_flow(void); + +void ui_accept_transaction_flow(bool is_self_transfer); + +void ui_display_transaction_prompt(const int external_outputs_total_count); + +bool ui_post_processing_confirm_wallet_registration(dispatcher_context_t *context, bool success); + +bool ui_post_processing_confirm_wallet_spend(dispatcher_context_t *context, bool success); + +bool ui_post_processing_confirm_transaction(dispatcher_context_t *context, bool success); + +bool ui_post_processing_confirm_message(dispatcher_context_t *context, bool success); + +void ui_pre_processing_message(void); + +#ifdef HAVE_NBGL +bool ui_transaction_prompt(dispatcher_context_t *context, const int external_outputs_total_count); +void ui_display_post_processing_confirm_message(bool success); +void ui_display_post_processing_confirm_wallet_registation(bool success); +void ui_display_post_processing_confirm_transaction(bool success); +void ui_display_post_processing_confirm_wallet_spend(bool success); +void ui_set_display_prompt(void); +#else +#define ux_layout_custom_params_t ux_layout_paging_params_t +void ux_layout_custom_init(unsigned int stack_slot); #endif + +uint8_t get_streaming_index(void); +void reset_streaming_index(void); +void increase_streaming_index(void); +void decrease_streaming_index(void); diff --git a/src/ui/display_bagl.c b/src/ui/display_bagl.c new file mode 100644 index 000000000..f6f03a087 --- /dev/null +++ b/src/ui/display_bagl.c @@ -0,0 +1,547 @@ +#ifdef HAVE_BAGL + +#pragma GCC diagnostic ignored "-Wformat-invalid-specifier" // snprintf +#pragma GCC diagnostic ignored "-Wformat-extra-args" // snprintf + +#include // bool +#include // snprintf +#include // memset +#include + +#include "os.h" +#include "ux.h" + +#include "./display.h" +#include "./display_utils.h" +#include "../constants.h" +#include "../globals.h" +#include "../boilerplate/io.h" +#include "../boilerplate/sw.h" +#include "../common/bip32.h" +#include "../common/format.h" +#include "../common/script.h" +#include "../constants.h" + +/* + STATELESS STEPS + As these steps do not access per-step globals (except possibly a callback), they can be used in + any flow. +*/ + +// Step with icon and text for pubkey +UX_STEP_NOCB(ux_display_confirm_pubkey_step, pn, {&C_icon_eye, "Confirm public key"}); + +// Step with icon and text for a suspicious address +UX_STEP_NOCB(ux_display_unusual_derivation_path_step, + pnn, + { + &C_icon_warning, + "The derivation", + "path is unusual", + }); + +// Step with icon and text to caution the user to reject if unsure +UX_STEP_CB(ux_display_reject_if_not_sure_step, + pnn, + set_ux_flow_response(false), + { + &C_icon_crossmark, + "Reject if you're", + "not sure", + }); + +// Step with approve button +UX_STEP_CB(ux_display_approve_step, + pb, + set_ux_flow_response(true), + { + &C_icon_validate_14, + "Approve", + }); + +// Step with continue button +UX_STEP_CB(ux_display_continue_step, + pb, + set_ux_flow_response(true), + { + &C_icon_validate_14, + "Continue", + }); + +// Step with reject button +UX_STEP_CB(ux_display_reject_step, + pb, + set_ux_flow_response(false), + { + &C_icon_crossmark, + "Reject", + }); + +/* + STATEFUL STEPS + These can only be used in the context of specific flows, as they access a common shared space + for strings. +*/ + +// PATH/PUBKEY or PATH/ADDRESS + +// Step with title/text for BIP32 path +UX_STEP_NOCB(ux_display_path_step, + bnnn_paging, + { + .title = "Path", + .text = g_ui_state.path_and_pubkey.bip32_path_str, + }); + +// Step with title/text for pubkey +UX_STEP_NOCB(ux_display_pubkey_step, + bnnn_paging, + { + .title = "Public key", + .text = g_ui_state.path_and_pubkey.pubkey, + }); + +// Step with description of a wallet policy +UX_STEP_NOCB(ux_display_wallet_policy_map_step, + bnnn_paging, + { + .title = "Wallet policy:", + .text = g_ui_state.wallet.descriptor_template, + }); + +// Step with index and xpub of a cosigner of a policy_map wallet +UX_STEP_NOCB(ux_display_wallet_policy_cosigner_pubkey_step, + bnnn_paging, + { + .title = g_ui_state.cosigner_pubkey_and_index.signer_index, + .text = g_ui_state.cosigner_pubkey_and_index.pubkey, + }); + +// Step with title/text for address, used when showing a wallet receive address +UX_STEP_NOCB(ux_display_wallet_address_step, + bnnn_paging, + { + .title = "Address", + .text = g_ui_state.wallet.address, + }); + +// Step with warning icon and text explaining that there are external inputs +UX_STEP_NOCB(ux_display_warning_external_inputs_step, + pnn, + { + &C_icon_warning, + "There are", + "external inputs", + }); + +// Step with warning icon for unverified inputs (segwit inputs with no non-witness-utxo) +UX_STEP_NOCB(ux_unverified_segwit_input_flow_1_step, pb, {&C_icon_warning, "Unverified inputs"}); +UX_STEP_NOCB(ux_unverified_segwit_input_flow_2_step, nn, {"Update", "Ledger Live"}); +UX_STEP_NOCB(ux_unverified_segwit_input_flow_3_step, nn, {"or third party", "wallet software"}); + +// Step with warning icon for nondefault sighash +UX_STEP_NOCB(ux_nondefault_sighash_flow_1_step, pb, {&C_icon_warning, "Non-default sighash"}); + +// Step with eye icon and "Review" and the output index +UX_STEP_NOCB(ux_review_step, + pnn, + { + &C_icon_eye, + "Review", + g_ui_state.validate_output.index, + }); + +// Step with "Amount" and an output amount +UX_STEP_NOCB(ux_validate_amount_step, + bnnn_paging, + { + .title = "Amount", + .text = g_ui_state.validate_output.amount, + }); + +// Step with "Address" and a paginated address +UX_STEP_NOCB(ux_validate_address_step, + bnnn_paging, + { + .title = "Address", + .text = g_ui_state.validate_output.address_or_description, + }); + +// Step with eye icon and a "high fees" warning +#ifdef TARGET_NANOS +UX_STEP_NOCB(ux_high_fee_step, + pnn, + { + &C_icon_eye, + "Fees over 10%", + "of total value!", + }); +#else +UX_STEP_NOCB(ux_high_fee_step, + pnn, + { + &C_icon_eye, + "Fees are above 10%", + "of total amount!", + }); +#endif + +UX_STEP_NOCB(ux_confirm_selftransfer_step, pnn, {&C_icon_eye, "Confirm", "self-transfer"}); +UX_STEP_NOCB(ux_confirm_transaction_fees_step, + bnnn_paging, + { + .title = "Fees", + .text = g_ui_state.validate_transaction.fee, + }); +UX_STEP_CB(ux_sign_transaction_step, + pbb, + set_ux_flow_response(true), + {&C_icon_validate_14, "Sign", "transaction"}); + +// Step with wallet icon and "Register wallet" +UX_STEP_NOCB(ux_display_register_wallet_step, + pb, + { + &C_icon_wallet, + "Register wallet", + }); + +// Step with wallet icon and "Receive in known wallet" +UX_STEP_NOCB(ux_display_receive_in_registered_wallet_step, + pnn, + { + &C_icon_wallet, + "Receive in", + "known wallet", + }); + +// Step with wallet icon and "Spend from known wallet" +UX_STEP_NOCB(ux_display_spend_from_registered_wallet_step, + pnn, + { + &C_icon_wallet, + "Spend from", + "known wallet", + }); + +// Step with "Wallet name:", followed by the wallet name +UX_STEP_NOCB(ux_display_wallet_name_step, + bnnn_paging, + { + .title = "Wallet name:", + .text = g_ui_state.wallet.wallet_name, + }); + +////////////////////////////////////////////////////////////////////// +UX_STEP_NOCB(ux_sign_message_step, + pnn, + { + &C_icon_certificate, + "Sign", + "message", + }); + +UX_STEP_CB(ux_message_sign_display_path_step, + bnnn_paging, + set_ux_flow_response(true), + { + .title = "Path", + .text = g_ui_state.path_and_message.bip32_path_str, + }); + +UX_STEP_NOCB(ux_message_hash_step, + bnnn_paging, + { + .title = "Message hash", + .text = g_ui_state.path_and_message.message, + }); + +UX_STEP_CB(ux_sign_message_accept_new, + pbb, + set_ux_flow_response(true), + {&C_icon_validate_14, "Sign", "message"}); + +UX_STEP_CB(ux_message_content_step, + custom, + set_ux_flow_response(true), + { + .title = "Message content", + .text = g_ui_state.path_and_message.message, + }); + +// FLOW to display BIP32 path to sign a message: +// #1 screen: certificate icon + "Sign message" +// #2 screen: display BIP32 Path +// #3 screen: first page of message content +UX_FLOW(ux_sign_message_path_and_content_flow, + &ux_sign_message_step, + &ux_message_sign_display_path_step, + &ux_message_content_step, + &ux_message_content_step); + +// FLOW to display a message hash and confirmation to sign a message: +// #1 screen: certificate icon + "Sign message" +// #2 screen: display BIP32 Path +// #3 screen: display message hash +// #4 screen: "Sign message" and approve button +// #5 screen: reject button +UX_FLOW(ux_sign_message_path_hash_and_confirm_flow, + &ux_sign_message_step, + &ux_message_sign_display_path_step, + &ux_message_hash_step, + &ux_sign_message_accept_new, + &ux_display_reject_step); + +// FLOW to display the message content: +// #1 screen: display message content +UX_FLOW(ux_sign_message_content_flow, &ux_message_content_step, FLOW_LOOP); + +// FLOW to display a confirmation to sign a message: +// #1 screen: "Sign message" and approve button +// #2 screen: reject button +UX_FLOW(ux_sign_message_confirm_flow, &ux_sign_message_accept_new, &ux_display_reject_step); + +// FLOW to display BIP32 path and pubkey: +// #1 screen: eye icon + "Confirm Pubkey" +// #2 screen: display BIP32 Path +// #3 screen: display pubkey +// #4 screen: approve button +// #5 screen: reject button +UX_FLOW(ux_display_pubkey_flow, + &ux_display_confirm_pubkey_step, + &ux_display_path_step, + &ux_display_pubkey_step, + &ux_display_approve_step, + &ux_display_reject_step); + +// FLOW to display BIP32 path and pubkey, for a non-standard path: +// #1 screen: warning icon + "The derivation path is unusual" +// #2 screen: crossmark icon + "Reject if not sure" (user can reject here) +// #3 screen: eye icon + "Confirm Pubkey" +// #4 screen: display BIP32 Path +// #5 screen: display pubkey +// #6 screen: approve button +// #7 screen: reject button +UX_FLOW(ux_display_pubkey_suspicious_flow, + &ux_display_unusual_derivation_path_step, + &ux_display_confirm_pubkey_step, + &ux_display_path_step, + &ux_display_reject_if_not_sure_step, + &ux_display_pubkey_step, + &ux_display_approve_step, + &ux_display_reject_step); + +// FLOW to display the header of a policy map wallet: +// #1 screen: Wallet icon + "Register wallet" +// #2 screen: "Wallet name:" and wallet name +// #3 screen: display policy map (paginated) +// #4 screen: approve button +// #5 screen: reject button +UX_FLOW(ux_display_register_wallet_flow, + &ux_display_register_wallet_step, + &ux_display_wallet_name_step, + &ux_display_wallet_policy_map_step, + &ux_display_approve_step, + &ux_display_reject_step); + +// FLOW to display the header of a policy_map wallet: +// #1 screen: Cosigner index and pubkey (paginated) +// #2 screen: approve button +// #3 screen: reject button +UX_FLOW(ux_display_policy_map_cosigner_pubkey_flow, + &ux_display_wallet_policy_cosigner_pubkey_step, + &ux_display_approve_step, + &ux_display_reject_step); + +// FLOW to display the name and an address of a registered wallet: +// #1 screen: Wallet icon + "Receive in known wallet" +// #2 screen: wallet name +// #3 screen: wallet address (paginated) +// #4 screen: approve button +// #5 screen: reject button +UX_FLOW(ux_display_receive_in_wallet_flow, + &ux_display_receive_in_registered_wallet_step, + &ux_display_wallet_name_step, + &ux_display_wallet_address_step, + &ux_display_approve_step, + &ux_display_reject_step); + +// FLOW to display an address of a default wallet policy: +// #1 screen: wallet address (paginated) +// #2 screen: approve button +// #3 screen: reject button +UX_FLOW(ux_display_default_wallet_address_flow, + &ux_display_wallet_address_step, + &ux_display_approve_step, + &ux_display_reject_step); + +// FLOW to display a registered wallet and authorize spending: +// #1 screen: "Spend from known wallet" +// #2 screen: wallet name +// #3 screen: "Continue" button +// #4 screen: reject button +UX_FLOW(ux_display_spend_from_wallet_flow, + &ux_display_spend_from_registered_wallet_step, + &ux_display_wallet_name_step, + &ux_display_continue_step, + &ux_display_reject_step); + +// FLOW to warn about external inputs +// #1 screen: warning icon + "There are external inputs" +// #2 screen: crossmark icon + "Reject if not sure" (user can reject here) +// #3 screen: "continue" button +UX_FLOW(ux_display_warning_external_inputs_flow, + &ux_display_warning_external_inputs_step, + &ux_display_reject_if_not_sure_step, + &ux_display_continue_step); + +// FLOW to warn about segwitv0 inputs with no non-witness-utxo +// #1 screen: warning icon + "Unverified inputs" +// #2 screen: "Update Ledger Live" +// #3 screen: "or external wallet software" +// #4 screen: "continue" button +// #5 screen: "reject" button +UX_FLOW(ux_display_unverified_segwit_inputs_flow, + &ux_unverified_segwit_input_flow_1_step, + &ux_unverified_segwit_input_flow_2_step, + &ux_unverified_segwit_input_flow_3_step, + &ux_display_continue_step, + &ux_display_reject_step); + +// FLOW to warn about segwitv1 inputs with non-default sighash +// #1 screen: warning icon + "Non default sighash" +// #2 screen: crossmark icon + "Reject if not sure" (user can reject here) +// #3 screen: "continue" button +// #4 screen: "reject" button +UX_FLOW(ux_display_nondefault_sighash_flow, + &ux_nondefault_sighash_flow_1_step, + &ux_display_reject_if_not_sure_step, + &ux_display_continue_step, + &ux_display_reject_step); + +// FLOW to validate a single output +// #1 screen: eye icon + "Review" + index of output to validate +// #2 screen: output amount +// #3 screen: output address (paginated) +// #4 screen: "Continue" button +// #5 screen: reject button +UX_FLOW(ux_display_output_address_amount_flow, + &ux_review_step, + &ux_validate_amount_step, + &ux_validate_address_step, + &ux_display_continue_step, + &ux_display_reject_step); + +// FLOW to warn about fees above 10% of total +// #1 screen: eye icon + fees too high message +// #2 screen: "Continue", with approve button +// #3 screen: reject button +UX_FLOW(ux_warn_high_fee_flow, + &ux_high_fee_step, + &ux_display_continue_step, + &ux_display_reject_step); + +// Show transaction fees and finally accept signing +// #1 screen: fee amount +// #2 screen: "Sign transaction", with approve button +// #3 screen: reject button +UX_FLOW(ux_accept_transaction_flow, + &ux_confirm_transaction_fees_step, + &ux_sign_transaction_step, + &ux_display_reject_step); + +// Show transaction fees and finally accept signing, in case of self-transfer +// Since there is no output to show, we add an initial screen to make sure +// the user understands the nature of the transaction. +// #1 screen: eye icon + "Confirm self-transfer" +// #2 screen: fee amount +// #3 screen: "Sign transaction", with approve button +// #4 screen: reject button +UX_FLOW(ux_accept_selftransfer_flow, + &ux_confirm_selftransfer_step, + &ux_confirm_transaction_fees_step, + &ux_sign_transaction_step, + &ux_display_reject_step); + +void ui_display_pubkey_flow(void) { + ux_flow_init(0, ux_display_pubkey_flow, NULL); +} + +void ui_display_pubkey_suspicious_flow(void) { + ux_flow_init(0, ux_display_pubkey_suspicious_flow, NULL); +} + +void ui_sign_message_path_hash_and_confirm_flow(void) { + ux_flow_init(0, ux_sign_message_path_hash_and_confirm_flow, NULL); +} + +void ui_sign_message_content_flow(uint8_t pageCount) { + (void) pageCount; + if (get_streaming_index() == 0) { + ux_flow_init(0, ux_sign_message_path_and_content_flow, NULL); + } else { + ux_flow_init(0, ux_sign_message_content_flow, NULL); + } +} + +void ui_sign_message_confirm_flow(void) { + ux_flow_init(0, ux_sign_message_confirm_flow, NULL); +} + +void ui_display_register_wallet_flow(void) { + ux_flow_init(0, ux_display_register_wallet_flow, NULL); +} + +void ui_display_policy_map_cosigner_pubkey_flow(void) { + ux_flow_init(0, ux_display_policy_map_cosigner_pubkey_flow, NULL); +} + +void ui_display_receive_in_wallet_flow(void) { + ux_flow_init(0, ux_display_receive_in_wallet_flow, NULL); +} + +void ui_display_default_wallet_address_flow(void) { + ux_flow_init(0, ux_display_default_wallet_address_flow, NULL); +} + +void ui_display_spend_from_wallet_flow(void) { + ux_flow_init(0, ux_display_spend_from_wallet_flow, NULL); +} + +void ui_display_warning_external_inputs_flow(void) { + ux_flow_init(0, ux_display_warning_external_inputs_flow, NULL); +} + +void ui_display_unverified_segwit_inputs_flows(void) { + ux_flow_init(0, ux_display_unverified_segwit_inputs_flow, NULL); +} + +void ui_display_nondefault_sighash_flow(void) { + ux_flow_init(0, ux_display_nondefault_sighash_flow, NULL); +} + +void ui_display_output_address_amount_flow(int index) { + snprintf(g_ui_state.validate_output.index, + sizeof(g_ui_state.validate_output.index), + "output #%d", + index); + ux_flow_init(0, ux_display_output_address_amount_flow, NULL); +} + +void ui_display_output_address_amount_no_index_flow(int index) { + // Currently we don't want any change in the UX so this function defaults to + // ui_display_output_address_amount_flow + ui_display_output_address_amount_flow(index); +} + +void ui_warn_high_fee_flow(void) { + ux_flow_init(0, ux_warn_high_fee_flow, NULL); +} + +void ui_accept_transaction_flow(bool is_self_transfer) { + ux_flow_init(0, + is_self_transfer ? ux_accept_selftransfer_flow : ux_accept_transaction_flow, + NULL); +} + +#endif // HAVE_BAGL diff --git a/src/ui/display_nbgl.c b/src/ui/display_nbgl.c new file mode 100644 index 000000000..f6edaa773 --- /dev/null +++ b/src/ui/display_nbgl.c @@ -0,0 +1,668 @@ +#ifdef HAVE_NBGL + +#include + +#include "nbgl_use_case.h" +#include "./display.h" +#include "./menu.h" +#include "io.h" + +typedef struct { + const char *confirm; // text displayed in last transaction page + const char *confirmed_status; // text displayed in confirmation page (after long press) + const char *rejected_status; // text displayed in rejection page (after reject confirmed) + nbgl_layoutTagValue_t tagValuePair[3]; + nbgl_layoutTagValueList_t tagValueList; + nbgl_pageInfoLongPress_t infoLongPress; + int extOutputCount; + int currentOutput; + bool displayPrompt; +} TransactionContext_t; + +enum { + CANCEL_TOKEN = 0, + CONFIRM_TOKEN, + SILENT_CONFIRM_TOKEN, + BACK_TOKEN_TRANSACTION, // for most transactions + BACK_TOKEN_SELFTRANSFER, // special case when it's a self-transfer (no external outputs) + BACK_TOKEN_MESSAGE, + MESSAGE_DISPLAYABLE_TOKEN, + MESSAGE_NON_DISPLAYABLE_TOKEN, + MESSAGE_CANCEL_TOKEN, + +}; + +extern bool G_was_processing_screen_shown; +static TransactionContext_t transactionContext; + +// ux_flow_response +static void ux_flow_response_false(void) { + set_ux_flow_response(false); +} + +static void ux_flow_response_true(void) { + set_ux_flow_response(true); +} + +static void ux_flow_response(bool confirm) { + if (confirm) { + ux_flow_response_true(); + } else { + ux_flow_response_false(); + } +} + +// Statuses +static void status_confirmation_callback(bool confirm) { + if (confirm) { + ux_flow_response_true(); + nbgl_useCaseStatus(transactionContext.confirmed_status, true, ui_menu_main); + } else { + ux_flow_response_false(); + nbgl_useCaseStatus(transactionContext.rejected_status, false, ui_menu_main); + } +} + +static void status_cancel(void) { + status_confirmation_callback(false); +} + +static void confirm_cancel(void) { + nbgl_useCaseConfirm("Reject transaction?", + "", + "Yes, Reject", + "Go back to transaction", + status_cancel); +} + +static void confirm_message_cancel(void) { + nbgl_useCaseConfirm("Reject message?", "", "Yes, Reject", "Go back to message", status_cancel); +} + +static void start_processing_callback_light(bool confirm) { + if (confirm) { + ux_flow_response_true(); + nbgl_useCaseSpinner("Processing"); + } else { + ux_flow_response_false(); + nbgl_useCaseStatus(transactionContext.rejected_status, false, ui_menu_main); + } +} + +static void start_processing_callback(bool confirm) { + if (confirm) { + ux_flow_response_true(); + nbgl_useCaseSpinner("Processing"); + } else { + confirm_cancel(); + } +} + +static void start_processing_message_callback(bool confirm) { + if (confirm) { + ux_flow_response_true(); + nbgl_useCaseSpinner("Processing"); + } else { + confirm_message_cancel(); + } +} + +static void transaction_confirm_callback(int token, uint8_t index) { + (void) index; + + switch (token) { + case CANCEL_TOKEN: + confirm_cancel(); + break; + case CONFIRM_TOKEN: + start_processing_callback(true); + break; + case SILENT_CONFIRM_TOKEN: + ux_flow_response(true); + break; + case BACK_TOKEN_TRANSACTION: + ui_accept_transaction_flow(false); + break; + case BACK_TOKEN_SELFTRANSFER: + ui_accept_transaction_flow(true); + break; + case BACK_TOKEN_MESSAGE: + decrease_streaming_index(); + ux_flow_response(true); + break; + case MESSAGE_DISPLAYABLE_TOKEN: + increase_streaming_index(); + ux_flow_response(true); + break; + case MESSAGE_NON_DISPLAYABLE_TOKEN: + ui_sign_message_confirm_flow(); + break; + case MESSAGE_CANCEL_TOKEN: + confirm_message_cancel(); + break; + default: + PRINTF("Unhandled token : %d", token); + } +} + +// Continue callbacks +static void continue_light_notify_callback(void) { + transactionContext.tagValueList.pairs = transactionContext.tagValuePair; + + transactionContext.infoLongPress.icon = &C_Bitcoin_64px; + transactionContext.infoLongPress.longPressText = "Approve"; + transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; + transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; + transactionContext.infoLongPress.text = transactionContext.confirm; + + nbgl_useCaseStaticReviewLight(&transactionContext.tagValueList, + &transactionContext.infoLongPress, + "Cancel", + status_confirmation_callback); +} + +static void continue_light_processing_callback(void) { + transactionContext.tagValueList.pairs = transactionContext.tagValuePair; + + transactionContext.infoLongPress.icon = &C_Bitcoin_64px; + transactionContext.infoLongPress.longPressText = "Approve"; + transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; + transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; + transactionContext.infoLongPress.text = transactionContext.confirm; + + nbgl_useCaseStaticReviewLight(&transactionContext.tagValueList, + &transactionContext.infoLongPress, + "Cancel", + start_processing_callback_light); +} + +static void continue_callback(void) { + transactionContext.tagValueList.pairs = transactionContext.tagValuePair; + + transactionContext.infoLongPress.icon = &C_Bitcoin_64px; + transactionContext.infoLongPress.longPressText = "Approve"; + transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; + transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; + transactionContext.infoLongPress.text = transactionContext.confirm; + + nbgl_useCaseStaticReview(&transactionContext.tagValueList, + &transactionContext.infoLongPress, + "Cancel", + start_processing_callback); +} + +static void continue_message_callback(void) { + transactionContext.tagValueList.pairs = transactionContext.tagValuePair; + + transactionContext.infoLongPress.icon = &C_Bitcoin_64px; + transactionContext.infoLongPress.longPressText = "Approve"; + transactionContext.infoLongPress.longPressToken = CONFIRM_TOKEN; + transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; + transactionContext.infoLongPress.text = transactionContext.confirm; + + nbgl_useCaseStaticReview(&transactionContext.tagValueList, + &transactionContext.infoLongPress, + "Cancel", + start_processing_message_callback); +} + +// Transaction flow +static void transaction_confirm(int token, uint8_t index) { + (void) index; + + // If it's a self-transfer, the UX is slightly different + int backToken = + transactionContext.extOutputCount == 0 ? BACK_TOKEN_SELFTRANSFER : BACK_TOKEN_TRANSACTION; + + if (token == CONFIRM_TOKEN) { + nbgl_pageNavigationInfo_t info = {.activePage = transactionContext.extOutputCount + 1, + .nbPages = transactionContext.extOutputCount + 2, + .navType = NAV_WITH_TAP, + .progressIndicator = true, + .navWithTap.backButton = true, + .navWithTap.backToken = backToken, + .navWithTap.nextPageText = NULL, + .navWithTap.quitText = "Reject transaction", + .quitToken = CANCEL_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + + nbgl_pageContent_t content = {.type = INFO_LONG_PRESS, + .infoLongPress.icon = &C_Bitcoin_64px, + .infoLongPress.text = transactionContext.confirm, + .infoLongPress.longPressText = "Hold to sign", + .infoLongPress.longPressToken = CONFIRM_TOKEN, + .infoLongPress.tuneId = TUNE_TAP_NEXT}; + + nbgl_pageDrawGenericContent(&transaction_confirm_callback, &info, &content); + nbgl_refresh(); + } else { + confirm_cancel(); + } +} + +void ui_accept_transaction_flow(bool is_self_transfer) { + if (!is_self_transfer) { + transactionContext.tagValuePair[0].item = "Fees"; + transactionContext.tagValuePair[0].value = g_ui_state.validate_transaction.fee; + + transactionContext.tagValueList.nbPairs = 1; + } else { + transactionContext.tagValuePair[0].item = "Amount"; + transactionContext.tagValuePair[0].value = "Self-transfer"; + transactionContext.tagValuePair[1].item = "Fees"; + transactionContext.tagValuePair[1].value = g_ui_state.validate_transaction.fee; + + transactionContext.tagValueList.nbPairs = 2; + } + + transactionContext.confirm = "Sign transaction\nto send Bitcoin?"; + transactionContext.confirmed_status = "TRANSACTION\nSIGNED"; + transactionContext.rejected_status = "Transaction rejected"; + + nbgl_pageNavigationInfo_t info = {.activePage = transactionContext.extOutputCount, + .nbPages = transactionContext.extOutputCount + 2, + .navType = NAV_WITH_TAP, + .progressIndicator = true, + .navWithTap.backButton = false, + .navWithTap.nextPageText = "Tap to continue", + .navWithTap.nextPageToken = CONFIRM_TOKEN, + .navWithTap.quitText = "Reject transaction", + .quitToken = CANCEL_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + + nbgl_pageContent_t content = {.type = TAG_VALUE_LIST, + .tagValueList.nbPairs = transactionContext.tagValueList.nbPairs, + .tagValueList.pairs = transactionContext.tagValuePair}; + + nbgl_pageDrawGenericContent(&transaction_confirm, &info, &content); + nbgl_refresh(); +} + +void ui_display_transaction_prompt(const int external_outputs_total_count) { + transactionContext.currentOutput = 0; + transactionContext.extOutputCount = external_outputs_total_count; + + transactionContext.rejected_status = "Transaction rejected"; + + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Review transaction\nto send Bitcoin", + "", + "Reject transaction", + ux_flow_response_true, + confirm_cancel); +} + +// Display outputs +static void display_output(void) { + transactionContext.rejected_status = "Transaction rejected"; + + nbgl_pageNavigationInfo_t info = {.activePage = transactionContext.currentOutput - 1, + .nbPages = transactionContext.extOutputCount + 2, + .navType = NAV_WITH_TAP, + .progressIndicator = true, + .navWithTap.backButton = false, + .navWithTap.nextPageText = "Tap to continue", + .navWithTap.nextPageToken = SILENT_CONFIRM_TOKEN, + .navWithTap.quitText = "Reject transaction", + .quitToken = CANCEL_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + + nbgl_pageContent_t content = {.type = TAG_VALUE_LIST, + .tagValueList.nbMaxLinesForValue = 8, + .tagValueList.nbPairs = transactionContext.tagValueList.nbPairs, + .tagValueList.pairs = transactionContext.tagValuePair}; + + nbgl_pageDrawGenericContent(&transaction_confirm_callback, &info, &content); + nbgl_refresh(); +} + +void ui_display_output_address_amount_flow(int index) { + snprintf(g_ui_state.validate_output.index, + sizeof(g_ui_state.validate_output.index), + "#%d", + index); + + transactionContext.currentOutput++; + + transactionContext.tagValuePair[0].item = "Output"; + transactionContext.tagValuePair[0].value = g_ui_state.validate_output.index; + + transactionContext.tagValuePair[1].item = "Amount"; + transactionContext.tagValuePair[1].value = g_ui_state.validate_output.amount; + + transactionContext.tagValuePair[2].item = "Address"; + transactionContext.tagValuePair[2].value = g_ui_state.validate_output.address_or_description; + + transactionContext.tagValueList.nbPairs = 3; + + display_output(); +} + +void ui_display_output_address_amount_no_index_flow(int index) { + (void) index; + transactionContext.currentOutput++; + + transactionContext.tagValuePair[0].item = "Amount"; + transactionContext.tagValuePair[0].value = g_ui_state.validate_output.amount; + + transactionContext.tagValuePair[1].item = "Address"; + transactionContext.tagValuePair[1].value = g_ui_state.validate_output.address_or_description; + + transactionContext.tagValueList.nbPairs = 2; + + display_output(); +} + +// Continue light notify callback +void ui_display_pubkey_flow(void) { + transactionContext.tagValuePair[0].item = "Path"; + transactionContext.tagValuePair[0].value = g_ui_state.path_and_pubkey.bip32_path_str; + + transactionContext.tagValuePair[1].item = "Public key"; + transactionContext.tagValuePair[1].value = g_ui_state.path_and_pubkey.pubkey; + transactionContext.tagValueList.nbPairs = 2; + + transactionContext.confirm = "Approve public key"; + transactionContext.confirmed_status = "PUBLIC KEY\nAPPROVED"; + transactionContext.rejected_status = "Public key rejected"; + + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Confirm public key", + "", + "Cancel", + continue_light_notify_callback, + status_cancel); +} + +void ui_display_receive_in_wallet_flow(void) { + transactionContext.tagValuePair[0].item = "Wallet name"; + transactionContext.tagValuePair[0].value = g_ui_state.wallet.wallet_name; + + transactionContext.tagValuePair[1].item = "Wallet Address"; + transactionContext.tagValuePair[1].value = g_ui_state.wallet.address; + + transactionContext.tagValueList.nbPairs = 2; + + transactionContext.confirm = "Confirm address"; + transactionContext.confirmed_status = "ADDRESS\nCONFIRMED"; + transactionContext.rejected_status = "Address rejected"; + + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Receive\nin known wallet", + "", + "Cancel", + continue_light_notify_callback, + status_cancel); +} + +void ui_display_policy_map_cosigner_pubkey_flow(void) { + transactionContext.tagValuePair[0].item = "Index"; + transactionContext.tagValuePair[0].value = g_ui_state.cosigner_pubkey_and_index.signer_index; + + transactionContext.tagValuePair[1].item = "Public key"; + transactionContext.tagValuePair[1].value = g_ui_state.cosigner_pubkey_and_index.pubkey; + + transactionContext.tagValueList.nbPairs = 2; + + transactionContext.confirm = "Confirm cosigner"; + transactionContext.confirmed_status = "COSIGNER\nREGISTERED"; + transactionContext.rejected_status = "Cosigner rejected"; + + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Register cosigner", + "", + "Cancel", + continue_light_notify_callback, + ux_flow_response_false); +} + +static void suspicious_pubkey_warning(void) { + nbgl_useCaseReviewStart(&C_round_warning_64px, + "WARNING", + "The derivation path\nis unusual", + "Cancel", + continue_light_notify_callback, + ux_flow_response_false); +} + +void ui_display_pubkey_suspicious_flow(void) { + transactionContext.tagValuePair[0].item = "Path"; + transactionContext.tagValuePair[0].value = g_ui_state.path_and_pubkey.bip32_path_str; + + transactionContext.tagValuePair[1].item = "Public key"; + transactionContext.tagValuePair[1].value = g_ui_state.path_and_pubkey.pubkey; + + transactionContext.tagValueList.nbPairs = 2; + + transactionContext.confirm = "Approve public key"; + transactionContext.confirmed_status = "PUBLIC KEY\nAPPROVED"; + transactionContext.rejected_status = "Public key rejected"; + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Confirm public key", + "", + "Cancel", + suspicious_pubkey_warning, + status_cancel); +} + +// Continue light processing callback +void ui_display_register_wallet_flow(void) { + transactionContext.tagValuePair[0].item = "Name"; + transactionContext.tagValuePair[0].value = g_ui_state.wallet.wallet_name; + + transactionContext.tagValuePair[1].item = "Policy map"; + transactionContext.tagValuePair[1].value = g_ui_state.wallet.descriptor_template; + + transactionContext.tagValueList.nbPairs = 2; + + transactionContext.confirm = "Register Wallet"; + transactionContext.confirmed_status = "WALLET\nREGISTERED"; + transactionContext.rejected_status = "Wallet rejected"; + + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Register wallet", + "", + "Cancel", + continue_light_processing_callback, + ux_flow_response_false); +} + +static void ui_display_message_content_flow(bool displayable, uint8_t pageCount) { + uint8_t token; + if (displayable) { + token = MESSAGE_DISPLAYABLE_TOKEN; + } else { + token = MESSAGE_NON_DISPLAYABLE_TOKEN; + } + + nbgl_pageNavigationInfo_t info = { + .activePage = get_streaming_index(), + .nbPages = pageCount + 1, + .navType = NAV_WITH_TAP, + .progressIndicator = true, + .navWithTap.backButton = displayable && get_streaming_index() != 0, + .navWithTap.backToken = BACK_TOKEN_MESSAGE, + .navWithTap.nextPageText = "Tap to continue", + .navWithTap.nextPageToken = token, + .navWithTap.quitText = "Reject message", + .quitToken = MESSAGE_CANCEL_TOKEN, + .tuneId = TUNE_TAP_CASUAL}; + + nbgl_pageContent_t content = {.type = TAG_VALUE_LIST, + .tagValueList.nbPairs = transactionContext.tagValueList.nbPairs, + .tagValueList.pairs = transactionContext.tagValuePair, + .tagValueList.wrapping = true}; + + transactionContext.tagValueList.nbPairs = 0; + nbgl_pageDrawGenericContent(&transaction_confirm_callback, &info, &content); + nbgl_refresh(); +} + +void ui_set_display_prompt(void) { + transactionContext.displayPrompt = true; +} + +static void display_message_content(void) { + ui_display_message_content_flow(true, transactionContext.extOutputCount); +} + +void ui_sign_message_content_flow(uint8_t pageCount) { + transactionContext.rejected_status = "Message rejected"; + + if (get_streaming_index() == 0) { + transactionContext.tagValuePair[0].item = "Path"; + transactionContext.tagValuePair[0].value = g_ui_state.path_and_message.bip32_path_str; + transactionContext.tagValueList.nbPairs = 1; + } + + transactionContext.tagValuePair[transactionContext.tagValueList.nbPairs].item = + "Message content"; + transactionContext.tagValuePair[transactionContext.tagValueList.nbPairs].value = + g_ui_state.path_and_message.message; + + transactionContext.tagValueList.nbPairs++; + + transactionContext.extOutputCount = pageCount; + + if (transactionContext.displayPrompt) { + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Review message", + "", + "Cancel", + display_message_content, + status_cancel); + transactionContext.displayPrompt = false; + } else { + display_message_content(); + } +} + +void ui_sign_message_path_hash_and_confirm_flow(void) { + transactionContext.rejected_status = "Message rejected"; + + transactionContext.tagValuePair[0].item = "Path"; + transactionContext.tagValuePair[0].value = g_ui_state.path_and_message.bip32_path_str; + transactionContext.tagValuePair[1].item = "Message hash"; + transactionContext.tagValuePair[1].value = g_ui_state.path_and_message.message; + + transactionContext.tagValueList.nbPairs = 2; + + ui_display_message_content_flow(false, 0); +} + +void ui_sign_message_confirm_flow(void) { + transactionContext.tagValueList.nbPairs = 0; + transactionContext.confirm = "Sign Message"; + transactionContext.confirmed_status = "MESSAGE\nSIGNED"; + transactionContext.rejected_status = "Message rejected"; + + continue_message_callback(); +} + +void ui_display_spend_from_wallet_flow(void) { + transactionContext.tagValuePair[0].item = "Wallet name"; + transactionContext.tagValuePair[0].value = g_ui_state.wallet.wallet_name; + + transactionContext.tagValueList.nbPairs = 1; + + transactionContext.confirm = "Confirm wallet name"; + transactionContext.confirmed_status = "WALLET NAME\nCONFIRMED"; + transactionContext.rejected_status = "Wallet name rejected"; + + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Spend from\nknown wallet", + "", + "Cancel", + continue_callback, + ux_flow_response_false); +} + +// Address flow +static void address_display(void) { + nbgl_useCaseAddressConfirmation(g_ui_state.wallet.address, status_confirmation_callback); +} + +void ui_display_default_wallet_address_flow(void) { + transactionContext.confirm = "Confirm address"; + transactionContext.confirmed_status = "ADDRESS\nVERIFIED"; + transactionContext.rejected_status = "Address verification\ncancelled"; + + nbgl_useCaseReviewStart(&C_Bitcoin_64px, + "Verify Bitcoin\naddress", + "", + "Cancel", + address_display, + status_cancel); +} + +// Warning Flows +void ui_warn_high_fee_flow(void) { + nbgl_useCaseChoice(&C_round_warning_64px, + "Warning", + "Fees are above 10%\n of total amount", + "Continue", + "Reject", + ux_flow_response); +} + +void ui_display_warning_external_inputs_flow(void) { + nbgl_useCaseChoice(&C_round_warning_64px, + "Warning", + "There are external inputs", + "Continue", + "Reject if not sure", + ux_flow_response); +} + +void ui_display_unverified_segwit_inputs_flows(void) { + nbgl_useCaseChoice(&C_round_warning_64px, + "Warning", + "Unverified inputs\nUpdate Ledger Live or\nthird party wallet software", + "Continue", + "Reject if not sure", + ux_flow_response); +} + +void ui_display_nondefault_sighash_flow(void) { + nbgl_useCaseChoice(&C_round_warning_64px, + "Warning", + "Non-default sighash", + "Continue", + "Reject if not sure", + ux_flow_response); +} + +// Statuses +void ui_display_post_processing_confirm_message(bool success) { + if (success) { + nbgl_useCaseStatus("MESSAGE\nSIGNED", true, ux_flow_response_true); + } else { + nbgl_useCaseStatus("Message rejected", false, ux_flow_response_false); + } +} + +void ui_display_post_processing_confirm_wallet_registation(bool success) { + if (success) { + nbgl_useCaseStatus("WALLET\nREGISTERED", true, ux_flow_response_true); + } else { + nbgl_useCaseStatus("Wallet rejected", false, ux_flow_response_false); + } +} + +void ui_display_post_processing_confirm_transaction(bool success) { + if (success) { + nbgl_useCaseStatus("TRANSACTION\nSIGNED", true, ux_flow_response_true); + } else { + nbgl_useCaseStatus("Transaction rejected", false, ux_flow_response_false); + } +} + +void ui_display_post_processing_confirm_wallet_spend(bool success) { + if (success) { + nbgl_useCaseStatus("WALLET NAME\nCONFIRMED", true, ux_flow_response_true); + } else { + nbgl_useCaseStatus("Wallet name rejected", false, ux_flow_response_false); + } +} + +#endif // HAVE_NBGL diff --git a/src/ui/display_utils.h b/src/ui/display_utils.h index 3695b550d..b35641562 100644 --- a/src/ui/display_utils.h +++ b/src/ui/display_utils.h @@ -4,8 +4,11 @@ #include "../constants.h" +/// Maximum amount length // up to 5 chars for ticker, 1 space, up to 20 digits (20 = digits of 2^64), + 1 decimal separator #define MAX_AMOUNT_LENGTH (MAX_ASSET_TICKER_LENGTH + 1 + 20 + 1) +/// Number of decimal digits in fractional part of BTC or L-BTC +#define BITCOIN_DECIMALS 8 /** * Converts a 64-bits unsigned integer into a decimal representation. Trailing decimal zeros are not @@ -22,3 +25,21 @@ void format_amount(const char *coin_name, uint64_t amount, uint8_t decimals, char out[static MAX_AMOUNT_LENGTH + 1]); + + +/** + * Converts a 64-bits unsigned integer into a decimal rapresentation, where the `amount` is a + * multiple of 1/100_000_000th. Trailing decimal zeros are not appended (and no decimal point is + * present if the `amount` is a multiple of 100_000_000). The resulting string is prefixed with a + * ticker name (up to 5 characters long), followed by a space. + * + * @param coin_name a zero-terminated ticker name, at most 5 characterso long (not including the + * terminating 0) + * @param amount the amount to format + * @param out the output array which must be at least MAX_AMOUNT_LENGTH + 1 bytes long + */ +static inline void format_sats_amount(const char *coin_name, + uint64_t amount, + char out[static MAX_AMOUNT_LENGTH + 1]) { + format_amount(coin_name, amount, BITCOIN_DECIMALS, out); +} diff --git a/src/ui/menu.c b/src/ui/menu.c index 326e78907..5f924166d 100644 --- a/src/ui/menu.c +++ b/src/ui/menu.c @@ -1,6 +1,6 @@ /***************************************************************************** * Ledger App Bitcoin. - * (c) 2021 Ledger SAS. + * (c) 2024 Ledger SAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,81 +23,15 @@ #include "../globals.h" #include "menu.h" -// We have a screen with the icon and "Bitcoin is ready" for Bitcoin, -// "Bitcoin Testnet is ready" for Bitcoin Testnet, "Application is ready" for all the altcoins -UX_STEP_NOCB(ux_menu_ready_step_bitcoin, pnn, {&C_bitcoin_logo, "Bitcoin", "is ready"}); -UX_STEP_NOCB(ux_menu_ready_step_bitcoin_testnet, - pnn, - {&C_bitcoin_logo, "Bitcoin Testnet", "is ready"}); -UX_STEP_NOCB(ux_menu_ready_step_altcoin, nn, {"Application", "is ready"}); - -UX_STEP_NOCB(ux_menu_version_step, bn, {"Version", APPVERSION}); -UX_STEP_CB(ux_menu_about_step, pb, ui_menu_about(), {&C_icon_certificate, "About"}); -UX_STEP_VALID(ux_menu_exit_step, pb, os_sched_exit(-1), {&C_icon_dashboard_x, "Quit"}); - -// FLOW for the main menu (for bitcoin): -// #1 screen: ready -// #2 screen: version of the app -// #3 screen: about submenu -// #4 screen: quit -UX_FLOW(ux_menu_main_flow_bitcoin, - &ux_menu_ready_step_bitcoin, - &ux_menu_version_step, - &ux_menu_about_step, - &ux_menu_exit_step, - FLOW_LOOP); - -// FLOW for the main menu (for bitcoin testnet): -// #1 screen: ready -// #2 screen: version of the app -// #3 screen: about submenu -// #4 screen: quit -UX_FLOW(ux_menu_main_flow_bitcoin_testnet, - &ux_menu_ready_step_bitcoin_testnet, - &ux_menu_version_step, - &ux_menu_about_step, - &ux_menu_exit_step, - FLOW_LOOP); - -// FLOW for the main menu (for altcoins): -// #1 screen: ready -// #2 screen: version of the app -// #3 screen: about submenu -// #4 screen: quit -UX_FLOW(ux_menu_main_flow_altcoin, - &ux_menu_ready_step_altcoin, - &ux_menu_version_step, - &ux_menu_about_step, - &ux_menu_exit_step, - FLOW_LOOP); - #define BIP32_PUBKEY_VERSION_MAINNET 0x0488B21E #define BIP32_PUBKEY_VERSION_TESTNET 0x043587CF void ui_menu_main() { - if (G_ux.stack_count == 0) { - ux_stack_push(); - } - if (BIP32_PUBKEY_VERSION == BIP32_PUBKEY_VERSION_MAINNET) { // mainnet - ux_flow_init(0, ux_menu_main_flow_bitcoin, NULL); + ui_menu_main_flow_bitcoin(); } else if (BIP32_PUBKEY_VERSION == BIP32_PUBKEY_VERSION_TESTNET) { // testnet - ux_flow_init(0, ux_menu_main_flow_bitcoin_testnet, NULL); - } else { - ux_flow_init(0, ux_menu_main_flow_altcoin, NULL); // some altcoin + ui_menu_main_flow_bitcoin_testnet(); } } -UX_STEP_NOCB(ux_menu_info_step, bn, {"Bitcoin App", "(c) 2022 Ledger"}); -UX_STEP_CB(ux_menu_back_step, pb, ui_menu_main(), {&C_icon_back, "Back"}); - -// FLOW for the about submenu: -// #1 screen: app info -// #2 screen: back button to main menu -UX_FLOW(ux_menu_about_flow, &ux_menu_info_step, &ux_menu_back_step, FLOW_LOOP); - -void ui_menu_about() { - ux_flow_init(0, ux_menu_about_flow, NULL); -} - #endif // !defined(HAVE_LIQUID) diff --git a/src/ui/menu.h b/src/ui/menu.h index d9c69e08a..6c3568b8b 100644 --- a/src/ui/menu.h +++ b/src/ui/menu.h @@ -1,7 +1,7 @@ #pragma once /** - * Show main menu (ready screen, version, about, quit). + * Entry point function to show main menu (ready screen, version, about, quit). */ void ui_menu_main(void); @@ -9,3 +9,13 @@ void ui_menu_main(void); * Show about submenu (copyright, date). */ void ui_menu_about(void); + +/** + * Show main menu (ready screen, version, about, quit). + */ +void ui_menu_main_flow_bitcoin(void); + +/** + * Show main menu for Testnet (ready screen, version, about, quit). + */ +void ui_menu_main_flow_bitcoin_testnet(void); diff --git a/src/ui/menu_bagl.c b/src/ui/menu_bagl.c new file mode 100644 index 000000000..ee617973c --- /dev/null +++ b/src/ui/menu_bagl.c @@ -0,0 +1,87 @@ +/***************************************************************************** + * Ledger App Bitcoin. + * (c) 2024 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#ifdef HAVE_BAGL +#include "os.h" +#include "ux.h" + +#include "../globals.h" +#include "menu.h" + +// We have a screen with the icon and "Bitcoin is ready" for Bitcoin, +// "Bitcoin Testnet is ready" for Bitcoin Testnet. +UX_STEP_NOCB(ux_menu_ready_step_bitcoin, pnn, {&C_bitcoin_logo, "Bitcoin", "is ready"}); +UX_STEP_NOCB(ux_menu_ready_step_bitcoin_testnet, + pnn, + {&C_bitcoin_logo, "Bitcoin Testnet", "is ready"}); + +UX_STEP_NOCB(ux_menu_version_step, bn, {"Version", APPVERSION}); +UX_STEP_CB(ux_menu_about_step, pb, ui_menu_about(), {&C_icon_certificate, "About"}); +UX_STEP_VALID(ux_menu_exit_step, pb, os_sched_exit(-1), {&C_icon_dashboard_x, "Quit"}); + +// FLOW for the main menu (for bitcoin): +// #1 screen: ready +// #2 screen: version of the app +// #3 screen: about submenu +// #4 screen: quit +UX_FLOW(ux_menu_main_flow_bitcoin, + &ux_menu_ready_step_bitcoin, + &ux_menu_version_step, + &ux_menu_about_step, + &ux_menu_exit_step, + FLOW_LOOP); + +// FLOW for the main menu (for bitcoin testnet): +// #1 screen: ready +// #2 screen: version of the app +// #3 screen: about submenu +// #4 screen: quit +UX_FLOW(ux_menu_main_flow_bitcoin_testnet, + &ux_menu_ready_step_bitcoin_testnet, + &ux_menu_version_step, + &ux_menu_about_step, + &ux_menu_exit_step, + FLOW_LOOP); + +UX_STEP_NOCB(ux_menu_info_step, bn, {"Bitcoin App", "(c) 2024 Ledger"}); +UX_STEP_CB(ux_menu_back_step, pb, ui_menu_main(), {&C_icon_back, "Back"}); + +// FLOW for the about submenu: +// #1 screen: app info +// #2 screen: back button to main menu +UX_FLOW(ux_menu_about_flow, &ux_menu_info_step, &ux_menu_back_step, FLOW_LOOP); + +void ui_menu_main_flow_bitcoin(void) { + if (G_ux.stack_count == 0) { + ux_stack_push(); + } + + ux_flow_init(0, ux_menu_main_flow_bitcoin, NULL); +} + +void ui_menu_main_flow_bitcoin_testnet(void) { + if (G_ux.stack_count == 0) { + ux_stack_push(); + } + + ux_flow_init(0, ux_menu_main_flow_bitcoin_testnet, NULL); +} + +void ui_menu_about(void) { + ux_flow_init(0, ux_menu_about_flow, NULL); +} +#endif // HAVE_BAGL diff --git a/src/ui/menu_nbgl.c b/src/ui/menu_nbgl.c new file mode 100644 index 000000000..4473a89bb --- /dev/null +++ b/src/ui/menu_nbgl.c @@ -0,0 +1,61 @@ +/***************************************************************************** + * Ledger App Bitcoin. + * (c) 2024 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#ifdef HAVE_NBGL +#include "nbgl_use_case.h" + +#include "../globals.h" +#include "menu.h" + +static const char* const infoTypes[] = {"Version", "Developer", "Copyright"}; +static const char* const infoContents[] = {APPVERSION, "Ledger", "(c) 2024 Ledger"}; + +static bool navigation_cb(uint8_t page, nbgl_pageContent_t* content) { + UNUSED(page); + content->type = INFOS_LIST; + content->infosList.nbInfos = 3; + content->infosList.infoTypes = (const char**) infoTypes; + content->infosList.infoContents = (const char**) infoContents; + return true; +} + +static void exit(void) { + os_sched_exit(-1); +} + +void ui_menu_about(void) { + nbgl_useCaseSettings("Bitcoin", 0, 1, false, ui_menu_main, navigation_cb, NULL); +} + +void ui_menu_about_testnet(void) { + nbgl_useCaseSettings("Bitcoin Testnet", 0, 1, false, ui_menu_main, navigation_cb, NULL); +} + +void ui_menu_main_flow_bitcoin(void) { + nbgl_useCaseHome("Bitcoin", &C_Bitcoin_64px, NULL, false, ui_menu_about, exit); +} + +void ui_menu_main_flow_bitcoin_testnet(void) { + nbgl_useCaseHome("Bitcoin Testnet", + &C_Bitcoin_64px, + "This app enables signing\ntransactions on all the Bitcoin\ntest networks.", + false, + ui_menu_about_testnet, + exit); +} + +#endif // HAVE_NBGL diff --git a/test_utils/__init__.py b/test_utils/__init__.py index 3f27ea45a..f7867407a 100644 --- a/test_utils/__init__.py +++ b/test_utils/__init__.py @@ -1,8 +1,11 @@ import hashlib +from typing import Literal, Union from mnemonic import Mnemonic from bip32 import BIP32 +from bitcoin_client.ledger_bitcoin.wallet import WalletPolicy + from .slip21 import Slip21Node mnemo = Mnemonic("english") @@ -41,9 +44,15 @@ def mnemonic(mnemo: str): def ripemd160(x: bytes) -> bytes: - h = hashlib.new("ripemd160") - h.update(x) - return h.digest() + try: + h = hashlib.new("ripemd160") + h.update(x) + return h.digest() + except BaseException: + # ripemd160 is not always present in hashlib. + # Fallback to custom implementation if missing. + from . import ripemd + return ripemd.ripemd160(x) def sha256(s: bytes) -> bytes: @@ -73,3 +82,36 @@ def __init__(self, mnemonic: str, network: str = "test"): slip21_root = Slip21Node.from_seed(self.seed) self.wallet_registration_key = slip21_root.derive_child( WALLET_POLICY_SLIP21_LABEL).key + + +def get_internal_xpub(seed: str, path: str) -> str: + bip32 = BIP32.from_seed(seed, network="test") + return bip32.get_xpub_from_path(f"m/{path}") if path else bip32.get_xpub_from_path("m") + + +def count_internal_keys(seed: str, network: Union[Literal['main'], Literal['test']], wallet_policy: WalletPolicy) -> int: + """Count how many of the keys in wallet_policy are indeed internal""" + + bip32 = BIP32.from_seed(seed, network) + master_key_fingerprint = hash160(bip32.pubkey)[0:4] + + count = 0 + for key_index, key_info in enumerate(wallet_policy.keys_info): + if "]" in key_info: + key_orig_end_pos = key_info.index("]") + fpr = key_info[1:9] + path = key_info[10:key_orig_end_pos] + xpub = key_info[key_orig_end_pos + 1:] + + # support for V1 policies, where the key info contains additional derivation steps + if "/" in xpub: + xpub = xpub[:xpub.index("/")] # truncate any additional steps + + if fpr == master_key_fingerprint.hex(): + computed_xpub = get_internal_xpub(seed, path) + if computed_xpub == xpub: + # there could be multiple placeholders using the same key; we must count all of them + count += wallet_policy.descriptor_template.count( + f"@{key_index}/") + + return count diff --git a/test_utils/fixtures.py b/test_utils/fixtures.py index 2d1bbbdd9..611a7d2ac 100644 --- a/test_utils/fixtures.py +++ b/test_utils/fixtures.py @@ -52,6 +52,8 @@ def pytest_addoption(parser): parser.addoption("--hid", action="store_true") parser.addoption("--headless", action="store_true") parser.addoption("--enableslowtests", action="store_true") + parser.addoption("--model", action="store", default="nanos") + parser.addoption('--speculos_api_port', action='store') @pytest.fixture(scope="module") @@ -93,13 +95,18 @@ def enable_slow_tests(pytestconfig): return pytestconfig.getoption("enableslowtests") +@pytest.fixture +def model(pytestconfig): + return pytestconfig.getoption("model") + + @pytest.fixture(scope='session', autouse=True) def root_directory(request): return Path(str(request.config.rootdir)) @pytest.fixture -def comm(settings, root_directory, hid, headless, app_version: str) -> Union[TransportClient, SpeculosClient]: +def comm(settings, root_directory, hid, headless, model, app_version: str) -> Union[TransportClient, SpeculosClient]: if hid: client = TransportClient("hid") else: @@ -123,7 +130,7 @@ def comm(settings, root_directory, hid, headless, app_version: str) -> Union[Tra client = SpeculosClient( app_binary, - ['--sdk', '2.1', '--seed', f'{settings["mnemonic"]}'] + ['--model', model, '--seed', f'{settings["mnemonic"]}'] + ["--display", "qt" if not headless else "headless"] + lib_params ) @@ -170,3 +177,9 @@ def client(bitcoin_network: str, comm: Union[TransportClient, SpeculosClient]) - def speculos_globals(settings: dict, bitcoin_network: str) -> SpeculosGlobals: return SpeculosGlobals(mnemonic=settings["mnemonic"], network=bitcoin_network) + + +@pytest.fixture +def additional_speculos_arguments(pytestconfig): + api_port = pytestconfig.getoption("speculos_api_port") + return ["--api-port", api_port] if api_port else [] \ No newline at end of file diff --git a/test_utils/requirements.txt b/test_utils/requirements.txt index 4319aa950..c06bc9eb3 100644 --- a/test_utils/requirements.txt +++ b/test_utils/requirements.txt @@ -1,2 +1,2 @@ mnemonic==0.20 -bip32>=2.1,<4.0 \ No newline at end of file +bip32>=3.4,<4.0 \ No newline at end of file diff --git a/test_utils/ripemd.py b/test_utils/ripemd.py new file mode 100644 index 000000000..ee08cc387 --- /dev/null +++ b/test_utils/ripemd.py @@ -0,0 +1,115 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Taken from https://github.com/bitcoin/bitcoin/blob/124e75a41ea0f3f0e90b63b0c41813184ddce2ab/test/functional/test_framework/ripemd160.py + +""" +Pure Python RIPEMD160 implementation. + +WARNING: This implementation is NOT constant-time. +Do not use without understanding the implications. +""" + +# Message schedule indexes for the left path. +ML = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +] + +# Message schedule indexes for the right path. +MR = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +] + +# Rotation counts for the left path. +RL = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +] + +# Rotation counts for the right path. +RR = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +] + +# K constants for the left path. +KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] + +# K constants for the right path. +KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] + + +def fi(x, y, z, i): + """The f1, f2, f3, f4, and f5 functions from the specification.""" + if i == 0: + return x ^ y ^ z + elif i == 1: + return (x & y) | (~x & z) + elif i == 2: + return (x | ~y) ^ z + elif i == 3: + return (x & z) | (y & ~z) + elif i == 4: + return x ^ (y | ~z) + else: + assert False + + +def rol(x, i): + """Rotate the bottom 32 bits of x left by i bits.""" + return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] + + # Iterate over the 80 rounds of the compression. + for j in range(80): + rnd = j >> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + state = compress(*state, data[64*b:64*(b+1)]) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') + # Process final blocks. + for b in range(len(fin) >> 6): + state = compress(*state, fin[64*b:64*(b+1)]) + # Produce output. + return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) diff --git a/test_utils/txmaker.py b/test_utils/txmaker.py index bc196d1b9..e5caeee73 100644 --- a/test_utils/txmaker.py +++ b/test_utils/txmaker.py @@ -1,7 +1,7 @@ from random import randint from typing import List, Tuple, Optional -from bitcoin_client.ledger_bitcoin import PolicyMapWallet +from bitcoin_client.ledger_bitcoin import WalletPolicy, WalletType from bitcoin_client.ledger_bitcoin.key import KeyOriginInfo, parse_path, get_taproot_output_key from bitcoin_client.ledger_bitcoin.psbt import PSBT, PartiallySignedInput, PartiallySignedOutput from bitcoin_client.ledger_bitcoin.tx import CScriptWitness, CTransaction, CTxIn, CTxInWitness, CTxOut, COutPoint, CTxWitness, uint256_from_str @@ -40,27 +40,28 @@ def random_txid() -> bytes: return random_bytes(32) -def getScriptPubkeyFromWallet(wallet: PolicyMapWallet, change: bool, address_index: int) -> Script: - descriptor_str = wallet.policy_map +def getScriptPubkeyFromWallet(wallet: WalletPolicy, change: bool, address_index: int) -> Script: + descriptor_str = wallet.descriptor_template # Iterate in reverse order, as strings identifying a small-index key (like @1) can be a # prefix of substrings identifying a large-index key (like @12), but not the other way around # A more structural parsing would be more robust for i, key_info_str in enumerate(reversed(wallet.keys_info)): - if key_info_str[-3:] != "/**": + if wallet.version == WalletType.WALLET_POLICY_V1 and key_info_str[-3:] != "/**": raise ValueError("All the keys must have wildcard (/**)") - key_info_str = key_info_str[:-3] + f"/{1 if change else 0}/*" - if f"@{i}" not in descriptor_str: raise ValueError(f"Invalid policy: not using key @{i}") descriptor_str = descriptor_str.replace(f"@{i}", key_info_str) + # by doing the text substitution of '/**' at the end, this works for either V1 or V2 + descriptor_str = descriptor_str.replace("/**", f"/{1 if change else 0}/*") + return Descriptor.from_string(descriptor_str).derive(address_index).script_pubkey() -def createFakeWalletTransaction(n_inputs: int, n_outputs: int, output_amount: int, wallet: PolicyMapWallet) -> Tuple[CTransaction, int, int, int]: +def createFakeWalletTransaction(n_inputs: int, n_outputs: int, output_amount: int, wallet: WalletPolicy) -> Tuple[CTransaction, int, int, int]: """ Creates a (fake) transaction that has n_inputs inputs and n_outputs outputs, with a random output equal to output_amount. Each output of the transaction is a spend to wallet (possibly to a change address); the change/address_index of the @@ -115,7 +116,7 @@ def createFakeWalletTransaction(n_inputs: int, n_outputs: int, output_amount: in return tx, selected_output_index, selected_output_change, selected_output_address_index -def createPsbt(wallet: PolicyMapWallet, input_amounts: List[int], output_amounts: List[int], output_is_change: List[bool], output_wallet: Optional[List[Optional[PolicyMapWallet]]] = None) -> PSBT: +def createPsbt(wallet: WalletPolicy, input_amounts: List[int], output_amounts: List[int], output_is_change: List[bool], output_wallet: Optional[List[Optional[WalletPolicy]]] = None) -> PSBT: if output_wallet is None: output_wallet = [None] * len(output_amounts) @@ -127,8 +128,15 @@ def createPsbt(wallet: PolicyMapWallet, input_amounts: List[int], output_amounts if wallet.n_keys != 1: raise NotImplementedError("Only 1-key wallets supported") - if wallet.policy_map not in ["pkh(@0)", "wpkh(@0)", "tr(@0)"]: - raise NotImplementedError("Unsupported policy type") + if wallet.version == WalletType.WALLET_POLICY_V1: + if wallet.descriptor_template not in ["pkh(@0)", "wpkh(@0)", "tr(@0)"]: + raise NotImplementedError("Unsupported policy type") + elif wallet.version == WalletType.WALLET_POLICY_V2: + if wallet.descriptor_template not in ["pkh(@0/**)", "wpkh(@0/**)", "tr(@0/**)"]: + raise NotImplementedError("Unsupported policy type") + else: + raise ValueError( + f"Unknown wallet policy version: {wallet.version}") vin: List[CTxIn] = [CTxIn() for _ in input_amounts] vout: List[CTxOut] = [CTxOut() for _ in output_amounts] @@ -164,10 +172,10 @@ def createPsbt(wallet: PolicyMapWallet, input_amounts: List[int], output_amounts psbt.outputs = [PartiallySignedOutput(0) for _ in output_amounts] # simplification; good enough for the scripts we support now, but will need more work - is_legacy = wallet.policy_map.startswith("pkh(") - is_segwitv0 = wallet.policy_map.startswith( - "wpkh(") or wallet.policy_map.startswith("sh(wpkh(") - is_taproot = wallet.policy_map.startswith("tr(") + is_legacy = wallet.descriptor_template.startswith("pkh(") + is_segwitv0 = wallet.descriptor_template.startswith( + "wpkh(") or wallet.descriptor_template.startswith("sh(wpkh(") + is_taproot = wallet.descriptor_template.startswith("tr(") key_origin = wallet.keys_info[0][1:wallet.keys_info[0].index("]")] @@ -190,9 +198,9 @@ def createPsbt(wallet: PolicyMapWallet, input_amounts: List[int], output_amounts psbt.inputs[i].hd_keypaths[input_key] = KeyOriginInfo( master_key_fpr, path) elif is_taproot: - tweaked_key = get_taproot_output_key(input_key) - psbt.inputs[i].tap_bip32_paths[tweaked_key] = ( - list(), KeyOriginInfo(master_key_fpr, path)) + internal_key = input_key[1:] + psbt.inputs[i].tap_bip32_paths[internal_key] = ( + {}, KeyOriginInfo(master_key_fpr, path)) else: raise RuntimeError("Unexpected state: unknown transaction type") @@ -216,12 +224,12 @@ def createPsbt(wallet: PolicyMapWallet, input_amounts: List[int], output_amounts psbt.outputs[i].hd_keypaths[output_key] = KeyOriginInfo( master_key_fpr, path) elif is_taproot: - tweaked_key = get_taproot_output_key(output_key) - psbt.outputs[i].tap_bip32_paths[tweaked_key] = ( - list(), KeyOriginInfo(master_key_fpr, path)) + internal_key = output_key[1:] + psbt.outputs[i].tap_bip32_paths[internal_key] = ( + {}, KeyOriginInfo(master_key_fpr, path)) - psbt.outputs[i].tap_bip32_paths[tweaked_key] = ( - list(), KeyOriginInfo(master_key_fpr, path)) + psbt.outputs[i].tap_bip32_paths[internal_key] = ( + {}, KeyOriginInfo(master_key_fpr, path)) psbt.tx = tx diff --git a/tests-legacy/README.md b/tests-legacy/README.md deleted file mode 100644 index 95ea7014e..000000000 --- a/tests-legacy/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# End-to-end tests using Bitcoin Testnet - -These tests are implemented in Python and can be executed either using the [Speculos](https://github.com/LedgerHQ/speculos) emulator or a Ledger Nano S/X. - -All the commands in this folder are meant to be ran from the `tests` folder, not from the root. - -Python dependencies are listed in [requirements.txt](requirements.txt), install them using [pip](https://pypi.org/project/pip/) - -``` -pip install -r requirements.txt -``` - -## Launch with Speculos - -In order to create the necessary binaries for the Bitcoin Testnet application, you can use the convenience scripts `prepare_tests_lib.sh` and `prepare_tests_native.sh`. The former compiles the Bitcoin mainnet version of the application to use as a library, then compiles the binary for the Bitcoin testnet version using the library (this is the mechanism used for the altcoins applications based on the legacy Bitcoin app). The latter natively compiles the Bitcoin application for testnet. - -``` -bash ./prepare_tests_lib.sh # or bash ./prepare_tests_native.sh -``` - -Then run all the tests using: - -``` -pytest -``` - -You can delete the test binaries with - -``` -bash ./clean_tests.sh -``` - -## Launch with your Nano S/X - -Compile and install the app on your device as normal. - -To run the tests on your Ledger Nano S/X you also need to install an optional dependency - -``` -pip install ledgercomm[hid] -``` - -Be sure to have you device connected through USB (without any other software interacting with it) and run - -``` -pytest --hid -``` - -Please note that tests that require an automation file are meant for speculos, and will currently hang the test suite. \ No newline at end of file diff --git a/tests-legacy/automations/accept.json b/tests-legacy/automations/accept.json deleted file mode 100644 index 150507c23..000000000 --- a/tests-legacy/automations/accept.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": 1, - "rules": [ - { - "regexp": "Review|Amount|Address|Confirm|Fees", - "actions": [ - ["button", 2, true], - ["button", 2, false] - ] - }, - { - "text": "Accept", - "actions": [ - [ "button", 1, true ], - [ "button", 2, true ], - [ "button", 2, false ], - [ "button", 1, false ] - ] - } - ] -} diff --git a/tests-legacy/automations/reject.json b/tests-legacy/automations/reject.json deleted file mode 100644 index 50d04f485..000000000 --- a/tests-legacy/automations/reject.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": 1, - "rules": [ - { - "regexp": "Review|Amount|Address|Confirm|Fees|Accept", - "actions": [ - ["button", 2, true], - ["button", 2, false] - ] - }, - { - "text": "Reject", - "actions": [ - [ "button", 1, true ], - [ "button", 2, true ], - [ "button", 2, false ], - [ "button", 1, false ] - ] - } - ] -} diff --git a/tests-legacy/bitcoin_client/bitcoin_base_cmd.py b/tests-legacy/bitcoin_client/bitcoin_base_cmd.py deleted file mode 100644 index 5e71fde56..000000000 --- a/tests-legacy/bitcoin_client/bitcoin_base_cmd.py +++ /dev/null @@ -1,345 +0,0 @@ -import struct -from typing import Tuple, List, Optional - -from ledgercomm import Transport - -from bitcoin_client.hwi.serialization import CTransaction, hash256 -from bitcoin_client.exception.device_exception import DeviceException -from bitcoin_client.bitcoin_cmd_builder import AddrType, InsType, BitcoinCommandBuilder - - -class BitcoinBaseCommand: - """Bitcoin Base Command. - - Send APDU command to device and get APDU response. - - Parameters - ---------- - transport : Transport - Transport interface to the device. - debug : bool - Whether you want to see logging or not. - - Attributes - ---------- - transport : Transport - Transport interface to send APDUs. - builder : BitcoinCommandBuilder - Command builder to construct APDUs. - - """ - - def __init__(self, transport: Transport, debug: bool = False) -> None: - """Init constructor.""" - self.transport = transport - self.builder = BitcoinCommandBuilder(debug=debug) - - def get_random(self, n: int = 5) -> bytes: - """Get `n` bytes random value. - - Parameters - ---------- - n : int - Number of bytes (5 <= n <= 248). - - Returns - ------- - bytes - Random bytes of length `n` from the device. - - """ - sw, response = self.transport.exchange_raw( - self.builder.get_random(n=n) - ) - - if sw != 0x9000: - raise DeviceException(error_code=sw, ins=InsType.GET_RANDOM) - - return response - - def get_firmware_version(self) -> Tuple[int, int, int]: - """Get the version of the application. - - Returns - ------- - Tuple[int, int, int] - (MAJOR, MINOR, PATCH) version of the application. - - """ - sw, response = self.transport.exchange_raw( - self.builder.get_firmware_version() - ) - - if sw != 0x9000: - raise DeviceException(error_code=sw, ins=InsType.GET_FIRMWARE_VERSION) - - # response = flag (1) [unused] || - # architecture id (1) [unused] || - # major version of the application (1) || - # minor version of the application (1) || - # patch version of the application (1) || - # loader id major version (1) [unused] || - # loader id minor version (1) [unused] || - # mode (1) [unused] - _, _, major, minor, patch, _, _, _ = struct.unpack( - "BBBBBBBB", - response - ) # type: int, int, int, int, int, int, int, int - - return major, minor, patch - - def get_coin_version(self) -> Tuple[int, int, int, str, str]: - """Get coin information depending on Bitcoin app fork. - - Returns - ------- - Tuple[int, int, int, str, str] - A tuple (p2pkh_pref, p2sh_prefix, coin_family, coin_name, coin_ticker). - - """ - sw, response = self.transport.exchange_raw( - self.builder.get_coin_version() - ) # type: int, bytes - - if sw != 0x9000: - raise DeviceException(error_code=sw, ins=InsType.GET_COIN_VERSION) - - # response = p2pkh_prefix (2) || p2sh_prefix (2) || coin_family (1) || - # len(coin_name) (1) || coin_name (var) || - # len(coin_ticker) (1) || coin_ticker (var) - offset: int = 0 - p2pkh_prefix: int = int.from_bytes(response[offset:offset + 2], byteorder="big") - offset += 2 - p2sh_prefix: int = int.from_bytes(response[offset:offset + 2], byteorder="big") - offset += 2 - coin_family: int = response[offset] - offset += 1 - coin_name_len: int = response[offset] - offset += 1 - coin_name: str = response[offset:offset + coin_name_len].decode("ascii") - offset += coin_name_len - coin_ticker_len: int = response[offset] - offset += 1 - coin_ticker: str = response[offset:offset + coin_ticker_len].decode("ascii") - offset += coin_ticker_len - - assert len(response) == offset - - return p2pkh_prefix, p2sh_prefix, coin_family, coin_name, coin_ticker - - def get_public_key(self, - addr_type: AddrType, - bip32_path: str, - display: bool = False) -> Tuple[bytes, str, bytes]: - """Get public key given address type and BIP32 path. - - Parameters - ---------- - addr_type : AddrType - Type of address. Could be AddrType.Legacy, AddrType.P2SH_P2WPKH, - AddrType.BECH32. - bip32_path : str - BIP32 path of the public key you want. - display : bool - Whether you want to display address and ask confirmation on the device. - - Returns - ------- - - """ - sw, response = self.transport.exchange_raw( - self.builder.get_public_key(addr_type=addr_type, - bip32_path=bip32_path, - display=display) - ) # type: int, bytes - - if sw != 0x9000: - raise DeviceException(error_code=sw, ins=InsType.GET_WALLET_PUBLIC_KEY) - - # response = len(pub_key) (1) || pub_key (var) || - # len(addr) (1) || addr (var) || bip32_chain_code (32) - offset: int = 0 - pub_key_len: int = response[offset] - offset += 1 - pub_key: bytes = response[offset:offset + pub_key_len] - offset += pub_key_len - addr_len: int = response[offset] - offset += 1 - addr: str = response[offset:offset + addr_len].decode("ascii") - offset += addr_len - bip32_chain_code: bytes = response[offset:offset + 32] - offset += 32 - - assert len(response) == offset - - return pub_key, addr, bip32_chain_code - - def get_trusted_input(self, - utxo: CTransaction, - output_index: int) -> bytes: - """Get trusted input given UTXO and output index. - - Parameters - ---------- - utxo : CTransaction - Serialized Bitcoin transaction to extract UTXO. - output_index : int - Index of the UTXO to build the trusted input. - - Returns - ------- - bytes - Serialized trusted input. - - """ - sw: int - response: bytes = b"" - - for chunk in self.builder.get_trusted_input(utxo, output_index): - self.transport.send_raw(chunk) - sw, response = self.transport.recv() # type: int, bytes - - if sw != 0x9000: - raise DeviceException(error_code=sw, ins=InsType.GET_TRUSTED_INPUT) - - # response = 0x32 (1) || 0x00 (1) || random (2) || prev_txid (32) || - # output_index (4) || amount (8) || HMAC (8) - assert len(response) == 56 - - offset: int = 0 - magic_trusted_input: int = response[offset] - assert magic_trusted_input == 0x32 - offset += 1 - zero: int = response[offset] - assert zero == 0x00 - offset += 1 - _: bytes = response[offset:offset + 2] # random - offset += 2 - prev_txid: bytes = response[offset:offset + 32] - assert prev_txid == hash256(utxo.serialize_without_witness()) - offset += 32 - out_index: int = int.from_bytes(response[offset:offset + 4], - byteorder="little") - assert out_index == output_index - offset += 4 - amount: int = int.from_bytes(response[offset:offset + 8], - byteorder="little") - assert amount == utxo.vout[output_index].nValue - offset += 8 - _: bytes = response[offset:offset + 8] # HMAC - offset += 8 - - assert offset == len(response) - - return response - - def untrusted_hash_tx_input_start(self, - tx: CTransaction, - inputs: List[Tuple[CTransaction, bytes]], - input_index: int, - script: bytes, - is_new_transaction: bool) -> None: - """Send trusted inputs to build the new transaction. - - Parameters - ---------- - tx : CTransaction - Serialized Bitcoin transaction to sign. - inputs : List[Tuple[CTransaction, bytes]] - List of inputs with pair of UTXO and trusted input. - input_index : int - Index of the input to process. - script : bytes - The scriptSig to add at `input_index`. - is_new_transaction: bool - First time sending this input. - - Returns - ------- - None - - """ - sw: int - - for chunk in self.builder.untrusted_hash_tx_input_start(tx=tx, - inputs=inputs, - input_index=input_index, - script=script, - is_new_transaction=is_new_transaction): - self.transport.send_raw(chunk) - sw, _ = self.transport.recv() # type: int, bytes - - if sw != 0x9000: - raise DeviceException( - error_code=sw, - ins=InsType.UNTRUSTED_HASH_TRANSACTION_INPUT_START - ) - - def untrusted_hash_tx_input_finalize(self, - tx: CTransaction, - change_path: Optional[str]) -> bytes: - """Send transaction outputs to finalize the new transaciton. - - Parameters - ---------- - tx: CTransaction - Transaction to sign. - change_path: Optional[str] - BIP32 path for the change. - - Returns - ------- - bytes - Two bytes Reserved for Future Use (RFU) and transaction validation flag. - Unused, always 0x00 and 0x00. - - - """ - sw: int - response: bytes = b"" - - for chunk in self.builder.untrusted_hash_tx_input_finalize(tx=tx, - change_path=change_path): - self.transport.send_raw(chunk) - sw, response = self.transport.recv() - - if sw != 0x9000: - raise DeviceException( - error_code=sw, - ins=InsType.UNTRUSTED_HASH_TRANSACTION_INPUT_FINALIZE - ) - # response = RFU (1) || User validation flag (1) - return response - - def untrusted_hash_sign(self, - sign_path: str, - lock_time: int = 0, - sig_hash: int = 1) -> Tuple[int, bytes]: - """Sign input just sent using `sign_path`. - - Parameters - ---------- - sign_path : str - BIP32 path to be used to sign. - lock_time : int - Block height or timestamp when transaction is final. - sig_hash : int - Either SIGHASH_ALL (0x01), SIGHASH_NONE (0x02) or SIGHASH_SINGLE (0x03). - Only SIGHASH_ALL (0x01) is supported. - - Returns - ------- - Tuple[int, bytes] - A pair (v, der_sig) with: - - v: 0x01 if y-coordinate of R is odd, 0x00 otherwise. - - der_sig: DER encoded Bitcoin ECDSA signature (with SIGHASH). - - """ - sw, response = self.transport.exchange_raw( - self.builder.untrusted_hash_sign(sign_path, lock_time, sig_hash) - ) # type: int, bytes - - if sw != 0x9000: - raise DeviceException(error_code=sw, ins=InsType.UNTRUSTED_HASH_SIGN) - - return (1, b"\x30" + response[1:]) if response[0] & 0x01 else (0, response) diff --git a/tests-legacy/bitcoin_client/bitcoin_cmd.py b/tests-legacy/bitcoin_client/bitcoin_cmd.py deleted file mode 100644 index ec5818fd8..000000000 --- a/tests-legacy/bitcoin_client/bitcoin_cmd.py +++ /dev/null @@ -1,244 +0,0 @@ -from typing import Tuple, List - -from ledgercomm import Transport - -from bitcoin_client.hwi.serialization import (CTransaction, CTxIn, CTxOut, COutPoint, - is_witness, is_p2wpkh, is_p2pkh, is_p2sh, hash160) -from bitcoin_client.hwi.bech32 import decode as bech32_decode -from bitcoin_client.hwi.base58 import decode as base58_decode -from bitcoin_client.utils import deser_trusted_input -from bitcoin_client.bitcoin_utils import bip143_digest, compress_pub_key -from bitcoin_client.bitcoin_cmd_builder import AddrType -from bitcoin_client.bitcoin_base_cmd import BitcoinBaseCommand - - -class BitcoinCommand(BitcoinBaseCommand): - """Bitcoin Command. - - Inherit from BitcoinBaseCommand and provide a high level - interface to sign Bitcoin transaction. - - Parameters - ---------- - transport : Transport - Transport interface to the device. - debug : bool - Whether you want to see logging or not. - - """ - - def __init__(self, transport: Transport, debug: bool = False) -> None: - """Init constructor.""" - super().__init__(transport, debug) - - def sign_new_tx(self, - address: str, - amount: int, - fees: int, - change_path: str, - sign_paths: List[str], - raw_utxos: List[Tuple[bytes, int]], - lock_time: int = 0) -> List[Tuple[bytes, bytes, Tuple[int, bytes]]]: - """Sign a new transaction with parameters.. - - Parameters - ---------- - address : str - Bitcoin address. - amount : int - Amount to send to address in satoshis. - fees : int - Fees of the new transaction. - change_path : str - BIP32 path for the change. - sign_paths : List[str] - BIP32 paths to sign inputs. - raw_utxos : List[Tuple[bytes, int]] - Pairs of raw hex transaction and output index to use as UTXOs. - lock_time : int - Block height or timestamp when transaction is final. - - Returns - ------- - List[Tuple[bytes, bytes, Tuple[int, bytes]]] - Tuples (tx_hash_digest, sign_pub_key, (v, der_sig)) - - """ - utxos: List[Tuple[CTransaction, int, int]] = [] - amount_available: int = 0 - for raw_tx, output_index in raw_utxos: - utxo = CTransaction.from_bytes(raw_tx) - value = utxo.vout[output_index].nValue - utxos.append((utxo, output_index, value)) - amount_available += value - - sign_pub_keys: List[bytes] = [] - for sign_path in sign_paths: - sign_pub_key, _, _ = self.get_public_key( - addr_type=AddrType.BECH32, - bip32_path=sign_path, - display=False - ) - sign_pub_keys.append(compress_pub_key(sign_pub_key)) - - inputs: List[Tuple[CTransaction, bytes]] = [ - (utxo, self.get_trusted_input(utxo=utxo, output_index=output_index)) - for utxo, output_index, _ in utxos - ] - - # new transaction - tx: CTransaction = CTransaction() - tx.nVersion = 2 - tx.nLockTime = lock_time - # prepare vin - for i, (utxo, trusted_input) in enumerate(inputs): - if utxo.sha256 is None: - utxo.calc_sha256(with_witness=False) - - _, _, _, prev_txid, output_index, _, _ = deser_trusted_input(trusted_input) - assert prev_txid != utxo.sha256 - - script_pub_key: bytes = utxo.vout[output_index].scriptPubKey - # P2WPKH - if is_p2wpkh(script_pub_key): - _, _, wit_prog = is_witness(script_pub_key) - script_pub_key = (b"\x76" + # OP_DUP - b"\xa9" + # OP_HASH160 - b"\x14" + # bytes to push (20) - wit_prog + # hash160(pubkey) - b"\x88" + # OP_EQUALVERIFY - b"\xac") # OP_CHECKSIG - # P2SH-P2WPKH or P2PKH - if (is_p2sh(script_pub_key) and not utxo.wit.is_null()) or is_p2pkh(script_pub_key): - script_pub_key = (b"\x76" + # OP_DUP - b"\xa9" + # OP_HASH160 - b"\x14" + # bytes to push (20) - hash160(sign_pub_keys[i]) + # hash160(pubkey) - b"\x88" + # OP_EQUALVERIFY - b"\xac") # OP_CHECKSIG - tx.vin.append(CTxIn(outpoint=COutPoint(h=utxo.sha256, n=output_index), - scriptSig=script_pub_key, - nSequence=0xfffffffd)) - - if amount_available - fees > amount: - change_pub_key, _, _ = self.get_public_key( - addr_type=AddrType.BECH32, - bip32_path=change_path, - display=False - ) - change_pubkey_hash = hash160(compress_pub_key(change_pub_key)) - change_script_pubkey: bytes - # Bech32 pubkey hash or script hash (mainnet and testnet) - if address.startswith("bc1") or address.startswith("tb1"): - change_script_pubkey = bytes([0, len(change_pubkey_hash)]) + change_pubkey_hash - # P2SH-P2WPKH (mainnet and testnet) - elif address.startswith("3") or address.startswith("2"): - change_script_pubkey = (b"\xa9" + # OP_HASH160 - b"\x14" + # bytes to push (20) - # hash160(redeem_script) - hash160(bytes([0, len(change_pubkey_hash)]) + change_pubkey_hash) + - b"\x87") # OP_EQUAL - # P2PKH address (mainnet and testnet) - elif address.startswith("1") or (address.startswith("m") or address.startswith("n")): - change_script_pubkey = (b"\x76" + # OP_DUP - b"\xa9" + # OP_HASH160 - b"\x14" + # bytes to push (20) - change_pubkey_hash + # hash160(pubkey) - b"\x88" + # OP_EQUALVERIFY - b"\xac") # OP_CHECKSIG - else: - raise Exception(f"Unsupported address: '{address}'") - tx.vout.append( - CTxOut(nValue=amount_available - fees - amount, - scriptPubKey=change_script_pubkey) - ) - - script_pub_key: bytes - # Bech32 pubkey hash or script hash (mainnet and testnet) - if address.startswith("bc1") or address.startswith("tb1"): - witness_version, witness_program = bech32_decode(address[0:2], address) - script_pub_key = bytes( - [witness_version + 0x50 if witness_version else 0, - len(witness_program)] + - witness_program - ) - # P2SH address (mainnet and testnet) - elif address.startswith("3") or address.startswith("2"): - script_pub_key = (b"\xa9" + # OP_HASH160 - b"\x14" + # bytes to push (20) - base58_decode(address)[1:-4] + # hash160(redeem_script) - b"\x87") # OP_EQUAL - # P2PKH address (mainnet and testnet) - elif address.startswith("1") or (address.startswith("m") or address.startswith("n")): - script_pub_key = (b"\x76" + # OP_DUP - b"\xa9" + # OP_HASH160 - b"\x14" + # bytes to push (20) - base58_decode(address)[1:-4] + # hash160(pubkey) - b"\x88" + # OP_EQUALVERIFY - b"\xac") # OP_CHECKSIG - else: - raise Exception(f"Unsupported address: '{address}'") - - tx.vout.append(CTxOut(nValue=amount, - scriptPubKey=script_pub_key)) - - for i in range(len(tx.vin)): - self.untrusted_hash_tx_input_start(tx=tx, - inputs=inputs, - input_index=i, - script=tx.vin[i].scriptSig, - is_new_transaction=(i == 0)) - - self.untrusted_hash_tx_input_finalize(tx=tx, - change_path=change_path) - - sigs: List[Tuple[bytes, bytes, Tuple[int, bytes]]] = [] - for i in range(len(tx.vin)): - self.untrusted_hash_tx_input_start(tx=tx, - inputs=[inputs[i]], - input_index=0, - script=tx.vin[i].scriptSig, - is_new_transaction=False) - _, _, amount = utxos[i] - sigs.append( - (bip143_digest(tx, amount, i), - sign_pub_keys[i], - self.untrusted_hash_sign(sign_path=sign_paths[i], - lock_time=tx.nLockTime, - sig_hash=1)) - ) - - return sigs - - def sign_tx(self, - tx: CTransaction, - change_path: str, - sign_paths: List[str], - utxos: List[Tuple[CTransaction, int, int]]) -> List[Tuple[int, bytes]]: - inputs: List[Tuple[CTransaction, bytes]] = [ - (utxo, self.get_trusted_input(utxo=utxo, output_index=output_index)) - for utxo, output_index, _ in utxos - ] - - for i in range(len(tx.vin)): - self.untrusted_hash_tx_input_start(tx=tx, - inputs=inputs, - input_index=i, - script=tx.vin[i].scriptSig, - is_new_transaction=(i == 0)) - - self.untrusted_hash_tx_input_finalize(tx=tx, - change_path=change_path) - - sigs = [] - for i in range(len(tx.vin)): - self.untrusted_hash_tx_input_start(tx=tx, - inputs=[inputs[i]], - input_index=0, - script=tx.vin[i].scriptSig, - is_new_transaction=False) - sigs.append(self.untrusted_hash_sign(sign_path=sign_paths[i], - lock_time=tx.nLockTime, - sig_hash=1)) - - return sigs diff --git a/tests-legacy/bitcoin_client/bitcoin_cmd_builder.py b/tests-legacy/bitcoin_client/bitcoin_cmd_builder.py deleted file mode 100644 index d4e3ec97e..000000000 --- a/tests-legacy/bitcoin_client/bitcoin_cmd_builder.py +++ /dev/null @@ -1,387 +0,0 @@ -import enum -import logging -import struct -from typing import Optional, List, Tuple, Iterator, Union, cast - -from bitcoin_client.hwi.serialization import CTransaction, ser_compact_size -from bitcoin_client.utils import chunkify, MAX_APDU_LEN -from bitcoin_client.bitcoin_utils import bip32_path_from_string - - -class InsType(enum.IntEnum): - """Instruction commands supported.""" - - GET_RANDOM = 0xC0 - GET_FIRMWARE_VERSION = 0xC4 - GET_COIN_VERSION = 0x16 - GET_WALLET_PUBLIC_KEY = 0x40 - GET_TRUSTED_INPUT = 0x42 - UNTRUSTED_HASH_TRANSACTION_INPUT_START = 0x44 - UNTRUSTED_HASH_TRANSACTION_INPUT_FINALIZE = 0x4A - UNTRUSTED_HASH_SIGN = 0x48 - - -class AddrType(enum.IntEnum): - """Type of Bitcoin address.""" - - Legacy = 0x00 - P2SH_P2WPKH = 0x01 - BECH32 = 0x02 - - -class BitcoinCommandBuilder: - """APDU command builder for the Bitcoin application. - - Parameters - ---------- - debug : bool - Whether you want to see logging or not. - - Attributes - ---------- - debug : bool - Whether you want to see logging or not. - - """ - - CLA: int = 0xE0 - - def __init__(self, debug: bool = False): - """Init constructor.""" - self.debug = debug - - def serialize(self, - cla: int, - ins: Union[int, enum.IntEnum], - p1: int = 0, - p2: int = 0, - cdata: bytes = b"") -> bytes: - """Serialize the whole APDU command (header + cdata). - - Parameters - ---------- - cla : int - Instruction class: CLA (1 byte) - ins : Union[int, IntEnum] - Instruction code: INS (1 byte) - p1 : int - Instruction parameter 1: P1 (1 byte). - p2 : int - Instruction parameter 2: P2 (1 byte). - cdata : bytes - Bytes of command data. - - Returns - ------- - bytes - Bytes of a complete APDU command. - - """ - ins = cast(int, ins.value) if isinstance(ins, enum.IntEnum) else cast(int, ins) - - header: bytes = struct.pack("BBBBB", - cla, - ins, - p1, - p2, - len(cdata)) # add Lc to APDU header - - if self.debug: - logging.info("header: %s", header.hex()) - logging.info("cdata: %s", cdata.hex()) - - return header + cdata - - def get_random(self, n: int = 248) -> bytes: - """Command builder for GET_RANDOM. - - Parameters - ---------- - n : int - Number of bytes (1 <= n <= 248). - - Returns - ------- - bytes - APDU command for GET_RANDOM. - - """ - return self.serialize(cla=self.CLA, - ins=InsType.GET_RANDOM, - p1=0x00, - p2=0x00, - cdata=b"\x00" * n) - - def get_firmware_version(self) -> bytes: - """Command builder for GET_FIRMWARE_VERSION. - - Returns - ------- - bytes - APDU command for GET_FIMWARE_VERSION. - - """ - ins: InsType = InsType.GET_FIRMWARE_VERSION - p1: int = 0x00 - p2: int = 0x00 - - return self.serialize(cla=self.CLA, ins=ins, p1=p1, p2=p2, cdata=b"") - - def get_coin_version(self) -> bytes: - """Command builder for GET_COIN_VERSION. - - Returns - ------- - bytes - APDU command for GET_COIN_VERSION. - - """ - ins: InsType = InsType.GET_COIN_VERSION - p1: int = 0x00 - p2: int = 0x00 - - return self.serialize(cla=self.CLA, ins=ins, p1=p1, p2=p2, cdata=b"") - - def get_public_key(self, - addr_type: AddrType, - bip32_path: str, - display: bool = False) -> bytes: - """Command builder for GET_WALLET_PUBLIC_KEY. - - Parameters - ---------- - addr_type : AddrType - The type of address expected in the response. Could be Legacy (0x00), - P2SH-P2WPKH (0x01) or BECH32 encoded P2WPKH (0x02). - bip32_path : str - String representation of BIP32 path (e.g. "m/44'/0'/0'/0" or "44'/0'/0'/0"). - display : bool - Whether you want to display address and ask confirmation on the device. - - Returns - ------- - bytes - APDU command for GET_WALLET_PUBLIC_KEY. - - """ - ins: InsType = InsType.GET_WALLET_PUBLIC_KEY - # P1: - # - 0x00, do not display the address - # - 0x01, display the address - # - 0x02, display the validation token (unused here) - p1: int = 0x01 if display else 0x00 - # P2: type of Bitcoin address in the response - p2: int = addr_type.value - - path: List[bytes] = bip32_path_from_string(bip32_path) - - cdata: bytes = b"".join([ - len(path).to_bytes(1, byteorder="big"), - *path - ]) - - return self.serialize(cla=self.CLA, ins=ins, p1=p1, p2=p2, cdata=cdata) - - def get_trusted_input(self, - utxo: CTransaction, - output_index: int) -> Iterator[bytes]: - """Command builder for GET_TRUSTED_INPUT. - - Parameters - ---------- - utxo: CTransaction - Unspent Transaction Output (UTXO) serialized. - output_index: int - Output index owned in the UTXO. - - Yields - ------ - bytes - APDU command chunk for GET_TRUSTED_INPUT. - - """ - ins: InsType = InsType.GET_TRUSTED_INPUT - # P1: - # - 0x00, first transaction data chunk - # - 0x80, other transaction data chunk - p1: int - p2: int = 0x00 - - cdata: bytes = (output_index.to_bytes(4, byteorder="big") + - utxo.serialize_without_witness()) - - for i, (is_last, chunk) in enumerate(chunkify(cdata, MAX_APDU_LEN)): - p1 = 0x00 if i == 0 else 0x80 - if is_last: - yield self.serialize(cla=self.CLA, - ins=ins, - p1=p1, - p2=p2, - cdata=chunk) - return - yield self.serialize(cla=self.CLA, - ins=ins, - p1=p1, - p2=p2, - cdata=chunk) - - def untrusted_hash_tx_input_start(self, - tx: CTransaction, - inputs: List[Tuple[CTransaction, bytes]], - input_index: int, - script: bytes, - is_new_transaction: bool - ) -> Iterator[bytes]: - """Command builder for UNTRUSTED_HASH_TRANSACTION_INPUT_START. - - Parameters - ---------- - tx: CTransaction - Serialized Bitcoin transaction to sign. - inputs: List[Tuple[CTransaction, bytes]] - List of inputs with pair of UTXO and trusted input. - input_index: int - Index of the input to process. - script : bytes - The scriptSig to add at `input_index`. - is_new_transaction: bool - First time sending this input. - - Yields - ------- - bytes - APDU command chunk for UNTRUSTED_HASH_TRANSACTION_INPUT_START. - - """ - ins: InsType = InsType.UNTRUSTED_HASH_TRANSACTION_INPUT_START - # P1: - # - 0x00, first transaction data chunk - # - 0x80, other transaction data chunk - p1: int = 0x00 - # P2: - # - 0x80, new transaction - # - 0x02, new transaction with segwit input - p2: int = 0x02 if is_new_transaction else 0x80 - - cdata: bytes = (tx.nVersion.to_bytes(4, byteorder="little") + - ser_compact_size(len(inputs))) - - yield self.serialize(cla=self.CLA, - ins=ins, - p1=p1, - p2=p2, - cdata=cdata) - - p1 = 0x80 - for i, (_, trusted_input) in enumerate(inputs): - script_sig: bytes = script if i == input_index else b"" - cdata = b"".join([ - b"\x01", # 0x01 for trusted input, 0x02 for witness, 0x00 otherwise - len(trusted_input).to_bytes(1, byteorder="big"), - trusted_input, - ser_compact_size(len(script_sig)) - ]) - - yield self.serialize(cla=self.CLA, - ins=ins, - p1=p1, - p2=p2, - cdata=cdata) - - yield self.serialize(cla=self.CLA, - ins=ins, - p1=p1, - p2=p2, - cdata=(script_sig + - 0xfffffffd.to_bytes(4, byteorder="little"))) - - def untrusted_hash_tx_input_finalize(self, - tx: CTransaction, - change_path: Optional[str] - ) -> Iterator[bytes]: - """Command builder for UNTRUSTED_HASH_TRANSACTION_INPUT_FINALIZE. - - Parameters - ---------- - tx: CTransaction - Transaction to sign. - change_path: Optional[str] - BIP32 path for the change. - - Yields - ------- - bytes - APDU command chunk for UNTRUSTED_HASH_TRANSACTION_INPUT_FINALIZE. - - """ - ins: InsType = InsType.UNTRUSTED_HASH_TRANSACTION_INPUT_FINALIZE - # P1: - # - 0x00, more input chunk to be sent - # - 0x80, last chunk to be sent - # - 0xFF, BIP32 path for the change address - p1: int - p2: int = 0x00 - - p1 = 0xFF - if change_path: - bip32_change_path: List[bytes] = bip32_path_from_string(change_path) - cdata: bytes = b"".join([ - len(bip32_change_path).to_bytes(1, byteorder="big"), - *bip32_change_path - ]) - yield self.serialize(cla=self.CLA, ins=ins, p1=p1, p2=p2, cdata=cdata) - else: - yield self.serialize(cla=self.CLA, ins=ins, p1=p1, p2=p2, cdata=b"\x00") - - vout_num = len(tx.vout) - p1 = 0x00 - yield self.serialize(cla=self.CLA, - ins=ins, - p1=p1, - p2=p2, - cdata=ser_compact_size(vout_num)) - - for i, ctxout in enumerate(tx.vout): - p1 = 0x00 if i < vout_num - 1 else 0x80 - yield self.serialize(cla=self.CLA, - ins=ins, - p1=p1, - p2=p2, - cdata=ctxout.serialize()) - - def untrusted_hash_sign(self, - sign_path: str, - lock_time: int = 0, - sig_hash: int = 1) -> bytes: - """Command builder for UNTRUSTED_HASH_SIGN. - - Parameters - ---------- - sign_path : str - BIP32 path to be used to sign. - lock_time : int - Block height or timestamp when transaction is final. - sig_hash : int - Either SIGHASH_ALL (0x01), SIGHASH_NONE (0x02) or SIGHASH_SINGLE (0x03). - Only SIGHASH_ALL (0x01) is supported. - - Returns - ------- - bytes - APDU command for UNTRUSTED_HASH_SIGN. - - """ - ins: InsType = InsType.UNTRUSTED_HASH_SIGN - p1: int = 0x00 - p2: int = 0x00 - - bip32_path: List[bytes] = bip32_path_from_string(sign_path) - cdata: bytes = b"".join([ - len(bip32_path).to_bytes(1, byteorder="big"), - *bip32_path, - b"\00", # unused (Reserved for Future Use) - lock_time.to_bytes(4, byteorder="big"), # /!\ big instead of little - sig_hash.to_bytes(1, byteorder="big") - ]) - - return self.serialize(cla=self.CLA, ins=ins, p1=p1, p2=p2, cdata=cdata) diff --git a/tests-legacy/bitcoin_client/bitcoin_utils.py b/tests-legacy/bitcoin_client/bitcoin_utils.py deleted file mode 100644 index 162d23dbd..000000000 --- a/tests-legacy/bitcoin_client/bitcoin_utils.py +++ /dev/null @@ -1,68 +0,0 @@ -import struct -from typing import List - -from bitcoin_client.hwi.serialization import CTransaction, hash256, ser_string - - -def bip143_digest(tx: CTransaction, - amount: int, - input_index: int, - sig_hash: int = 0x01) -> bytes: - hash_prev_outs: bytes = b"".join([ - txin.prevout.serialize() for txin in tx.vin - ]) - - hash_sequence: bytes = b"".join([ - struct.pack(" List[bytes]: - """Convert BIP32 path string to list of bytes.""" - splitted_path: List[str] = path.split("/") - - if "m" in splitted_path and splitted_path[0] == "m": - splitted_path = splitted_path[1:] - - return [int(p).to_bytes(4, byteorder="big") if "'" not in p - else (0x80000000 | int(p[:-1])).to_bytes(4, byteorder="big") - for p in splitted_path] - - -def compress_pub_key(pub_key: bytes) -> bytes: - """Convert uncompressed to compressed public key.""" - if pub_key[-1] & 1: - return b"\x03" + pub_key[1:33] - - return b"\x02" + pub_key[1:33] \ No newline at end of file diff --git a/tests-legacy/bitcoin_client/exception/__init__.py b/tests-legacy/bitcoin_client/exception/__init__.py deleted file mode 100644 index 4320abeb1..000000000 --- a/tests-legacy/bitcoin_client/exception/__init__.py +++ /dev/null @@ -1,64 +0,0 @@ -from .device_exception import DeviceException -from .errors import (AccessConditionNotFullfilledError, - AlgorithmNotSupportedError, - ClaNotSupportedError, - CodeBlockedError, - CodeNotInitializedError, - CommandIncompatibleFileStructureError, - ConditionOfUseNotSatisfiedError, - ContradictionInvalidationError, - ContradictionSecretCodeStatusError, - FileAlreadyExistsError, - FileNotFoundError, - GPAuthFailedError, - HaltedError, - InconsistentFileError, - IncorrectDataError, - IncorrectLengthError, - IncorrectP1P2Error, - InsNotSupportedError, - InvalidKCVError, - InvalidOffsetError, - LicensingError, - MaxValueReachedError, - MemoryProblemError, - NoEFSelectedError, - NotEnoughMemorySpaceError, - ReferencedDataNotFoundError, - SecurityStatusNotSatisfiedError, - SwapWithoutTrustedInputsError, - TechnicalProblemError, - UnknownDeviceError) - -__all__ = [ - "AccessConditionNotFullfilledError", - "AlgorithmNotSupportedError", - "ClaNotSupportedError", - "CodeBlockedError", - "CodeNotInitializedError", - "CommandIncompatibleFileStructureError", - "ConditionOfUseNotSatisfiedError", - "ContradictionInvalidationError", - "ContradictionSecretCodeStatusError", - "FileAlreadyExistsError", - "FileNotFoundError", - "GPAuthFailedError", - "HaltedError", - "InconsistentFileError", - "IncorrectDataError", - "IncorrectLengthError", - "IncorrectP1P2Error", - "InsNotSupportedError", - "InvalidKCVError", - "InvalidOffsetError", - "LicensingError", - "MaxValueReachedError", - "MemoryProblemError", - "NoEFSelectedError", - "NotEnoughMemorySpaceError", - "ReferencedDataNotFoundError", - "SecurityStatusNotSatisfiedError", - "SwapWithoutTrustedInputsError", - "TechnicalProblemError", - "UnknownDeviceError" -] diff --git a/tests-legacy/bitcoin_client/exception/device_exception.py b/tests-legacy/bitcoin_client/exception/device_exception.py deleted file mode 100644 index ce00193f8..000000000 --- a/tests-legacy/bitcoin_client/exception/device_exception.py +++ /dev/null @@ -1,53 +0,0 @@ -import enum -from typing import Dict, Any, Optional - -from .errors import * - - -class DeviceException(Exception): # pylint: disable=too-few-public-methods - exc: Dict[int, Any] = { - 0x6700: IncorrectLengthError, - 0x6981: CommandIncompatibleFileStructureError, - 0x6982: SecurityStatusNotSatisfiedError, - 0x6985: ConditionOfUseNotSatisfiedError, - 0x6A80: IncorrectDataError, - 0x6A84: NotEnoughMemorySpaceError, - 0x6A88: ReferencedDataNotFoundError, - 0x6A89: FileAlreadyExistsError, - 0x6A8A: SwapWithoutTrustedInputsError, - 0x6B00: IncorrectP1P2Error, - 0x6D00: InsNotSupportedError, - 0x6E00: ClaNotSupportedError, - 0x6F00: TechnicalProblemError, - 0x9240: MemoryProblemError, - 0x9400: NoEFSelectedError, - 0x9402: InvalidOffsetError, - 0x9404: FileNotFoundError, - 0x9408: InconsistentFileError, - 0x9484: AlgorithmNotSupportedError, - 0x9485: InvalidKCVError, - 0x9802: CodeNotInitializedError, - 0x9804: AccessConditionNotFullfilledError, - 0x9808: ContradictionSecretCodeStatusError, - 0x9810: ContradictionInvalidationError, - 0x9840: CodeBlockedError, - 0x9850: MaxValueReachedError, - 0x6300: GPAuthFailedError, - 0x6F42: LicensingError, - 0x6FAA: HaltedError - } - - def __new__(cls, - error_code: int, - ins: Optional[enum.IntEnum] = None, - message: str = "" - ) -> Any: - error_message: str = (f"Error in {ins!r} command" - if ins else "Error in command") - - if error_code in DeviceException.exc: - return DeviceException.exc[error_code](hex(error_code), - error_message, - message) - - return UnknownDeviceError(hex(error_code), error_message, message) diff --git a/tests-legacy/bitcoin_client/exception/errors.py b/tests-legacy/bitcoin_client/exception/errors.py deleted file mode 100644 index e19f4f733..000000000 --- a/tests-legacy/bitcoin_client/exception/errors.py +++ /dev/null @@ -1,118 +0,0 @@ -class UnknownDeviceError(Exception): - pass - - -class IncorrectLengthError(Exception): - pass - - -class CommandIncompatibleFileStructureError(Exception): - pass - - -class SecurityStatusNotSatisfiedError(Exception): - pass - - -class ConditionOfUseNotSatisfiedError(Exception): - pass - - -class IncorrectDataError(Exception): - pass - - -class NotEnoughMemorySpaceError(Exception): - pass - - -class ReferencedDataNotFoundError(Exception): - pass - - -class FileAlreadyExistsError(Exception): - pass - - -class SwapWithoutTrustedInputsError(Exception): - pass - - -class IncorrectP1P2Error(Exception): - pass - - -class InsNotSupportedError(Exception): - pass - - -class ClaNotSupportedError(Exception): - pass - - -class TechnicalProblemError(Exception): - pass - - -class MemoryProblemError(Exception): - pass - - -class NoEFSelectedError(Exception): - pass - - -class InvalidOffsetError(Exception): - pass - - -class FileNotFoundError(Exception): - pass - - -class InconsistentFileError(Exception): - pass - - -class AlgorithmNotSupportedError(Exception): - pass - - -class InvalidKCVError(Exception): - pass - - -class CodeNotInitializedError(Exception): - pass - - -class AccessConditionNotFullfilledError(Exception): - pass - - -class ContradictionSecretCodeStatusError(Exception): - pass - - -class ContradictionInvalidationError(Exception): - pass - - -class CodeBlockedError(Exception): - pass - - -class MaxValueReachedError(Exception): - pass - - -class GPAuthFailedError(Exception): - pass - - -class LicensingError(Exception): - pass - - -class HaltedError(Exception): - pass diff --git a/tests-legacy/bitcoin_client/hwi/base58.py b/tests-legacy/bitcoin_client/hwi/base58.py deleted file mode 100644 index e0df1152f..000000000 --- a/tests-legacy/bitcoin_client/hwi/base58.py +++ /dev/null @@ -1,110 +0,0 @@ -"""base58 module. - -Original source: git://github.com/joric/brutus.git -which was forked from git://github.com/samrushing/caesure.git - -Distributed under the MIT/X11 software license, see the accompanying -file COPYING or http://www.opensource.org/licenses/mit-license.php. - -""" - -import hashlib -from binascii import hexlify, unhexlify -from typing import List - - -b58_digits: str = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' - - -def sha256(s: bytes) -> bytes: - return hashlib.new('sha256', s).digest() - - -def hash256(s: bytes) -> bytes: - return sha256(sha256(s)) - - -def encode(b: bytes) -> str: - """Encode bytes to a base58-encoded string""" - - # Convert big-endian bytes to integer - n: int = int('0x0' + hexlify(b).decode('utf8'), 16) - - # Divide that integer into base58 - temp: List[str] = [] - while n > 0: - n, r = divmod(n, 58) - temp.append(b58_digits[r]) - res: str = ''.join(temp[::-1]) - - # Encode leading zeros as base58 zeros - czero: int = 0 - pad: int = 0 - for c in b: - if c == czero: - pad += 1 - else: - break - return b58_digits[0] * pad + res - - -def decode(s: str) -> bytes: - """Decode a base58-encoding string, returning bytes""" - if not s: - return b'' - - # Convert the string to an integer - n: int = 0 - for c in s: - n *= 58 - if c not in b58_digits: - raise ValueError('Character %r is not a valid base58 character' % c) - digit = b58_digits.index(c) - n += digit - - # Convert the integer to bytes - h: str = '%x' % n - if len(h) % 2: - h = '0' + h - res = unhexlify(h.encode('utf8')) - - # Add padding back. - pad = 0 - for c in s[:-1]: - if c == b58_digits[0]: - pad += 1 - else: - break - return b'\x00' * pad + res - - -def get_xpub_fingerprint(s: str) -> bytes: - data = decode(s) - fingerprint = data[5:9] - return fingerprint - - -def get_xpub_fingerprint_hex(xpub: str) -> str: - data = decode(xpub) - fingerprint = data[5:9] - return hexlify(fingerprint).decode() - - -def to_address(b: bytes, version: bytes) -> str: - data = version + b - checksum = hash256(data)[0:4] - data += checksum - return encode(data) - - -def xpub_to_pub_hex(xpub: str) -> str: - data = decode(xpub) - pubkey = data[-37:-4] - return hexlify(pubkey).decode() - - -def xpub_main_2_test(xpub: str) -> str: - data = decode(xpub) - test_data = b'\x04\x35\x87\xCF' + data[4:-4] - checksum = hash256(test_data)[0:4] - return encode(test_data + checksum) diff --git a/tests-legacy/bitcoin_client/hwi/bech32.py b/tests-legacy/bitcoin_client/hwi/bech32.py deleted file mode 100644 index 68f246874..000000000 --- a/tests-legacy/bitcoin_client/hwi/bech32.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2017 Pieter Wuille -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -"""Reference implementation for Bech32 and segwit addresses.""" - - -CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" - - -def bech32_polymod(values): - """Internal function that computes the Bech32 checksum.""" - generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] - chk = 1 - for value in values: - top = chk >> 25 - chk = (chk & 0x1ffffff) << 5 ^ value - for i in range(5): - chk ^= generator[i] if ((top >> i) & 1) else 0 - return chk - - -def bech32_hrp_expand(hrp): - """Expand the HRP into values for checksum computation.""" - return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] - - -def bech32_verify_checksum(hrp, data): - """Verify a checksum given HRP and converted data characters.""" - return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1 - - -def bech32_create_checksum(hrp, data): - """Compute the checksum values given HRP and data.""" - values = bech32_hrp_expand(hrp) + data - polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1 - return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] - - -def bech32_encode(hrp, data): - """Compute a Bech32 string given HRP and data values.""" - combined = data + bech32_create_checksum(hrp, data) - return hrp + '1' + ''.join([CHARSET[d] for d in combined]) - - -def bech32_decode(bech): - """Validate a Bech32 string, and determine HRP and data.""" - if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or - (bech.lower() != bech and bech.upper() != bech)): - return (None, None) - bech = bech.lower() - pos = bech.rfind('1') - if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: - return (None, None) - if not all(x in CHARSET for x in bech[pos + 1:]): - return (None, None) - hrp = bech[:pos] - data = [CHARSET.find(x) for x in bech[pos + 1:]] - if not bech32_verify_checksum(hrp, data): - return (None, None) - return (hrp, data[:-6]) - - -def convertbits(data, frombits, tobits, pad=True): - """General power-of-2 base conversion.""" - acc = 0 - bits = 0 - ret = [] - maxv = (1 << tobits) - 1 - max_acc = (1 << (frombits + tobits - 1)) - 1 - for value in data: - if value < 0 or (value >> frombits): - return None - acc = ((acc << frombits) | value) & max_acc - bits += frombits - while bits >= tobits: - bits -= tobits - ret.append((acc >> bits) & maxv) - if pad: - if bits: - ret.append((acc << (tobits - bits)) & maxv) - elif bits >= frombits or ((acc << (tobits - bits)) & maxv): - return None - return ret - - -def decode(hrp, addr): - """Decode a segwit address.""" - hrpgot, data = bech32_decode(addr) - if hrpgot != hrp: - return (None, None) - decoded = convertbits(data[1:], 5, 8, False) - if decoded is None or len(decoded) < 2 or len(decoded) > 40: - return (None, None) - if data[0] > 16: - return (None, None) - if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: - return (None, None) - return (data[0], decoded) - - -def encode(hrp, witver, witprog): - """Encode a segwit address.""" - ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) - if decode(hrp, ret) == (None, None): - return None - return ret diff --git a/tests-legacy/bitcoin_client/hwi/serialization.py b/tests-legacy/bitcoin_client/hwi/serialization.py deleted file mode 100644 index fc1cc6437..000000000 --- a/tests-legacy/bitcoin_client/hwi/serialization.py +++ /dev/null @@ -1,524 +0,0 @@ -"""Bitcoin Object Python Serializations. - -Copyright (c) 2010 ArtForz -- public domain half-a-node -Copyright (c) 2012 Jeff Garzik -Copyright (c) 2010-2016 The Bitcoin Core developers - -Distributed under the MIT software license, see the accompanying -file COPYING or http://www.opensource.org/licenses/mit-license.php. - -Modified from the test/test_framework/mininode.py file from the -Bitcoin repository - -CTransaction,CTxIn, CTxOut, etc....: - data structures that should map to corresponding structures in - bitcoin/primitives for transactions only -ser_*, deser_*: functions that handle serialization/deserialization - -""" - -import struct -import binascii -import hashlib -import copy -import base64 -from io import BytesIO -from typing import ( - List, - Optional, - Sequence, - Tuple, - TypeVar, - Callable -) -from typing_extensions import Protocol - - -class Readable(Protocol): - def read(self, n: int = -1) -> bytes: - ... - - -class Deserializable(Protocol): - def deserialize(self, f: Readable) -> None: - ... - - -class Serializable(Protocol): - def serialize(self) -> bytes: - ... - - -def sha256(s: bytes) -> bytes: - return hashlib.new('sha256', s).digest() - - -def ripemd160(s: bytes) -> bytes: - return hashlib.new('ripemd160', s).digest() - - -def hash256(s: bytes) -> bytes: - return sha256(sha256(s)) - - -def hash160(s: bytes) -> bytes: - return ripemd160(sha256(s)) - - -# Serialization/deserialization tools -def ser_compact_size(size: int) -> bytes: - r: bytes - if size < 253: - r = struct.pack("B", size) - elif size < 0x10000: - r = struct.pack(" int: - nit: int = struct.unpack(" bytes: - nit = deser_compact_size(f) - return f.read(nit) - - -def ser_string(s: bytes) -> bytes: - return ser_compact_size(len(s)) + s - - -def deser_uint256(f: Readable) -> int: - r = 0 - for i in range(8): - t = struct.unpack(" bytes: - rs = b"" - for _ in range(8): - rs += struct.pack(">= 32 - return rs - - -def uint256_from_str(s: bytes) -> int: - r = 0 - t = struct.unpack(" List[D]: - nit = deser_compact_size(f) - r = [] - for _ in range(nit): - t = c() - t.deserialize(f) - r.append(t) - return r - - -def ser_vector(v: Sequence[Serializable]) -> bytes: - r = ser_compact_size(len(v)) - for i in v: - r += i.serialize() - return r - - -def deser_string_vector(f: Readable) -> List[bytes]: - nit = deser_compact_size(f) - r = [] - for _ in range(nit): - t = deser_string(f) - r.append(t) - return r - - -def ser_string_vector(v: List[bytes]) -> bytes: - r = ser_compact_size(len(v)) - for sv in v: - r += ser_string(sv) - return r - - -def hex_to_base64(s: str) -> bytes: - return base64.b64encode(binascii.unhexlify(s)) - - -def ser_sig_der(r: bytes, s: bytes) -> bytes: - sig = b"\x30" - - # Make r and s as short as possible - ri = 0 - for b in r: - if b == 0: - ri += 1 - else: - break - r = r[ri:] - si = 0 - for b in s: - if b == 0: - si += 1 - else: - break - s = s[si:] - - # Make positive of neg - first = r[0] - if first & (1 << 7) != 0: - r = b"\x00" + r - first = s[0] - if first & (1 << 7) != 0: - s = b"\x00" + s - - # Write total length - total_len = len(r) + len(s) + 4 - sig += struct.pack("B", total_len) - - # write r - sig += b"\x02" - sig += struct.pack("B", len(r)) - sig += r - - # write s - sig += b"\x02" - sig += struct.pack("B", len(s)) - sig += s - - sig += b"\x01" - return sig - - -def ser_sig_compact(r: bytes, s: bytes, recid: bytes) -> bytes: - rec = struct.unpack("B", recid)[0] - prefix = struct.pack("B", 27 + 4 + rec) - - sig = b"" - sig += prefix - sig += r + s - - return sig - -# Objects that map to bitcoind objects, which can be serialized/deserialized - - -MSG_WITNESS_FLAG = 1 << 30 - - -class COutPoint(object): - def __init__(self, h: int = 0, n: int = 0xffffffff): - self.hash = h - self.n = n - - def deserialize(self, f: Readable) -> None: - self.hash = deser_uint256(f) - self.n = struct.unpack(" bytes: - r = b"" - r += ser_uint256(self.hash) - r += struct.pack(" str: - return "COutPoint(hash=%064x n=%i)" % (self.hash, self.n) - - -class CTxIn(object): - def __init__( - self, - outpoint: Optional[COutPoint] = None, - scriptSig: bytes = b"", - nSequence: int = 0, - ): - if outpoint is None: - self.prevout = COutPoint() - else: - self.prevout = outpoint - self.scriptSig = scriptSig - self.nSequence = nSequence - - def deserialize(self, f: Readable) -> None: - self.prevout = COutPoint() - self.prevout.deserialize(f) - self.scriptSig = deser_string(f) - self.nSequence = struct.unpack(" bytes: - r = b"" - r += self.prevout.serialize() - r += ser_string(self.scriptSig) - r += struct.pack(" str: - return "CTxIn(prevout=%s scriptSig=%s nSequence=%i)" \ - % (repr(self.prevout), self.scriptSig.hex(), - self.nSequence) - - -def is_p2sh(script: bytes) -> bool: - return len(script) == 23 and script[0] == 0xa9 and script[1] == 0x14 and script[22] == 0x87 - - -def is_p2pkh(script: bytes) -> bool: - return (len(script) == 25 and - script[0] == 0x76 and - script[1] == 0xa9 and - script[2] == 0x14 and - script[23] == 0x88 and - script[24] == 0xac) - - -def is_p2pk(script: bytes) -> bool: - return ((len(script) == 35 or len(script) == 67) and - (script[0] == 0x21 or script[0] == 0x41) and - script[-1] == 0xac) - - -def is_witness(script: bytes) -> Tuple[bool, int, bytes]: - if len(script) < 4 or len(script) > 42: - return False, 0, b"" - - if script[0] != 0 and (script[0] < 81 or script[0] > 96): - return False, 0, b"" - - if script[1] + 2 == len(script): - return True, script[0] - 0x50 if script[0] else 0, script[2:] - - return False, 0, b"" - - -def is_p2wpkh(script: bytes) -> bool: - is_wit, wit_ver, wit_prog = is_witness(script) - if not is_wit: - return False - elif wit_ver != 0: - return False - return len(wit_prog) == 20 - - -def is_p2wsh(script: bytes) -> bool: - is_wit, wit_ver, wit_prog = is_witness(script) - if not is_wit: - return False - elif wit_ver != 0: - return False - return len(wit_prog) == 32 - - -class CTxOut(object): - def __init__(self, nValue: int = 0, scriptPubKey: bytes = b""): - self.nValue = nValue - self.scriptPubKey = scriptPubKey - - def deserialize(self, f: Readable) -> None: - self.nValue = struct.unpack(" bytes: - r = b"" - r += struct.pack(" bool: - return is_p2sh(self.scriptPubKey) - - def is_p2pkh(self) -> bool: - return is_p2pkh(self.scriptPubKey) - - def is_p2pk(self) -> bool: - return is_p2pk(self.scriptPubKey) - - def is_witness(self) -> Tuple[bool, int, bytes]: - return is_witness(self.scriptPubKey) - - def __repr__(self) -> str: - return "CTxOut(nValue=%i.%08i scriptPubKey=%s)" \ - % (self.nValue, self.nValue, self.scriptPubKey.hex()) - - -class CScriptWitness(object): - def __init__(self) -> None: - # stack is a vector of strings - self.stack: List[bytes] = [] - - def __repr__(self) -> str: - return "CScriptWitness(%s)" % \ - (",".join([x.hex() for x in self.stack])) - - def is_null(self) -> bool: - if self.stack: - return False - return True - - -class CTxInWitness(object): - def __init__(self) -> None: - self.scriptWitness = CScriptWitness() - - def deserialize(self, f: Readable) -> None: - self.scriptWitness.stack = deser_string_vector(f) - - def serialize(self) -> bytes: - return ser_string_vector(self.scriptWitness.stack) - - def __repr__(self) -> str: - return repr(self.scriptWitness) - - def is_null(self) -> bool: - return self.scriptWitness.is_null() - - -class CTxWitness(object): - def __init__(self) -> None: - self.vtxinwit: List[CTxInWitness] = [] - - def deserialize(self, f: Readable) -> None: - for i in range(len(self.vtxinwit)): - self.vtxinwit[i].deserialize(f) - - def serialize(self) -> bytes: - r = b"" - # This is different than the usual vector serialization -- - # we omit the length of the vector, which is required to be - # the same length as the transaction's vin vector. - for x in self.vtxinwit: - r += x.serialize() - return r - - def __repr__(self) -> str: - return "CTxWitness(%s)" % \ - (';'.join([repr(x) for x in self.vtxinwit])) - - def is_null(self) -> bool: - for x in self.vtxinwit: - if not x.is_null(): - return False - return True - - -class CTransaction(object): - def __init__(self, tx: Optional['CTransaction'] = None) -> None: - if tx is None: - self.nVersion = 1 - self.vin: List[CTxIn] = [] - self.vout: List[CTxOut] = [] - self.wit = CTxWitness() - self.nLockTime = 0 - self.sha256: Optional[int] = None - self.hash: Optional[str] = None - else: - self.nVersion = tx.nVersion - self.vin = copy.deepcopy(tx.vin) - self.vout = copy.deepcopy(tx.vout) - self.nLockTime = tx.nLockTime - self.sha256 = tx.sha256 - self.hash = tx.hash - self.wit = copy.deepcopy(tx.wit) - - def deserialize(self, f: Readable) -> None: - self.nVersion = struct.unpack(" bytes: - r = b"" - r += struct.pack(" bytes: - flags = 0 - if not self.wit.is_null(): - flags |= 1 - r = b"" - r += struct.pack(" bytes: - return self.serialize_without_witness() - - # Recalculate the txid (transaction hash without witness) - def rehash(self) -> None: - self.sha256 = None - self.calc_sha256() - - # We will only cache the serialization without witness in - # self.sha256 and self.hash -- those are expected to be the txid. - def calc_sha256(self, with_witness: bool = False) -> Optional[int]: - if with_witness: - # Don't cache the result, just return it - return uint256_from_str(hash256(self.serialize_with_witness())) - - if self.sha256 is None: - self.sha256 = uint256_from_str(hash256(self.serialize_without_witness())) - self.hash = hash256(self.serialize())[::-1].hex() - return None - - def is_null(self) -> bool: - return len(self.vin) == 0 and len(self.vout) == 0 - - @classmethod - def from_bytes(cls, b: bytes): - tx = cls() - tx.deserialize(BytesIO(b)) - - return tx - - def __repr__(self) -> str: - return "CTransaction(nVersion=%i vin=%s vout=%s wit=%s nLockTime=%i)" \ - % (self.nVersion, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime) diff --git a/tests-legacy/bitcoin_client/utils.py b/tests-legacy/bitcoin_client/utils.py deleted file mode 100644 index 1027127db..000000000 --- a/tests-legacy/bitcoin_client/utils.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Tuple, Iterator - - -MAX_APDU_LEN: int = 255 - - -def chunkify(data: bytes, chunk_len: int) -> Iterator[Tuple[bool, bytes]]: - """Split `data` into chunk of length `chunk_len`.`""" - size: int = len(data) - - if size <= chunk_len: - yield True, data - return - - chunk: int = size // chunk_len - remaining: int = size % chunk_len - offset: int = 0 - - for i in range(chunk): - yield False, data[offset:offset + chunk_len] - offset += chunk_len - - if remaining: - yield True, data[offset:] - - -def deser_trusted_input(trusted_input: bytes - ) -> Tuple[int, int, bytes, bytes, int, int, bytes]: - """Deserialize trusted input into 7 items.""" - assert len(trusted_input) == 56 - - offset: int = 0 - magic_trusted_input: int = trusted_input[offset] - assert magic_trusted_input == 0x32 - offset += 1 - zero: int = trusted_input[offset] - assert zero == 0x00 - offset += 1 - random: bytes = trusted_input[offset:offset + 2] - offset += 2 - prev_txid: bytes = trusted_input[offset:offset + 32] - offset += 32 - out_index: int = int.from_bytes(trusted_input[offset:offset + 4], - byteorder="little") - offset += 4 - amount: int = int.from_bytes(trusted_input[offset:offset + 8], - byteorder="little") - offset += 8 - hmac: bytes = trusted_input[offset:offset + 8] - offset += 8 - - assert offset == len(trusted_input) - - return (magic_trusted_input, zero, random, - prev_txid, out_index, amount, hmac) diff --git a/tests-legacy/clean_tests.sh b/tests-legacy/clean_tests.sh deleted file mode 100755 index 83b9187b3..000000000 --- a/tests-legacy/clean_tests.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -rm -rf bitcoin-bin -rm -rf bitcoin-testnet-bin diff --git a/tests-legacy/conftest.py b/tests-legacy/conftest.py deleted file mode 100644 index c9d55113e..000000000 --- a/tests-legacy/conftest.py +++ /dev/null @@ -1,92 +0,0 @@ -import subprocess -import os -import socket -import time -import logging -import pytest - -from ledgercomm import Transport - -from bitcoin_client.bitcoin_cmd import BitcoinCommand - - -logging.basicConfig(level=logging.INFO) - - -def pytest_addoption(parser): - parser.addoption("--hid", - action="store_true") - - -@pytest.fixture -def hid(pytestconfig): - return pytestconfig.getoption("hid") - - -@pytest.fixture -def device(request, hid): - # If running on real hardware, nothing to do here - if hid: - yield - return - - # Gets the speculos executable from the SPECULOS environment variable, - # or hopes that "speculos.py" is in the $PATH if not set - speculos_executable = os.environ.get("SPECULOS", "speculos.py") - - lib_path = "./bitcoin-bin/app.elf" - if os.path.isfile(lib_path): - print("Bitcoin app running with library") - lib_arg = ["-l", f"Bitcoin:{lib_path}"] - else: - print("Bitcoin app running as native application") - lib_arg = [] - - base_args = [ - speculos_executable, "./bitcoin-testnet-bin/app.elf", - *lib_arg, - "--sdk", "2.0", - "--display", "headless" - ] - - # Look for the automation_file attribute in the test function, if present - try: - automation_args = ["--automation", f"file:{request.function.automation_file}"] - except AttributeError: - automation_args = [] - - speculos_proc = subprocess.Popen([*base_args, *automation_args]) - - # Attempts to connect to speculos to make sure that it's ready when the test starts - for _ in range(100): - try: - socket.create_connection(("127.0.0.1", 9999), timeout=1.0) - connected = True - break - except ConnectionRefusedError: - time.sleep(0.1) - connected = False - - if not connected: - raise RuntimeError("Unable to connect to speculos.") - - yield - - speculos_proc.terminate() - speculos_proc.wait() - - -@pytest.fixture -def transport(device, hid): - transport = (Transport(interface="hid", debug=True) - if hid else Transport(interface="tcp", - server="127.0.0.1", - port=9999, - debug=True)) - yield transport - transport.close() - - -@pytest.fixture -def cmd(transport): - return BitcoinCommand(transport=transport, debug=False) diff --git a/tests-legacy/data/many-to-many/p2pkh/apdu_debug.log b/tests-legacy/data/many-to-many/p2pkh/apdu_debug.log deleted file mode 100644 index ac5e51b09..000000000 --- a/tests-legacy/data/many-to-many/p2pkh/apdu_debug.log +++ /dev/null @@ -1,446 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -F026000000 -New APDU received: -E0C4000000 -New APDU received: -E024000000 -New APDU received: -E02280000130 -New APDU received: -E04000000100 -pin ok -Using private component -34AC5D784EBB4DF4727BCDDF6A6743F5D5D46D83DD74AA825866390C694F2938 -To hash -0251EC84E33A3119486461A44240E906FF94BF40CF807B025B1CA43332B80DC9DB -Hash160 -F5ACC2FD5C60B7263C4A459541C1473BC29E58F4 -Checksum -A21FB2F3 -Length to encode 25 -To encode -6FF5ACC2FD5C60B7263C4A459541C1473BC29E58F4A21FB2F3 -Length encoded 34 -Encoded -6E3375786F6B3843337A566277476A72584476344E544E516A313865335A4D4A426B -Length 34 -New APDU received: -E0400000050180000000 -pin ok -Using private component -C71AE2E616FBCCC325A71600E9C82816505A6373EFCE4CAEDA60400667E0B75C -To hash -02E5646066C2C51CEAEA66F04EC85858D34E2BD285F4A60B1402BFC03CF462E767 -Hash160 -7D825341D36FC1297A41A9839A72975815B1FE61 -Checksum -E01B4FD2 -Length to encode 25 -To encode -6F7D825341D36FC1297A41A9839A72975815B1FE61E01B4FD2 -Length encoded 34 -Encoded -6D727861744272776D555959504E774E4879334A5461567350474750716A4A68674D -Length 34 -New APDU received: -E040000009028000002C80000001 -pin ok -Using private component -D0A20FEE4287C3C34ED5C30030FED4E87A6D4A86C87F8281091482BE76E266AD -To hash -03AC9DC20F0C2E9483FBDF9060E8154A19E38B1A886E0883B1E5E7C5582D56A638 -Hash160 -7CAC5506C90EF8E1F7D8928BDC0E3DC0DA4CD714 -Checksum -0EE9DF4F -Length to encode 25 -To encode -6F7CAC5506C90EF8E1F7D8928BDC0E3DC0DA4CD7140EE9DF4F -Length encoded 34 -Encoded -6D727441586771544634423463736770507972663639336455374C3256766A5A787A -Length 34 -New APDU received: -E04000000D038000002C8000000180000000 -pin ok -Using private component -F2D80B9A6B840D409622DB675EE45A2ED8051029D298F847AA8905BEC7967453 -To hash -03E84C7F4B7662FAED9F5EB2D812D9B7BCF0E0BF2A33B17AC45E38B8459669C321 -Hash160 -0812D36245DAC14478722DB30D66285EB115661A -Checksum -E53861E3 -Length to encode 25 -To encode -6F0812D36245DAC14478722DB30D66285EB115661AE53861E3 -Length encoded 34 -Encoded -6D6746654664344D42345A6B566257644647546156356956693369426668775A617A -Length 34 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000000200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E042800025F4C2B478DC9CEA2DF43C8BB94938FF43F3BED5AEB3AF9E36CDB7016104626BF2000000006B -Process input ---- ADD TO HASH FULL: -F4C2B478DC9CEA2DF43C8BB94938FF43F3BED5AEB3AF9E36CDB7016104626BF200000000 ---- ADD TO HASH FULL: -6B -Script to read 107 -Process input script, remaining 107 -New APDU received: -E04280006F483045022100B7B8244B3F83648397FA3C37349ED1827BF40BFCA22D4F87CC019D3FDED63E1D02205434CE222DE35E04AB98BE54AF57D2C8B464F0DA76A0275443B33C7B1DF79344012103462F1E48396CE2A4D7694D107FEF5123FE4D1E394394A370FA4901E744B4884EFDFFFFFF -Process input script, remaining 107 ---- ADD TO HASH FULL: -483045022100B7B8244B3F83648397FA3C37349ED1827BF40BFCA22D4F87CC019D3FDED63E1D02205434CE222DE35E04AB98BE54AF57D2C8B464F0DA76A0275443B33C7B1DF79344012103462F1E48396CE2A4D7694D107FEF5123FE4D1E394394A370FA4901E744B488 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -4E ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009DCDB06000000000019 ---- ADD TO HASH FULL: -DCDB060000000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process output script, remaining 25 -New APDU received: -E04280001976A914B242BA04B8526B533B17B168D27EAF0DA7BACB8588AC -Process output script, remaining 25 ---- ADD TO HASH FULL: -76A914B242BA04B8526B533B17B168D27EAF0DA7BACB8588AC -Process output script, remaining 0 -New APDU received: -E042800009706408000000000019 -Process output script, remaining 0 ---- ADD TO HASH FULL: -7064080000000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process output script, remaining 25 -New APDU received: -E04280001976A91426C680EC6AC9C0ED760177F92096F1C5571B72EF88AC -Process output script, remaining 25 ---- ADD TO HASH FULL: -76A91426C680EC6AC9C0ED760177F92096F1C5571B72EF88AC -Process output script, remaining 0 -New APDU received: -E04280000420041D00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -20041D00 -Transaction parsed -New APDU received: -E042000009000000010200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E0428000255DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E34010000006B -Process input ---- ADD TO HASH FULL: -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3401000000 ---- ADD TO HASH FULL: -6B -Script to read 107 -Process input script, remaining 107 -New APDU received: -E04280006F483045022100C806957429935C5D61F548223C3536DC739FE351814BCE9EDC1694E5A5C25ABF022013E307B0B4885ACB64CCFFAF5F4794F205040325EBC36C258787C488B038C2BB0121038BA462D7DD65917ECE454142903BB362A0B412D1841D52087AD881646B6CDCCFFDFFFFFF -Process input script, remaining 107 ---- ADD TO HASH FULL: -483045022100C806957429935C5D61F548223C3536DC739FE351814BCE9EDC1694E5A5C25ABF022013E307B0B4885ACB64CCFFAF5F4794F205040325EBC36C258787C488B038C2BB0121038BA462D7DD65917ECE454142903BB362A0B412D1841D52087AD881646B6CDC -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -CF ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009140C03000000000019 ---- ADD TO HASH FULL: -140C030000000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process output script, remaining 25 -New APDU received: -E04280001976A914B3E17C4DEF8A39148ED6FAD7DC6FEBE30DD324CB88AC -Process output script, remaining 25 ---- ADD TO HASH FULL: -76A914B3E17C4DEF8A39148ED6FAD7DC6FEBE30DD324CB88AC -Process output script, remaining 0 -New APDU received: -E042800009305705000000000019 -Process output script, remaining 0 ---- ADD TO HASH FULL: -3057050000000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process output script, remaining 25 -New APDU received: -E04280001976A914E0A89380635F18B7EE09546D963232BCB992412B88AC -Process output script, remaining 25 ---- ADD TO HASH FULL: -76A914E0A89380635F18B7EE09546D963232BCB992412B88AC -Process output script, remaining 0 -New APDU received: -E042800004FE041D00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -FE041D00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440000050200000002 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH AUTH: -02000000 ---- ADD TO HASH FULL: -02 ---- ADD TO HASH AUTH: -02 -Number of inputs : 2 -Process input -New APDU received: -E04480003B013832005E0D5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000DCDB060000000000D9A1FF455D570FF519 -Process input -====> Input HMAC: D9A1FF455D570FF5 -====> Computed HMAC: D9A1FF455D570FF5 -Trusted input hash -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000 ---- ADD TO HASH FULL: -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000 ---- ADD TO HASH AUTH: -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000 -Adding amount -DCDB060000000000 -New amount -000000000006DBDC ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914B242BA04B8526B533B17B168D27EAF0DA7BACB8588ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A914B242BA04B8526B533B17B168D27EAF0DA7BACB8588 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC ---- ADD TO HASH FULL: -FDFFFFFF ---- ADD TO HASH AUTH: -FDFFFFFF -Process input -New APDU received: -E04480003B01383200BA0857F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE010000003057050000000000124EAC0C193C39F400 -Process input -====> Input HMAC: 124EAC0C193C39F4 -====> Computed HMAC: 124EAC0C193C39F4 -Trusted input hash -57F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE01000000 ---- ADD TO HASH FULL: -57F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE01000000 ---- ADD TO HASH AUTH: -57F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE01000000 -Adding amount -3057050000000000 -New amount -00000000000C330C ---- ADD TO HASH FULL: -00 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E044800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF ---- ADD TO HASH AUTH: -FDFFFFFF -Process input -Input hashing done -Presign ready -New APDU received: -E04AFF0015058000002C80000001800000000000000100000002 -state=1 -Using private component -B35419B8D5EE1629A70718308955F5E8B736EC0C843FB2BEDB94940A29ADC18C -New APDU received: -E04A000032025C900400000000001976A914D8BB03D84A3AA993AE6DB1114488488DAB43D6DB88AC20A10700000000001976A914CBAE5B -state=1 ---- ADD TO HASH FULL: -025C900400000000001976A914D8BB03D84A3AA993AE6DB1114488488DAB43D6DB88AC20A10700000000001976A914CBAE5B ---- ADD TO HASH AUTH: -025C900400000000001976A914D8BB03D84A3AA993AE6DB1114488488DAB43D6DB88AC20A10700000000001976A914CBAE5B -New APDU received: -E04A80001350CF939E6F531B8A6B7ABD788FE14B029788AC -state=2 ---- ADD TO HASH FULL: -50CF939E6F531B8A6B7ABD788FE14B029788AC ---- ADD TO HASH AUTH: -50CF939E6F531B8A6B7ABD788FE14B029788AC -Checksum -21F8E07E -Length to encode 25 -To encode -6FCBAE5B50CF939E6F531B8A6B7ABD788FE14B029721F8E07E -Length encoded 34 -Encoded -6D7A35764C57644D3177485647536D58556B684B56765A624A32673465704D58536D -New APDU received: -E04800001B058000002C8000000180000000000000010000000000001D04D901 ---- ADD TO HASH FULL: -D9041D0001000000 -Using private component -AAEAB6598E3F057858A97DBDC8E9291F6EBECBB5D5D68BE0F83B623BA6EA3471 -Hash1 -B7DC3C28B80673141EFF6523DEFC02C054F7E6F8B7D977FB4490BBAEA2613E23 -Hash2 -C16F948104A1637627CD46DDB28D454C65A58B497D725A30081074BC632DD0A7 -New APDU received: -E0440080050200000002 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH AUTH: -02000000 ---- ADD TO HASH FULL: -02 ---- ADD TO HASH AUTH: -02 -Number of inputs : 2 -Process input -New APDU received: -E04480003B013832005E0D5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000DCDB060000000000D9A1FF455D570FF500 -Process input -====> Input HMAC: D9A1FF455D570FF5 -====> Computed HMAC: D9A1FF455D570FF5 -Trusted input hash -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000 ---- ADD TO HASH FULL: -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000 ---- ADD TO HASH AUTH: -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3400000000 -Adding amount -DCDB060000000000 -New amount -000000000006DBDC ---- ADD TO HASH FULL: -00 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E044800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF ---- ADD TO HASH AUTH: -FDFFFFFF -Process input -New APDU received: -E04480003B01383200BA0857F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE010000003057050000000000124EAC0C193C39F419 -Process input -====> Input HMAC: 124EAC0C193C39F4 -====> Computed HMAC: 124EAC0C193C39F4 -Trusted input hash -57F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE01000000 ---- ADD TO HASH FULL: -57F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE01000000 ---- ADD TO HASH AUTH: -57F891351E60E944718F1DF12B4F77B642CC63146184BEDC6C7BC64A0C0B39DE01000000 -Adding amount -3057050000000000 -New amount -00000000000C330C ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914E0A89380635F18B7EE09546D963232BCB992412B88ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A914E0A89380635F18B7EE09546D963232BCB992412B88 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC ---- ADD TO HASH FULL: -FDFFFFFF ---- ADD TO HASH AUTH: -FDFFFFFF -Process input -Input hashing done -Presign ready -New APDU received: -E04AFF0015058000002C80000001800000000000000100000002 -state=1 -New APDU received: -E04A000032025C900400000000001976A914D8BB03D84A3AA993AE6DB1114488488DAB43D6DB88AC20A10700000000001976A914CBAE5B -state=1 ---- ADD TO HASH FULL: -025C900400000000001976A914D8BB03D84A3AA993AE6DB1114488488DAB43D6DB88AC20A10700000000001976A914CBAE5B ---- ADD TO HASH AUTH: -025C900400000000001976A914D8BB03D84A3AA993AE6DB1114488488DAB43D6DB88AC20A10700000000001976A914CBAE5B -New APDU received: -E04A80001350CF939E6F531B8A6B7ABD788FE14B029788AC -state=1 ---- ADD TO HASH FULL: -50CF939E6F531B8A6B7ABD788FE14B029788AC ---- ADD TO HASH AUTH: -50CF939E6F531B8A6B7ABD788FE14B029788AC -New APDU received: -E04800001B058000002C8000000180000000000000000000000300001D04D901 ---- ADD TO HASH FULL: -D9041D0001000000 -Using private component -BC8CF06B27565698FBD1BEE909B761B17AF6D137D2CAA6ECEA6D45FC60C0DD5A -Hash1 -357754D0F6F0755BBAB62C6567F70A85A46AB5C4C7EE43CE52931B15E9837D69 -Hash2 -3C34D939F56D25E9636E1584D31C6D7E315F5BB28BD6071220978F3B10BD27B0 diff --git a/tests-legacy/data/many-to-many/p2pkh/tx.json b/tests-legacy/data/many-to-many/p2pkh/tx.json deleted file mode 100644 index ce38b5b4d..000000000 --- a/tests-legacy/data/many-to-many/p2pkh/tx.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "txid": "6eb7ece9e847574ca460414b6c2db535cec29ba803cd79d2c0718bd66c289fba", - "raw": "02000000025df7b9e20c52d1da2701a244a49754215266f016f6bd77f640a5200cd1fb7e34000000006a4730440220186ec195f04cfedc62d6d383a855e4b0fec550efedd22c177d2031b293556c16022039f05b1b8cc40c48d07323fdf3cc0453909d54409551c7ddc259210b2862bc54012103ed3cf038f00b7ad1c3998e66cf22a688ab5dbaed8400784cbf962d54cd42c2bffdffffff57f891351e60e944718f1df12b4f77b642cc63146184bedc6c7bc64a0c0b39de010000006a47304402201592969826e01baba12c9833584e0e958c1562736192bd1264d079383e8f277902204d5aafb9566a6de419b9446536ccce62f750361530d2a8f9a3bbc5e47af685900121035879ca173a9c1b3f300ec587fb4cc6d54d618e30584e425c1b53b98828708f1dfdffffff025c900400000000001976a914d8bb03d84a3aa993ae6db1114488488dab43d6db88ac20a10700000000001976a914cbae5b50cf939e6f531b8a6b7abd788fe14b029788acd9041d00", - "amount": 500000, - "fees": 400, - "to": "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm", - "sign_paths": ["m/84'/1'/0'/1/0", "m/84'/1'/0'/0/3"], - "change_path": "m/84'/1'/0'/1/2", - "lock_time": 1901785, - "utxos": [ - { - "txid": "347efbd10c20a540f677bdf616f06652215497a444a20127dad1520ce2b9f75d", - "raw": "0200000001f4c2b478dc9cea2df43c8bb94938ff43f3bed5aeb3af9e36cdb7016104626bf2000000006b483045022100b7b8244b3f83648397fa3c37349ed1827bf40bfca22d4f87cc019d3fded63e1d02205434ce222de35e04ab98be54af57d2c8b464f0da76a0275443b33c7b1df79344012103462f1e48396ce2a4d7694d107fef5123fe4d1e394394a370fa4901e744b4884efdffffff02dcdb0600000000001976a914b242ba04b8526b533b17b168d27eaf0da7bacb8588ac70640800000000001976a91426c680ec6ac9c0ed760177f92096f1c5571b72ef88ac20041d00", - "output_indexes": [0], - "output_amounts": [449500] - }, - { - "txid": "de390b0c4ac67b6cdcbe84611463cc42b6774f2bf11d8f7144e9601e3591f857", - "raw": "02000000015df7b9e20c52d1da2701a244a49754215266f016f6bd77f640a5200cd1fb7e34010000006b483045022100c806957429935c5d61f548223c3536dc739fe351814bce9edc1694e5a5c25abf022013e307b0b4885acb64ccffaf5f4794f205040325ebc36c258787c488b038c2bb0121038ba462d7dd65917ece454142903bb362a0b412d1841d52087ad881646b6cdccffdffffff02140c0300000000001976a914b3e17c4def8a39148ed6fad7dc6febe30dd324cb88ac30570500000000001976a914e0a89380635f18b7ee09546d963232bcb992412b88acfe041d00", - "output_indexes": [1], - "output_amounts": [350000] - } - ] - -} diff --git a/tests-legacy/data/many-to-many/p2sh-p2wpkh/apdu_debug.log b/tests-legacy/data/many-to-many/p2sh-p2wpkh/apdu_debug.log deleted file mode 100644 index 053cd54d8..000000000 --- a/tests-legacy/data/many-to-many/p2sh-p2wpkh/apdu_debug.log +++ /dev/null @@ -1,359 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000000200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E042800025C3945B596FD88FD0D3031A2DF8963A68ADF9E9F0DFEF4F3C4DD72DF7D80777AE0000000017 -Process input ---- ADD TO HASH FULL: -C3945B596FD88FD0D3031A2DF8963A68ADF9E9F0DFEF4F3C4DD72DF7D80777AE00000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process input script, remaining 23 -New APDU received: -E04280001B16001485D33A279D6AE91E59E0C43DE832FD4C3398252DFDFFFFFF -Process input script, remaining 23 ---- ADD TO HASH FULL: -16001485D33A279D6AE91E59E0C43DE832FD4C339825 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -2D ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009909F07000000000017 ---- ADD TO HASH FULL: -909F070000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A914F0464D9FA0EA42D80E4D5F1457883982E23B8EEC87 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A914F0464D9FA0EA42D80E4D5F1457883982E23B8EEC87 -Process output script, remaining 0 -New APDU received: -E04280000920A107000000000017 -Process output script, remaining 0 ---- ADD TO HASH FULL: -20A1070000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A91423E6F63C476F49E8ACF6AA583A0FDEC7B8ACA64587 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A91423E6F63C476F49E8ACF6AA583A0FDEC7B8ACA64587 -Process output script, remaining 0 -New APDU received: -E04280000400041D00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -00041D00 -Transaction parsed -New APDU received: -E042000009000000010200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E042800025C3945B596FD88FD0D3031A2DF8963A68ADF9E9F0DFEF4F3C4DD72DF7D80777AE0000000017 -Process input ---- ADD TO HASH FULL: -C3945B596FD88FD0D3031A2DF8963A68ADF9E9F0DFEF4F3C4DD72DF7D80777AE00000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process input script, remaining 23 -New APDU received: -E04280001B16001485D33A279D6AE91E59E0C43DE832FD4C3398252DFDFFFFFF -Process input script, remaining 23 ---- ADD TO HASH FULL: -16001485D33A279D6AE91E59E0C43DE832FD4C339825 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -2D ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009909F07000000000017 ---- ADD TO HASH FULL: -909F070000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A914F0464D9FA0EA42D80E4D5F1457883982E23B8EEC87 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A914F0464D9FA0EA42D80E4D5F1457883982E23B8EEC87 -Process output script, remaining 0 -New APDU received: -E04280000920A107000000000017 -Process output script, remaining 0 ---- ADD TO HASH FULL: -20A1070000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A91423E6F63C476F49E8ACF6AA583A0FDEC7B8ACA64587 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A91423E6F63C476F49E8ACF6AA583A0FDEC7B8ACA64587 -Process output script, remaining 0 -New APDU received: -E04280000400041D00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -00041D00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440002050200000002 -Init transaction parser -Number of inputs : 2 -Process input -New APDU received: -E04480003B01383200F97C53D3C0B27291BFFBF09D6EF5BD4F6051ED911914F55BEF0E3315B258BB13108900000000909F07000000000045E1BEBC7832647019 -Process input -Trusted input used in segwit mode -====> Input HMAC: 45E1BEBC78326470 -====> Computed HMAC: 45E1BEBC78326470 -Adding amount -909F070000000000 -New amount -0000000000079F90 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914CB078087EFF485AAA2260E94A53D7D6D1C5DD15188ACFDFFFFFF -Process input script, remaining 25 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -FDFFFFFF -Process input -New APDU received: -E04480003B01383200F2B153D3C0B27291BFFBF09D6EF5BD4F6051ED911914F55BEF0E3315B258BB1310890100000020A10700000000005B710DFE9F3F308D00 -Process input -Trusted input used in segwit mode -====> Input HMAC: 5B710DFE9F3F308D -====> Computed HMAC: 5B710DFE9F3F308D -Adding amount -20A1070000000000 -New amount -00000000000F40B0 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E044800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done ---- ADD TO HASH FULL: -82D397CBBCFF87BC5D0C4C70E424F9B830EFBAD7BF0BE479DA5D1D1BAFDB9798 -hashPrevout -4E7FBD6CBE534923DBDC2E194612F7040271013133C736C5AE3DFC0CF1034370 -hashSequence -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 -Presign ready -New APDU received: -E04AFF0015058000003180000001800000000000000100000001 -state=1 -Using private component -B2E6AA60D0137A93FF51ABE4FCA1512EF016222473B454CA9A4B4B9C8AB8402C -New APDU received: -E04A00003202840A03000000000017A914FFC91A30E33FC6D6ECFF42E4D9BD6C7E115D84988700350C000000000017A9142F5864A8ACD2 -state=1 ---- ADD TO HASH FULL: -840A03000000000017A914FFC91A30E33FC6D6ECFF42E4D9BD6C7E115D84988700350C000000000017A9142F5864A8ACD2 -New APDU received: -E04A80000F3FA85977D73E9AC30FD6B341B78C87 -state=2 ---- ADD TO HASH FULL: -3FA85977D73E9AC30FD6B341B78C87 -hashOutputs -B3B2A31981BCC4F256B0851EBE9D7A11BFBFA8559E7795190B4472D7E1E974C9 -Auth Hash: -E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -Checksum -CD4FBEC6 -Length to encode 25 -To encode -C42F5864A8ACD23FA85977D73E9AC30FD6B341B78CCD4FBEC6 -Length encoded 35 -Encoded -324D775A5A6479336243764568695073427963726543466971774468796B4163574E39 -Segwit parsed once -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -4E7FBD6CBE534923DBDC2E194612F7040271013133C736C5AE3DFC0CF1034370 -SEGWIT HashedSequence -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -4E7FBD6CBE534923DBDC2E194612F7040271013133C736C5AE3DFC0CF1034370 ---- ADD TO HASH FULL: -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH AUTH: -4E7FBD6CBE534923DBDC2E194612F7040271013133C736C5AE3DFC0CF1034370957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098B3B2A31981BCC4F256B0851EBE9D7A11BFBFA8559E7795190B4472D7E1E974C9 -Number of inputs : 1 -Process input -New APDU received: -E04480003B01383200F97C53D3C0B27291BFFBF09D6EF5BD4F6051ED911914F55BEF0E3315B258BB13108900000000909F07000000000045E1BEBC7832647019 -Process input -Trusted input used in segwit mode -====> Input HMAC: 45E1BEBC78326470 -====> Computed HMAC: 45E1BEBC78326470 ---- ADD TO HASH FULL: -53D3C0B27291BFFBF09D6EF5BD4F6051ED911914F55BEF0E3315B258BB13108900000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914CB078087EFF485AAA2260E94A53D7D6D1C5DD15188ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A914CB078087EFF485AAA2260E94A53D7D6D1C5DD15188 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -909F070000000000 ---- ADD TO HASH FULL: -909F070000000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -B3B2A31981BCC4F256B0851EBE9D7A11BFBFA8559E7795190B4472D7E1E974C9 -Sign ready -New APDU received: -E04800001B05800000318000000180000000000000010000000000001D03D001 ---- ADD TO HASH FULL: -D0031D0001000000 -Using private component -E8AE81F93160D032A7AA82BE9B719499A5DE635639D7237B7AB2572DE86F7E52 -Hash1 -01BB2AB227D2D0978E01A7212F1C7C5BC6FA848A8D46446A8242B3B00A7EF9BC -Hash2 -86B03281CD802D9764FFAB2E1F0E4B6825FA001899924C1DB1674C3A1D51054A -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -4E7FBD6CBE534923DBDC2E194612F7040271013133C736C5AE3DFC0CF1034370 -SEGWIT HashedSequence -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -4E7FBD6CBE534923DBDC2E194612F7040271013133C736C5AE3DFC0CF1034370 ---- ADD TO HASH FULL: -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH AUTH: -4E7FBD6CBE534923DBDC2E194612F7040271013133C736C5AE3DFC0CF1034370957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098B3B2A31981BCC4F256B0851EBE9D7A11BFBFA8559E7795190B4472D7E1E974C9 -Number of inputs : 1 -Process input -New APDU received: -E04480003B01383200F2B153D3C0B27291BFFBF09D6EF5BD4F6051ED911914F55BEF0E3315B258BB1310890100000020A10700000000005B710DFE9F3F308D19 -Process input -Trusted input used in segwit mode -====> Input HMAC: 5B710DFE9F3F308D -====> Computed HMAC: 5B710DFE9F3F308D ---- ADD TO HASH FULL: -53D3C0B27291BFFBF09D6EF5BD4F6051ED911914F55BEF0E3315B258BB13108901000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A9143F8F2B556915A9306FD92EB5AE72217BE9DD593F88ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A9143F8F2B556915A9306FD92EB5AE72217BE9DD593F88 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -20A1070000000000 ---- ADD TO HASH FULL: -20A1070000000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -B3B2A31981BCC4F256B0851EBE9D7A11BFBFA8559E7795190B4472D7E1E974C9 -Sign ready -New APDU received: -E04800001B05800000318000000180000000000000000000000200001D03D001 ---- ADD TO HASH FULL: -D0031D0001000000 -Using private component -026553901CCECDDF76396E3B3BB2ACC48E68667FB793AAA239AC233AC99CE546 -Hash1 -FAA127DA08393CA5858E158219974145DB64C9C1F70ECB3E8582D6F562A56351 -Hash2 -D1D43A5549457E532745227067826A917150B060F55264615D077F2BA2AF530D diff --git a/tests-legacy/data/many-to-many/p2sh-p2wpkh/tx.json b/tests-legacy/data/many-to-many/p2sh-p2wpkh/tx.json deleted file mode 100644 index f62e5696d..000000000 --- a/tests-legacy/data/many-to-many/p2sh-p2wpkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "f5ff97147f8c7cca717536325eb157c696ef9630934271905b3f8c564b18e708", - "raw": "0200000000010253d3c0b27291bffbf09d6ef5bd4f6051ed911914f55bef0e3315b258bb1310890000000017160014cb078087eff485aaa2260e94a53d7d6d1c5dd151fdffffff53d3c0b27291bffbf09d6ef5bd4f6051ed911914f55bef0e3315b258bb13108901000000171600143f8f2b556915a9306fd92eb5ae72217be9dd593ffdffffff02840a03000000000017a914ffc91a30e33fc6d6ecff42e4d9bd6c7e115d84988700350c000000000017a9142f5864a8acd23fa85977d73e9ac30fd6b341b78c8702483045022100a0173bbd3dcdfe51c24386fee905b8197f36c2024f582b1a838e7bf09a1ec16902206bcd803041b13738f7d8896561e6a8ccec9a3f5d243be8645c403e83beb19b340121024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f6702483045022100ac5701f0cc3cb4b490269674d3463c4fab74f6ee9faf7c29b3d0dea22875dd0002206ba4f4195dce69ffd3e7d832ee5d67ee0d037bad03f4e00f841fbf84649375b5012103a1989dadf0bce57f4c96cfc4d9e6aa9abeba7ac0733e99176f69432e852666f1d0031d00", - "amount": 800000, - "fees": 300, - "to": "2MwZZdy3bCvEhiPsBycreCFiqwDhykAcWN9", - "sign_paths": ["m/49'/1'/0'/1/0", "m/49'/1'/0'/0/2"], - "change_path": "m/49'/1'/0'/1/1", - "lock_time": 1901520, - "utxos": [ - { - "txid": "891013bb58b215330eef5bf5141991ed51604fbdf56e9df0fbbf9172b2c0d353", - "raw": "02000000000101c3945b596fd88fd0d3031a2df8963a68adf9e9f0dfef4f3c4dd72df7d80777ae000000001716001485d33a279d6ae91e59e0c43de832fd4c3398252dfdffffff02909f07000000000017a914f0464d9fa0ea42d80e4d5f1457883982e23b8eec8720a107000000000017a91423e6f63c476f49e8acf6aa583a0fdec7b8aca6458702483045022100da88128e41ae9ea8d6c7fe54a32ab89bbd558993924acf7d86e7039675dd3e0002207228d510d4d8e16f77ef5b82af051a6e5bd5f0263e4464984db8be777dfc6387012102549c187d80a2c8d26760d1646b40e3dce3b9e6042579dcdb9fdeee85208ceb7c00041d00", - "output_indexes": [0, 1], - "output_amounts": [499600, 500000] - } - ] - -} diff --git a/tests-legacy/data/many-to-many/p2wpkh/apdu_debug.log b/tests-legacy/data/many-to-many/p2wpkh/apdu_debug.log deleted file mode 100644 index be6dcab4c..000000000 --- a/tests-legacy/data/many-to-many/p2wpkh/apdu_debug.log +++ /dev/null @@ -1,415 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -F026000000 -New APDU received: -E0C4000000 -New APDU received: -E024000000 -New APDU received: -E02280000130 -New APDU received: -E04000000100 -pin ok -Using private component -34AC5D784EBB4DF4727BCDDF6A6743F5D5D46D83DD74AA825866390C694F2938 -To hash -0251EC84E33A3119486461A44240E906FF94BF40CF807B025B1CA43332B80DC9DB -Hash160 -F5ACC2FD5C60B7263C4A459541C1473BC29E58F4 -Checksum -A21FB2F3 -Length to encode 25 -To encode -6FF5ACC2FD5C60B7263C4A459541C1473BC29E58F4A21FB2F3 -Length encoded 34 -Encoded -6E3375786F6B3843337A566277476A72584476344E544E516A313865335A4D4A426B -Length 34 -New APDU received: -E0400000050180000000 -pin ok -Using private component -C71AE2E616FBCCC325A71600E9C82816505A6373EFCE4CAEDA60400667E0B75C -To hash -02E5646066C2C51CEAEA66F04EC85858D34E2BD285F4A60B1402BFC03CF462E767 -Hash160 -7D825341D36FC1297A41A9839A72975815B1FE61 -Checksum -E01B4FD2 -Length to encode 25 -To encode -6F7D825341D36FC1297A41A9839A72975815B1FE61E01B4FD2 -Length encoded 34 -Encoded -6D727861744272776D555959504E774E4879334A5461567350474750716A4A68674D -Length 34 -New APDU received: -E040000009028000005480000001 -pin ok -Using private component -ADF229CD64E7ADC0915EA16BA3CD4A6923E65D2B6EA127D23DFE84E2D563D22A -To hash -0331B89176766FA4A6CA844A3A6B0DD301B55197DE3250EF47A9936019CF80CE31 -Hash160 -751B87653B01FC4A355D3E08D22995E02BEA88E4 -Checksum -57CDF1D9 -Length to encode 25 -To encode -6F751B87653B01FC4A355D3E08D22995E02BEA88E457CDF1D9 -Length encoded 34 -Encoded -6D7243414C456F71715961654368444C335233323975387578483651486145597243 -Length 34 -New APDU received: -E04000000D03800000548000000180000000 -pin ok -Using private component -08B5ED5B131552DA3B9CA9869E506420EDF4ABEC8C1BA5D468BE04CCC4EC4A48 -To hash -02668C624FDBF81D0E9D3601D5DA195E0D06E48E3F5021D9269774BD0A9E5F2CBE -Hash160 -DAAEB63EBF9E49ED7F25C491432B01D945E37A7C -Checksum -9B84A352 -Length to encode 25 -To encode -6FDAAEB63EBF9E49ED7F25C491432B01D945E37A7C9B84A352 -Length encoded 34 -Encoded -6E3154457534763756734C59696D575A5775333947553871736E7039694456676F6A -Length 34 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000000200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E0428000252C4CA2F29958ECF3E14DDBD449E7DFC716400D7BFEE8A9D7EEC1647C3A14A7100000000000 -Process input ---- ADD TO HASH FULL: -2C4CA2F29958ECF3E14DDBD449E7DFC716400D7BFEE8A9D7EEC1647C3A14A71000000000 ---- ADD TO HASH FULL: -00 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E042800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009D02CF8000000000016 ---- ADD TO HASH FULL: -D02CF80000000000 ---- ADD TO HASH FULL: -16 -Script to read 22 -Process output script, remaining 22 -New APDU received: -E04280001600142318D66F84FEF5C4875F933B038DC63831F8DA13 -Process output script, remaining 22 ---- ADD TO HASH FULL: -00142318D66F84FEF5C4875F933B038DC63831F8DA13 -Process output script, remaining 0 -New APDU received: -E042800009008793030000000016 -Process output script, remaining 0 ---- ADD TO HASH FULL: -0087930300000000 ---- ADD TO HASH FULL: -16 -Script to read 22 -Process output script, remaining 22 -New APDU received: -E0428000160014EEE49309A5CE45D01F691AEC8E5CE206194F9A9F -Process output script, remaining 22 ---- ADD TO HASH FULL: -0014EEE49309A5CE45D01F691AEC8E5CE206194F9A9F -Process output script, remaining 0 -New APDU received: -E042800004F1F61C00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -F1F61C00 -Transaction parsed -New APDU received: -E042000009000000010200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E0428000252C4CA2F29958ECF3E14DDBD449E7DFC716400D7BFEE8A9D7EEC1647C3A14A7100000000000 -Process input ---- ADD TO HASH FULL: -2C4CA2F29958ECF3E14DDBD449E7DFC716400D7BFEE8A9D7EEC1647C3A14A71000000000 ---- ADD TO HASH FULL: -00 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E042800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009D02CF8000000000016 ---- ADD TO HASH FULL: -D02CF80000000000 ---- ADD TO HASH FULL: -16 -Script to read 22 -Process output script, remaining 22 -New APDU received: -E04280001600142318D66F84FEF5C4875F933B038DC63831F8DA13 -Process output script, remaining 22 ---- ADD TO HASH FULL: -00142318D66F84FEF5C4875F933B038DC63831F8DA13 -Process output script, remaining 0 -New APDU received: -E042800009008793030000000016 -Process output script, remaining 0 ---- ADD TO HASH FULL: -0087930300000000 ---- ADD TO HASH FULL: -16 -Script to read 22 -Process output script, remaining 22 -New APDU received: -E0428000160014EEE49309A5CE45D01F691AEC8E5CE206194F9A9F -Process output script, remaining 22 ---- ADD TO HASH FULL: -0014EEE49309A5CE45D01F691AEC8E5CE206194F9A9F -Process output script, remaining 0 -New APDU received: -E042800004F1F61C00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -F1F61C00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440002050200000002 -Init transaction parser -Number of inputs : 2 -Process input -New APDU received: -E04480003B0138320034B9AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D00000000D02CF800000000005B5ADD428D79EB3C19 -Process input -Trusted input used in segwit mode -====> Input HMAC: 5B5ADD428D79EB3C -====> Computed HMAC: 5B5ADD428D79EB3C -Adding amount -D02CF80000000000 -New amount -0000000000F82CD0 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A9142318D66F84FEF5C4875F933B038DC63831F8DA1388ACFDFFFFFF -Process input script, remaining 25 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -FDFFFFFF -Process input -New APDU received: -E04480003B013832006616AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D010000000087930300000000EAA238F5DA4E088C00 -Process input -Trusted input used in segwit mode -====> Input HMAC: EAA238F5DA4E088C -====> Computed HMAC: EAA238F5DA4E088C -Adding amount -0087930300000000 -New amount -00000000048BB3D0 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E044800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done ---- ADD TO HASH FULL: -82D397CBBCFF87BC5D0C4C70E424F9B830EFBAD7BF0BE479DA5D1D1BAFDB9798 -hashPrevout -B2790BC71EE0340E89998B4BEC430CFD8A6847CA49B6EA8185C1D6C368091185 -hashSequence -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 -Presign ready -New APDU received: -E04AFF0015058000005480000001800000000000000100000005 -state=1 -Using private component -6938E6DBEDECC9FF7DF29A2C73BBB8250213D96F4B16B6E947AD5CA9F4160CC7 -New APDU received: -E04A00003202FE2BF80000000000160014C132F90DE4A19728E35D8AF8B3F9EC4E43CF194801879303000000001600143318E04FAE6C12 -state=1 ---- ADD TO HASH FULL: -FE2BF80000000000160014C132F90DE4A19728E35D8AF8B3F9EC4E43CF194801879303000000001600143318E04FAE6C12 -New APDU received: -E04A80000DAFCB009C69CD57E5B2504AE6B4 -state=2 ---- ADD TO HASH FULL: -AFCB009C69CD57E5B2504AE6B4 -hashOutputs -7AB7EB5CB887AE706482C5EF7A0218686D5EECDE5F8F6D6B3565433FB2EECFD6 -Auth Hash: -E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -Segwit parsed once -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -B2790BC71EE0340E89998B4BEC430CFD8A6847CA49B6EA8185C1D6C368091185 -SEGWIT HashedSequence -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -B2790BC71EE0340E89998B4BEC430CFD8A6847CA49B6EA8185C1D6C368091185 ---- ADD TO HASH FULL: -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH AUTH: -B2790BC71EE0340E89998B4BEC430CFD8A6847CA49B6EA8185C1D6C368091185957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A0987AB7EB5CB887AE706482C5EF7A0218686D5EECDE5F8F6D6B3565433FB2EECFD6 -Number of inputs : 1 -Process input -New APDU received: -E04480003B0138320034B9AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D00000000D02CF800000000005B5ADD428D79EB3C19 -Process input -Trusted input used in segwit mode -====> Input HMAC: 5B5ADD428D79EB3C -====> Computed HMAC: 5B5ADD428D79EB3C ---- ADD TO HASH FULL: -AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D00000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A9142318D66F84FEF5C4875F933B038DC63831F8DA1388ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A9142318D66F84FEF5C4875F933B038DC63831F8DA1388 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -D02CF80000000000 ---- ADD TO HASH FULL: -D02CF80000000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -7AB7EB5CB887AE706482C5EF7A0218686D5EECDE5F8F6D6B3565433FB2EECFD6 -Sign ready -New APDU received: -E04800001B05800000548000000180000000000000000000000100001D024301 ---- ADD TO HASH FULL: -43021D0001000000 -Using private component -21E9C06E0D5E169EE7E6C961D2F3F3286666EF74F2CE5EFF6F6108EB6D855DA5 -Hash1 -7F95C240698CB2D4E372B1EFDFB565D9447D4F0706249907AA8D90BF0AD615C0 -Hash2 -45EB814B30BA5E2E6C39A7326290F7DC1548484507CC230B11F84D56FE26800F -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -B2790BC71EE0340E89998B4BEC430CFD8A6847CA49B6EA8185C1D6C368091185 -SEGWIT HashedSequence -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -B2790BC71EE0340E89998B4BEC430CFD8A6847CA49B6EA8185C1D6C368091185 ---- ADD TO HASH FULL: -957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A098 ---- ADD TO HASH AUTH: -B2790BC71EE0340E89998B4BEC430CFD8A6847CA49B6EA8185C1D6C368091185957879FDCE4D8AB885E32FF307D54E75884DA52522CC53D3C4FDB60EDB69A0987AB7EB5CB887AE706482C5EF7A0218686D5EECDE5F8F6D6B3565433FB2EECFD6 -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832006616AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D010000000087930300000000EAA238F5DA4E088C19 -Process input -Trusted input used in segwit mode -====> Input HMAC: EAA238F5DA4E088C -====> Computed HMAC: EAA238F5DA4E088C ---- ADD TO HASH FULL: -AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D01000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914EEE49309A5CE45D01F691AEC8E5CE206194F9A9F88ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A914EEE49309A5CE45D01F691AEC8E5CE206194F9A9F88 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -0087930300000000 ---- ADD TO HASH FULL: -0087930300000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -7AB7EB5CB887AE706482C5EF7A0218686D5EECDE5F8F6D6B3565433FB2EECFD6 -Sign ready -New APDU received: -E04800001B05800000548000000180000000000000010000000400001D024301 ---- ADD TO HASH FULL: -43021D0001000000 -Using private component -EF8D5CCDB132BA01BAD92AA97B1DF75C7C6914809F2510E91AA1FCE703770625 -Hash1 -7A9AA279B55E743EEFE4B819CB5B6E197B32D2C19DF8A3B6DDF1E0520C3A2180 -Hash2 -94D3EBE11569A69F1A2B948C4275940854CD8ED914377B8ADEE0F2257C6AD925 diff --git a/tests-legacy/data/many-to-many/p2wpkh/tx.json b/tests-legacy/data/many-to-many/p2wpkh/tx.json deleted file mode 100644 index 1b3bdf2bb..000000000 --- a/tests-legacy/data/many-to-many/p2wpkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "5c09c82a4efa271adb692917aa35e92b1b360e333fade6cf081952bc7fd16a7a", - "raw": "02000000000102ac4f7220317b689845cd25c42f608368b7b92a784169372d3c2ca2612ef1cd0d0000000000fdffffffac4f7220317b689845cd25c42f608368b7b92a784169372d3c2ca2612ef1cd0d0100000000fdffffff02fe2bf80000000000160014c132f90de4a19728e35d8af8b3f9ec4e43cf194801879303000000001600143318e04fae6c12afcb009c69cd57e5b2504ae6b40247304402203aed260c81d21e36cfdd5a095ad289366103ae3a2690011b20e15001173e44d902206f17e60fc169db8bc21820dfefe51271446a6305284bcfb73738ca38a2715d9f012103455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3024730440220223a3f37f85ed5e42d6ed463ca36932db28799ff12d0391b5e02bcfef829007302206c42e9cb5817bff184e8854848032867f129814df07a96afc49baa5542ede8a4012103affafaf410b74f019028055618695f89c23478aa5fca3ba531f44d382ad7791a43021d00", - "amount": 60000001, - "fees": 209, - "to": "tb1qxvvwqnawdsf2ljcqn35u64l9kfgy4e45v3uamu", - "sign_paths": ["m/84'/1'/0'/0/1", "m/84'/1'/0'/1/4"], - "change_path": "m/84'/1'/0'/1/5", - "lock_time": 1901123, - "utxos": [ - { - "txid": "0dcdf12e61a22c3c2d376941782ab9b76883602fc425cd4598687b3120724fac", - "raw": "020000000001012c4ca2f29958ecf3e14ddbd449e7dfc716400d7bfee8a9d7eec1647c3a14a7100000000000fdffffff02d02cf800000000001600142318d66f84fef5c4875f933b038dc63831f8da130087930300000000160014eee49309a5ce45d01f691aec8e5ce206194f9a9f02483045022100badae1c06849c8ba6616af3f306e5a2fccd433f23c1fa621e0dc7114f3c84a4002205ca637953d0b6fc2431f3cf6368fa00dbcb328d46c4778d8173cdb51b22a60760121027cb75d34b005c4eb9f62bbf2c457d7638e813e757efcec8fa68677d950b63662f1f61c00", - "output_indexes": [0, 1], - "output_amounts": [16264400, 60000000] - } - ] - -} \ No newline at end of file diff --git a/tests-legacy/data/one-to-many/p2pkh/apdu_debug.log b/tests-legacy/data/one-to-many/p2pkh/apdu_debug.log deleted file mode 100644 index 74689b25f..000000000 --- a/tests-legacy/data/one-to-many/p2pkh/apdu_debug.log +++ /dev/null @@ -1,168 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000010200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E042800025F4C2B478DC9CEA2DF43C8BB94938FF43F3BED5AEB3AF9E36CDB7016104626BF2000000006B -Process input ---- ADD TO HASH FULL: -F4C2B478DC9CEA2DF43C8BB94938FF43F3BED5AEB3AF9E36CDB7016104626BF200000000 ---- ADD TO HASH FULL: -6B -Script to read 107 -Process input script, remaining 107 -New APDU received: -E04280006F483045022100B7B8244B3F83648397FA3C37349ED1827BF40BFCA22D4F87CC019D3FDED63E1D02205434CE222DE35E04AB98BE54AF57D2C8B464F0DA76A0275443B33C7B1DF79344012103462F1E48396CE2A4D7694D107FEF5123FE4D1E394394A370FA4901E744B4884EFDFFFFFF -Process input script, remaining 107 ---- ADD TO HASH FULL: -483045022100B7B8244B3F83648397FA3C37349ED1827BF40BFCA22D4F87CC019D3FDED63E1D02205434CE222DE35E04AB98BE54AF57D2C8B464F0DA76A0275443B33C7B1DF79344012103462F1E48396CE2A4D7694D107FEF5123FE4D1E394394A370FA4901E744B488 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -4E ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009DCDB06000000000019 ---- ADD TO HASH FULL: -DCDB060000000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process output script, remaining 25 -New APDU received: -E04280001976A914B242BA04B8526B533B17B168D27EAF0DA7BACB8588AC -Process output script, remaining 25 ---- ADD TO HASH FULL: -76A914B242BA04B8526B533B17B168D27EAF0DA7BACB8588AC -Process output script, remaining 0 -New APDU received: -E042800009706408000000000019 -Process output script, remaining 0 ---- ADD TO HASH FULL: -7064080000000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process output script, remaining 25 -New APDU received: -E04280001976A91426C680EC6AC9C0ED760177F92096F1C5571B72EF88AC -Process output script, remaining 25 ---- ADD TO HASH FULL: -76A91426C680EC6AC9C0ED760177F92096F1C5571B72EF88AC -Process output script, remaining 0 -New APDU received: -E04280000420041D00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -20041D00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440000050200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH AUTH: -02000000 ---- ADD TO HASH FULL: -01 ---- ADD TO HASH AUTH: -01 -Number of inputs : 1 -Process input -New APDU received: -E04480003B0138320002AC5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3401000000706408000000000095CF3BF2ACF74FF919 -Process input -====> Input HMAC: 95CF3BF2ACF74FF9 -====> Computed HMAC: 95CF3BF2ACF74FF9 -Trusted input hash -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3401000000 ---- ADD TO HASH FULL: -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3401000000 ---- ADD TO HASH AUTH: -5DF7B9E20C52D1DA2701A244A49754215266F016F6BD77F640A5200CD1FB7E3401000000 -Adding amount -7064080000000000 -New amount -0000000000086470 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A91426C680EC6AC9C0ED760177F92096F1C5571B72EF88ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A91426C680EC6AC9C0ED760177F92096F1C5571B72EF88 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC ---- ADD TO HASH FULL: -FDFFFFFF ---- ADD TO HASH AUTH: -FDFFFFFF -Process input -Input hashing done -Presign ready -New APDU received: -E04AFF0015058000002C80000001800000000000000100000001 -state=1 -Using private component -885E2F32C80E1D56DABC55AC271CC3983BF122F771AB45A1C890E0DEB740FAAB -New APDU received: -E04A00003202140C0300000000001976A914B3E17C4DEF8A39148ED6FAD7DC6FEBE30DD324CB88AC30570500000000001976A914E0A893 -state=1 ---- ADD TO HASH FULL: -02140C0300000000001976A914B3E17C4DEF8A39148ED6FAD7DC6FEBE30DD324CB88AC30570500000000001976A914E0A893 ---- ADD TO HASH AUTH: -02140C0300000000001976A914B3E17C4DEF8A39148ED6FAD7DC6FEBE30DD324CB88AC30570500000000001976A914E0A893 -New APDU received: -E04A80001380635F18B7EE09546D963232BCB992412B88AC -state=2 ---- ADD TO HASH FULL: -80635F18B7EE09546D963232BCB992412B88AC ---- ADD TO HASH AUTH: -80635F18B7EE09546D963232BCB992412B88AC -Checksum -62CF1861 -Length to encode 25 -To encode -6FE0A89380635F18B7EE09546D963232BCB992412B62CF1861 -Length encoded 34 -Encoded -6E317A71627548485461746D727359476A5042597953656F4637637250696A326A53 -New APDU received: -E04800001B058000002C8000000180000000000000000000000200001D04FE01 ---- ADD TO HASH FULL: -FE041D0001000000 -Using private component -E116F09653EDEBCCC7AC55622ECFA8DE2EE60C51A5006B6B6153B184B4DA914D -Hash1 -7E0EEB79AFE7BB94A68EB1CB0BCB591C380ADAB2120DB528F2D83EBB0BB9029B -Hash2 -971753A7B93CAB3510D9DAD6D08FB1805E580D8CBE292BF89A68C6CBE3BC8A72 diff --git a/tests-legacy/data/one-to-many/p2pkh/tx.json b/tests-legacy/data/one-to-many/p2pkh/tx.json deleted file mode 100644 index b68a65640..000000000 --- a/tests-legacy/data/one-to-many/p2pkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "de390b0c4ac67b6cdcbe84611463cc42b6774f2bf11d8f7144e9601e3591f857", - "raw": "02000000015df7b9e20c52d1da2701a244a49754215266f016f6bd77f640a5200cd1fb7e34010000006b483045022100c806957429935c5d61f548223c3536dc739fe351814bce9edc1694e5a5c25abf022013e307b0b4885acb64ccffaf5f4794f205040325ebc36c258787c488b038c2bb0121038ba462d7dd65917ece454142903bb362a0b412d1841d52087ad881646b6cdccffdffffff02140c0300000000001976a914b3e17c4def8a39148ed6fad7dc6febe30dd324cb88ac30570500000000001976a914e0a89380635f18b7ee09546d963232bcb992412b88acfe041d00", - "amount": 350000, - "fees": 300, - "to": "n1zqbuHHTatmrsYGjPBYySeoF7crPij2jS", - "sign_paths": ["m/84'/1'/0'/0/2"], - "change_path": "m/84'/1'/0'/1/1", - "lock_time": 1901822, - "utxos": [ - { - "txid": "347efbd10c20a540f677bdf616f06652215497a444a20127dad1520ce2b9f75d", - "raw": "0200000001f4c2b478dc9cea2df43c8bb94938ff43f3bed5aeb3af9e36cdb7016104626bf2000000006b483045022100b7b8244b3f83648397fa3c37349ed1827bf40bfca22d4f87cc019d3fded63e1d02205434ce222de35e04ab98be54af57d2c8b464f0da76a0275443b33c7b1df79344012103462f1e48396ce2a4d7694d107fef5123fe4d1e394394a370fa4901e744b4884efdffffff02dcdb0600000000001976a914b242ba04b8526b533b17b168d27eaf0da7bacb8588ac70640800000000001976a91426c680ec6ac9c0ed760177f92096f1c5571b72ef88ac20041d00", - "output_indexes": [1], - "output_amounts": [550000] - } - ] - -} diff --git a/tests-legacy/data/one-to-many/p2sh-p2wpkh/apdu_debug.log b/tests-legacy/data/one-to-many/p2sh-p2wpkh/apdu_debug.log deleted file mode 100644 index 2116c1fa9..000000000 --- a/tests-legacy/data/one-to-many/p2sh-p2wpkh/apdu_debug.log +++ /dev/null @@ -1,191 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000000200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E042800025167FA3F3D4233D1660B4B3BB62E78FB35C9786B0A96E6B4D61697CA112D9BA4E0100000017 -Process input ---- ADD TO HASH FULL: -167FA3F3D4233D1660B4B3BB62E78FB35C9786B0A96E6B4D61697CA112D9BA4E01000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process input script, remaining 23 -New APDU received: -E04280001B160014E310D044F88DAB1B42769E4A84CAF08363F9ECC1FDFFFFFF -Process input script, remaining 23 ---- ADD TO HASH FULL: -160014E310D044F88DAB1B42769E4A84CAF08363F9EC -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -C1 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000101 -Input hashing done ---- ADD TO HASH FULL: -01 -Number of outputs : 1 -New APDU received: -E042800009AA410F000000000017 ---- ADD TO HASH FULL: -AA410F0000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A9144D7997B902D0F1A2441A0542C920413B957E8A2287 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A9144D7997B902D0F1A2441A0542C920413B957E8A2287 -Process output script, remaining 0 -New APDU received: -E042800004FD031D00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -FD031D00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440002050200000001 -Init transaction parser -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832006773C3945B596FD88FD0D3031A2DF8963A68ADF9E9F0DFEF4F3C4DD72DF7D80777AE00000000AA410F0000000000F438E5A7C4F7887819 -Process input -Trusted input used in segwit mode -====> Input HMAC: F438E5A7C4F78878 -====> Computed HMAC: F438E5A7C4F78878 -Adding amount -AA410F0000000000 -New amount -00000000000F41AA -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A91485D33A279D6AE91E59E0C43DE832FD4C3398252D88ACFDFFFFFF -Process input script, remaining 25 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done ---- ADD TO HASH FULL: -23E9829BFB4E23FBD3C4848BAA035AF15D73BCB83E510F7F097F90A21A4280D2 -hashPrevout -82C5F725DB8A39C6B9C7B3C0DAF5C67E85DDA91C1F0FE866598ECF64CAF01419 -hashSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 -Presign ready -New APDU received: -E04AFF0015058000003180000001800000000000000100000000 -state=1 -Using private component -E8AE81F93160D032A7AA82BE9B719499A5DE635639D7237B7AB2572DE86F7E52 -New APDU received: -E04A00003202909F07000000000017A914F0464D9FA0EA42D80E4D5F1457883982E23B8EEC8720A107000000000017A91423E6F63C476F -state=1 ---- ADD TO HASH FULL: -909F07000000000017A914F0464D9FA0EA42D80E4D5F1457883982E23B8EEC8720A107000000000017A91423E6F63C476F -New APDU received: -E04A80000F49E8ACF6AA583A0FDEC7B8ACA64587 -state=2 ---- ADD TO HASH FULL: -49E8ACF6AA583A0FDEC7B8ACA64587 -hashOutputs -BBC96114CDDD96D506521DFD897A0F946AE5E59CFA4373A8A1AD62B8A5D38E14 -Auth Hash: -E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -Checksum -BF56514F -Length to encode 25 -To encode -C423E6F63C476F49E8ACF6AA583A0FDEC7B8ACA645BF56514F -Length encoded 35 -Encoded -324D7658344B5756424B344E717A4A766A67517952556841615538503775536B625141 -Segwit parsed once -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -82C5F725DB8A39C6B9C7B3C0DAF5C67E85DDA91C1F0FE866598ECF64CAF01419 -SEGWIT HashedSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -82C5F725DB8A39C6B9C7B3C0DAF5C67E85DDA91C1F0FE866598ECF64CAF01419 ---- ADD TO HASH FULL: -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH AUTH: -82C5F725DB8A39C6B9C7B3C0DAF5C67E85DDA91C1F0FE866598ECF64CAF01419CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52BBC96114CDDD96D506521DFD897A0F946AE5E59CFA4373A8A1AD62B8A5D38E14 -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832006773C3945B596FD88FD0D3031A2DF8963A68ADF9E9F0DFEF4F3C4DD72DF7D80777AE00000000AA410F0000000000F438E5A7C4F7887819 -Process input -Trusted input used in segwit mode -====> Input HMAC: F438E5A7C4F78878 -====> Computed HMAC: F438E5A7C4F78878 ---- ADD TO HASH FULL: -C3945B596FD88FD0D3031A2DF8963A68ADF9E9F0DFEF4F3C4DD72DF7D80777AE00000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A91485D33A279D6AE91E59E0C43DE832FD4C3398252D88ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A91485D33A279D6AE91E59E0C43DE832FD4C3398252D88 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -AA410F0000000000 ---- ADD TO HASH FULL: -AA410F0000000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -BBC96114CDDD96D506521DFD897A0F946AE5E59CFA4373A8A1AD62B8A5D38E14 -Sign ready -New APDU received: -E04800001B05800000318000000180000000000000000000000100001D040001 ---- ADD TO HASH FULL: -00041D0001000000 -Using private component -54D5FA61DC01FB1B00C7B2FD7DE49273A859AB47A572BD02E68DFB1F126867ED -Hash1 -AD7ADE5C5390D208BC4992C615545E0C1E379832D9801396C4AD20E8D670381B -Hash2 -97BC0CA93B0530F5EBC5AA69EB214948BF1A28D9637D7B0030F53FA5C951F4A5 diff --git a/tests-legacy/data/one-to-many/p2sh-p2wpkh/tx.json b/tests-legacy/data/one-to-many/p2sh-p2wpkh/tx.json deleted file mode 100644 index 23898eadb..000000000 --- a/tests-legacy/data/one-to-many/p2sh-p2wpkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "891013bb58b215330eef5bf5141991ed51604fbdf56e9df0fbbf9172b2c0d353", - "raw": "02000000000101c3945b596fd88fd0d3031a2df8963a68adf9e9f0dfef4f3c4dd72df7d80777ae000000001716001485d33a279d6ae91e59e0c43de832fd4c3398252dfdffffff02909f07000000000017a914f0464d9fa0ea42d80e4d5f1457883982e23b8eec8720a107000000000017a91423e6f63c476f49e8acf6aa583a0fdec7b8aca6458702483045022100da88128e41ae9ea8d6c7fe54a32ab89bbd558993924acf7d86e7039675dd3e0002207228d510d4d8e16f77ef5b82af051a6e5bd5f0263e4464984db8be777dfc6387012102549c187d80a2c8d26760d1646b40e3dce3b9e6042579dcdb9fdeee85208ceb7c00041d00", - "amount": 500000, - "fees": 250, - "to": "2MvX4KWVBK4NqzJvjgQyRUhAaU8P7uSkbQA", - "sign_paths": ["m/49'/1'/0'/0/1"], - "change_path": "m/49'/1'/0'/1/0", - "lock_time": 1901568, - "utxos": [ - { - "txid": "ae7707d8f72dd74d3c4fefdff0e9f9ad683a96f82d1a03d3d08fd86f595b94c3", - "raw": "02000000000101167fa3f3d4233d1660b4b3bb62e78fb35c9786b0a96e6b4d61697ca112d9ba4e0100000017160014e310d044f88dab1b42769e4a84caf08363f9ecc1fdffffff01aa410f000000000017a9144d7997b902d0f1a2441a0542c920413b957e8a228702483045022100b8ab2d40241c406b7e232e98fc99188ed46c062cc3f49ea39cfb77ae9f4918760220078763f7e0078be84ceb939bea8a87934e64ae3502d8636eb249c7ddd97036f7012102a80f007194d53d37f6f99539f635390588c4e1c328b098295f61af40d60cb28afd031d00", - "output_indexes": [0], - "output_amounts": [999850] - } - ] - -} diff --git a/tests-legacy/data/one-to-many/p2wpkh/apdu_debug.log b/tests-legacy/data/one-to-many/p2wpkh/apdu_debug.log deleted file mode 100644 index 86fa41855..000000000 --- a/tests-legacy/data/one-to-many/p2wpkh/apdu_debug.log +++ /dev/null @@ -1,207 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000010200000002 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -02 -Number of inputs : 2 -Process input -New APDU received: -E042800025AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D0000000000 -Process input ---- ADD TO HASH FULL: -AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D00000000 ---- ADD TO HASH FULL: -00 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E042800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -New APDU received: -E042800025AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D0100000000 -Process input ---- ADD TO HASH FULL: -AC4F7220317B689845CD25C42F608368B7B92A784169372D3C2CA2612EF1CD0D01000000 ---- ADD TO HASH FULL: -00 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E042800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009FE2BF8000000000016 ---- ADD TO HASH FULL: -FE2BF80000000000 ---- ADD TO HASH FULL: -16 -Script to read 22 -Process output script, remaining 22 -New APDU received: -E0428000160014C132F90DE4A19728E35D8AF8B3F9EC4E43CF1948 -Process output script, remaining 22 ---- ADD TO HASH FULL: -0014C132F90DE4A19728E35D8AF8B3F9EC4E43CF1948 -Process output script, remaining 0 -New APDU received: -E042800009018793030000000016 -Process output script, remaining 0 ---- ADD TO HASH FULL: -0187930300000000 ---- ADD TO HASH FULL: -16 -Script to read 22 -Process output script, remaining 22 -New APDU received: -E04280001600143318E04FAE6C12AFCB009C69CD57E5B2504AE6B4 -Process output script, remaining 22 ---- ADD TO HASH FULL: -00143318E04FAE6C12AFCB009C69CD57E5B2504AE6B4 -Process output script, remaining 0 -New APDU received: -E04280000443021D00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -43021D00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440002050200000001 -Init transaction parser -Number of inputs : 1 -Process input -New APDU received: -E04480003B0138320001107A6AD17FBC521908CFE6AD3F330E361B2BE935AA172969DB1A27FA4E2AC8095C01000000018793030000000019D862B903E5346019 -Process input -Trusted input used in segwit mode -====> Input HMAC: 19D862B903E53460 -====> Computed HMAC: 19D862B903E53460 -Adding amount -0187930300000000 -New amount -0000000003938701 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A9143318E04FAE6C12AFCB009C69CD57E5B2504AE6B488ACFDFFFFFF -Process input script, remaining 25 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done ---- ADD TO HASH FULL: -23E9829BFB4E23FBD3C4848BAA035AF15D73BCB83E510F7F097F90A21A4280D2 -hashPrevout -0AD0C2779F8B2F0C994FD54CA86701607C7D162C622D8BCAA2DF8A195DE98C63 -hashSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 -Presign ready -New APDU received: -E04AFF0015058000005480000001800000000000000100000006 -state=1 -Using private component -422277B0087551BAF831C6F099FADBD0E36048ABD1497E05DC9ECD0BFCF13243 -New APDU received: -E04A00003202B895980000000000160014AE4A9E12C72BAFA7C09DA2D0D924E0552FF68C2580F0FA02000000001600141347E82A037B5D -state=1 ---- ADD TO HASH FULL: -B895980000000000160014AE4A9E12C72BAFA7C09DA2D0D924E0552FF68C2580F0FA02000000001600141347E82A037B5D -New APDU received: -E04A80000DBB38CF8C4759F242B1F5C7E09A -state=2 ---- ADD TO HASH FULL: -BB38CF8C4759F242B1F5C7E09A -hashOutputs -D00F205254FBFFF03E8A8D31FE82421C83271E803EE12B4B0E2BF307E3E8D965 -Auth Hash: -E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -Segwit parsed once -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -0AD0C2779F8B2F0C994FD54CA86701607C7D162C622D8BCAA2DF8A195DE98C63 -SEGWIT HashedSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -0AD0C2779F8B2F0C994FD54CA86701607C7D162C622D8BCAA2DF8A195DE98C63 ---- ADD TO HASH FULL: -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH AUTH: -0AD0C2779F8B2F0C994FD54CA86701607C7D162C622D8BCAA2DF8A195DE98C63CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52D00F205254FBFFF03E8A8D31FE82421C83271E803EE12B4B0E2BF307E3E8D965 -Number of inputs : 1 -Process input -New APDU received: -E04480003B0138320001107A6AD17FBC521908CFE6AD3F330E361B2BE935AA172969DB1A27FA4E2AC8095C01000000018793030000000019D862B903E5346019 -Process input -Trusted input used in segwit mode -====> Input HMAC: 19D862B903E53460 -====> Computed HMAC: 19D862B903E53460 ---- ADD TO HASH FULL: -7A6AD17FBC521908CFE6AD3F330E361B2BE935AA172969DB1A27FA4E2AC8095C01000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A9143318E04FAE6C12AFCB009C69CD57E5B2504AE6B488ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A9143318E04FAE6C12AFCB009C69CD57E5B2504AE6B488 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -0187930300000000 ---- ADD TO HASH FULL: -0187930300000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -D00F205254FBFFF03E8A8D31FE82421C83271E803EE12B4B0E2BF307E3E8D965 -Sign ready -New APDU received: -E04800001B05800000548000000180000000000000000000000300001D02A401 ---- ADD TO HASH FULL: -A4021D0001000000 -Using private component -BB2A3DBCB0FF5B3F483F58A916AC39D6B1D6D6F10CAF148FBB90BC8462214DB5 -Hash1 -D9463068D03E9557C2E47D7C8AC4B9C6651E535FD0F4B6FC955E9597832D07FE -Hash2 -D89635D546800B70DD465E2C89EE6764DD454091EA23D7F936408E8C2460D460 diff --git a/tests-legacy/data/one-to-many/p2wpkh/tx.json b/tests-legacy/data/one-to-many/p2wpkh/tx.json deleted file mode 100644 index 0c0b45f9d..000000000 --- a/tests-legacy/data/one-to-many/p2wpkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "2e06df6ac8282f8e93f49285a2dedb5d4d5821427cfd5cbcc8f98b24a925a09c", - "raw": "020000000001017a6ad17fbc521908cfe6ad3f330e361b2be935aa172969db1a27fa4e2ac8095c0100000000fdffffff02b895980000000000160014ae4a9e12c72bafa7c09da2d0d924e0552ff68c2580f0fa02000000001600141347e82a037b5dbb38cf8c4759f242b1f5c7e09a02483045022100f6e4c827b7c0ffc2cc781ea9b64ddacc9a6ac72ad50d08e84f5ae9ee26b6f88f02206441cd69a31563c6e80bd7fd21ba050d6720116330c461a4014ac6164d24e2a401210232804f473db3282d5baca4c53713463645729335b90d738e18e2286b603d3fc5a4021d00", - "amount": 50000000, - "fees": 201, - "to": "tb1qzdr7s2sr0dwmkwx033r4nujzk86u0cy6fmzfjk", - "sign_paths": ["m/84'/1'/0'/0/3"], - "change_path": "m/84'/1'/0'/1/6", - "lock_time": 1901220, - "utxos": [ - { - "txid": "5c09c82a4efa271adb692917aa35e92b1b360e333fade6cf081952bc7fd16a7a", - "raw": "02000000000102ac4f7220317b689845cd25c42f608368b7b92a784169372d3c2ca2612ef1cd0d0000000000fdffffffac4f7220317b689845cd25c42f608368b7b92a784169372d3c2ca2612ef1cd0d0100000000fdffffff02fe2bf80000000000160014c132f90de4a19728e35d8af8b3f9ec4e43cf194801879303000000001600143318e04fae6c12afcb009c69cd57e5b2504ae6b40247304402203aed260c81d21e36cfdd5a095ad289366103ae3a2690011b20e15001173e44d902206f17e60fc169db8bc21820dfefe51271446a6305284bcfb73738ca38a2715d9f012103455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3024730440220223a3f37f85ed5e42d6ed463ca36932db28799ff12d0391b5e02bcfef829007302206c42e9cb5817bff184e8854848032867f129814df07a96afc49baa5542ede8a4012103affafaf410b74f019028055618695f89c23478aa5fca3ba531f44d382ad7791a43021d00", - "output_indexes": [1], - "output_amounts": [60000001] - } - ] - -} \ No newline at end of file diff --git a/tests-legacy/data/one-to-one/p2pkh/apdu_debug.log b/tests-legacy/data/one-to-one/p2pkh/apdu_debug.log deleted file mode 100644 index 36ff13074..000000000 --- a/tests-legacy/data/one-to-one/p2pkh/apdu_debug.log +++ /dev/null @@ -1,159 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000010200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E042800025EC230E53095256052A2428270EEC0498944B10F6F1C578F431C23D0098B4AE5A0100000017 -Process input ---- ADD TO HASH FULL: -EC230E53095256052A2428270EEC0498944B10F6F1C578F431C23D0098B4AE5A01000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process input script, remaining 23 -New APDU received: -E04280001B160014281539820E2DE973AE41BA6004B431C921C4D86DFEFFFFFF -Process input script, remaining 23 ---- ADD TO HASH FULL: -160014281539820E2DE973AE41BA6004B431C921C4D8 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -6D ---- ADD TO HASH FULL: -FEFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E042800009727275000000000017 ---- ADD TO HASH FULL: -7272750000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A914C8B906AF298C70E603A28C3EFC2FAE19E6AB280F87 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A914C8B906AF298C70E603A28C3EFC2FAE19E6AB280F87 -Process output script, remaining 0 -New APDU received: -E04280000940420F000000000019 -Process output script, remaining 0 ---- ADD TO HASH FULL: -40420F0000000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process output script, remaining 25 -New APDU received: -E04280001976A914CBAE5B50CF939E6F531B8A6B7ABD788FE14B029788AC -Process output script, remaining 25 ---- ADD TO HASH FULL: -76A914CBAE5B50CF939E6F531B8A6B7ABD788FE14B029788AC -Process output script, remaining 0 -New APDU received: -E04280000484F21C00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -84F21C00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440000050200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH AUTH: -02000000 ---- ADD TO HASH FULL: -01 ---- ADD TO HASH AUTH: -01 -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832007BB65122C2CDE6823E55754175B92C9C57A0A8E1AC83C38E1787FD3A1FF3348E95130100000040420F00000000009AC6444F05C5817019 -Process input -====> Input HMAC: 9AC6444F05C58170 -====> Computed HMAC: 9AC6444F05C58170 -Trusted input hash -5122C2CDE6823E55754175B92C9C57A0A8E1AC83C38E1787FD3A1FF3348E951301000000 ---- ADD TO HASH FULL: -5122C2CDE6823E55754175B92C9C57A0A8E1AC83C38E1787FD3A1FF3348E951301000000 ---- ADD TO HASH AUTH: -5122C2CDE6823E55754175B92C9C57A0A8E1AC83C38E1787FD3A1FF3348E951301000000 -Adding amount -40420F0000000000 -New amount -00000000000F4240 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914CBAE5B50CF939E6F531B8A6B7ABD788FE14B029788ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A914CBAE5B50CF939E6F531B8A6B7ABD788FE14B029788 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC ---- ADD TO HASH FULL: -FDFFFFFF ---- ADD TO HASH AUTH: -FDFFFFFF -Process input -Input hashing done -Presign ready -New APDU received: -E04AFF000100 -state=1 -New APDU received: -E04A8000230178410F00000000001976A91413D7D58166946C3EC022934066D8C0D111D1BB4188AC -state=1 ---- ADD TO HASH FULL: -0178410F00000000001976A91413D7D58166946C3EC022934066D8C0D111D1BB4188AC ---- ADD TO HASH AUTH: -0178410F00000000001976A91413D7D58166946C3EC022934066D8C0D111D1BB4188AC -Checksum -BA597C35 -Length to encode 25 -To encode -6F13D7D58166946C3EC022934066D8C0D111D1BB41BA597C35 -Length encoded 34 -Encoded -6D684B736837457A4A6F316753553176727079656A533171734A41754B7961575767 -New APDU received: -E04800001B058000002C8000000180000000000000000000000000001D041A01 ---- ADD TO HASH FULL: -1A041D0001000000 -Using private component -4FD5547026D24A120A268D246E90DD23568459A16BCFCB54FCEE86EBCD1834FA -Hash1 -9C12EA3D3E8644284EB010F24F97D5BC1FDA5A8F9B2E11B3D65FBC40BD7089BD -Hash2 -AB3C3430727A430B162A7CD73CE0BDC87A3DAEDDBAAA1A871E232CB0674F9129 diff --git a/tests-legacy/data/one-to-one/p2pkh/tx.json b/tests-legacy/data/one-to-one/p2pkh/tx.json deleted file mode 100644 index cce076fed..000000000 --- a/tests-legacy/data/one-to-one/p2pkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "f26b62046101b7cd369eafb3aed5bef343ff3849b98b3cf42dea9cdc78b4c2f4", - "raw": "02000000015122c2cde6823e55754175b92c9c57a0a8e1ac83c38e1787fd3a1ff3348e9513010000006b483045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f4012102ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718fdffffff0178410f00000000001976a91413d7d58166946c3ec022934066d8c0d111d1bb4188ac1a041d00", - "amount": 999800, - "fees": 200, - "to": "mhKsh7EzJo1gSU1vrpyejS1qsJAuKyaWWg", - "sign_paths": ["m/84'/1'/0'/0/0"], - "change_path": null, - "lock_time": 1901594, - "utxos": [ - { - "txid": "13958e34f31f3afd87178ec383ace1a8a0579c2cb9754175553e82e6cdc22251", - "raw": "02000000000101ec230e53095256052a2428270eec0498944b10f6f1c578f431c23d0098b4ae5a0100000017160014281539820e2de973ae41ba6004b431c921c4d86dfeffffff02727275000000000017a914c8b906af298c70e603a28c3efc2fae19e6ab280f8740420f00000000001976a914cbae5b50cf939e6f531b8a6b7abd788fe14b029788ac02473044022037ecb4248361aafd4f8c11e705f0fa7a5fbdcd595172fcd5643f3b11beff5d400220020c6d326f6c37d63cecadaf4eb335faedf7c44e05f5ef1d2b68140b023bd13d012103dac82fc0acfcfc36348d4a48a46f01cea77f2b9ece3f8c3b4c99d0b0b2f995d284f21c00", - "output_indexes": [1], - "output_amounts": [999800] - } - ] - -} diff --git a/tests-legacy/data/one-to-one/p2sh-p2wpkh/apdu_debug.log b/tests-legacy/data/one-to-one/p2sh-p2wpkh/apdu_debug.log deleted file mode 100644 index 8fed79782..000000000 --- a/tests-legacy/data/one-to-one/p2sh-p2wpkh/apdu_debug.log +++ /dev/null @@ -1,199 +0,0 @@ -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000010200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E0428000251AF94AFBD37204AF7C23D6C7B8D41617831555831F72675C4181DB82B8A650410100000017 -Process input ---- ADD TO HASH FULL: -1AF94AFBD37204AF7C23D6C7B8D41617831555831F72675C4181DB82B8A6504101000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process input script, remaining 23 -New APDU received: -E04280001B16001431C162CDDC618B8E80049620FFAF3F9210B10783FEFFFFFF -Process input script, remaining 23 ---- ADD TO HASH FULL: -16001431C162CDDC618B8E80049620FFAF3F9210B107 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -83 ---- ADD TO HASH FULL: -FEFFFFFF -Process input -Input hashing done -New APDU received: -E04280000102 -Input hashing done ---- ADD TO HASH FULL: -02 -Number of outputs : 2 -New APDU received: -E0428000090E19B7000000000017 ---- ADD TO HASH FULL: -0E19B70000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A914C9ED58BD29EF20ADC85156DA674A03E478799F1787 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A914C9ED58BD29EF20ADC85156DA674A03E478799F1787 -Process output script, remaining 0 -New APDU received: -E04280000940420F000000000017 -Process output script, remaining 0 ---- ADD TO HASH FULL: -40420F0000000000 ---- ADD TO HASH FULL: -17 -Script to read 23 -Process output script, remaining 23 -New APDU received: -E042800017A914424B246398B68142926AEDC8D85628AC224A54DC87 -Process output script, remaining 23 ---- ADD TO HASH FULL: -A914424B246398B68142926AEDC8D85628AC224A54DC87 -Process output script, remaining 0 -New APDU received: -E04280000475F21C00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -75F21C00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440002050200000001 -Init transaction parser -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832003BEF167FA3F3D4233D1660B4B3BB62E78FB35C9786B0A96E6B4D61697CA112D9BA4E0100000040420F000000000027708A585F4E617B19 -Process input -Trusted input used in segwit mode -====> Input HMAC: 27708A585F4E617B -====> Computed HMAC: 27708A585F4E617B -Adding amount -40420F0000000000 -New amount -00000000000F4240 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914E310D044F88DAB1B42769E4A84CAF08363F9ECC188ACFDFFFFFF -Process input script, remaining 25 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done ---- ADD TO HASH FULL: -23E9829BFB4E23FBD3C4848BAA035AF15D73BCB83E510F7F097F90A21A4280D2 -hashPrevout -5878F55307FBC6A50458E9470B134440E1EB0AAF19172B45796FE925F031B18C -hashSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 -Presign ready -New APDU received: -E04AFF000100 -state=1 -New APDU received: -E04A80002101AA410F000000000017A9144D7997B902D0F1A2441A0542C920413B957E8A2287 -state=1 ---- ADD TO HASH FULL: -AA410F000000000017A9144D7997B902D0F1A2441A0542C920413B957E8A2287 -hashOutputs -056D1137157786D3D8724535C9894AD074D7B1569A5E79B5221641CAB56D1900 -Auth Hash: -E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -Checksum -FF599171 -Length to encode 25 -To encode -C44D7997B902D0F1A2441A0542C920413B957E8A22FF599171 -Length encoded 35 -Encoded -324D7A4A736754696966747266426B636472766232394E34326273674B3177644A5938 -Segwit parsed once -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -5878F55307FBC6A50458E9470B134440E1EB0AAF19172B45796FE925F031B18C -SEGWIT HashedSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -5878F55307FBC6A50458E9470B134440E1EB0AAF19172B45796FE925F031B18C ---- ADD TO HASH FULL: -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH AUTH: -5878F55307FBC6A50458E9470B134440E1EB0AAF19172B45796FE925F031B18CCAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52056D1137157786D3D8724535C9894AD074D7B1569A5E79B5221641CAB56D1900 -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832003BEF167FA3F3D4233D1660B4B3BB62E78FB35C9786B0A96E6B4D61697CA112D9BA4E0100000040420F000000000027708A585F4E617B19 -Process input -Trusted input used in segwit mode -====> Input HMAC: 27708A585F4E617B -====> Computed HMAC: 27708A585F4E617B ---- ADD TO HASH FULL: -167FA3F3D4233D1660B4B3BB62E78FB35C9786B0A96E6B4D61697CA112D9BA4E01000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A914E310D044F88DAB1B42769E4A84CAF08363F9ECC188ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A914E310D044F88DAB1B42769E4A84CAF08363F9ECC188 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -40420F0000000000 ---- ADD TO HASH FULL: -40420F0000000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -056D1137157786D3D8724535C9894AD074D7B1569A5E79B5221641CAB56D1900 -Sign ready -New APDU received: -E04800001B05800000318000000180000000000000000000000000001D03FD01 ---- ADD TO HASH FULL: -FD031D0001000000 -Using private component -C16863B6D7B2B534AB6A7E049731E79907972F4689EBFC5A95398C537D683416 -Hash1 -746039019C2B7F0ACBF215F8DFB139CE369ECD7D463C18975EE588B9109FA971 -Hash2 -1398DE4932EC3444C1C18A8D3D9D5C8FEF5FAA838F45B91AD08D242405718092 diff --git a/tests-legacy/data/one-to-one/p2sh-p2wpkh/tx.json b/tests-legacy/data/one-to-one/p2sh-p2wpkh/tx.json deleted file mode 100644 index 33ce877b2..000000000 --- a/tests-legacy/data/one-to-one/p2sh-p2wpkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "ae7707d8f72dd74d3c4fefdff0e9f9ad683a96f82d1a03d3d08fd86f595b94c3", - "raw": "02000000000101167fa3f3d4233d1660b4b3bb62e78fb35c9786b0a96e6b4d61697ca112d9ba4e0100000017160014e310d044f88dab1b42769e4a84caf08363f9ecc1fdffffff01aa410f000000000017a9144d7997b902d0f1a2441a0542c920413b957e8a228702483045022100b8ab2d40241c406b7e232e98fc99188ed46c062cc3f49ea39cfb77ae9f4918760220078763f7e0078be84ceb939bea8a87934e64ae3502d8636eb249c7ddd97036f7012102a80f007194d53d37f6f99539f635390588c4e1c328b098295f61af40d60cb28afd031d00", - "amount": 999850, - "fees": 150, - "to": "2MzJsgTiiftrfBkcdrvb29N42bsgK1wdJY8", - "sign_paths": ["m/49'/1'/0'/0/0"], - "change_path": null, - "lock_time": 1901565, - "utxos": [ - { - "txid": "4ebad912a17c69614d6b6ea9b086975cb38fe762bbb3b460163d23d4f3a37f16", - "raw": "020000000001011af94afbd37204af7c23d6c7b8d41617831555831f72675c4181db82b8a65041010000001716001431c162cddc618b8e80049620ffaf3f9210b10783feffffff020e19b7000000000017a914c9ed58bd29ef20adc85156da674a03e478799f178740420f000000000017a914424b246398b68142926aedc8d85628ac224a54dc8702473044022072190b182ab580404974fc5f569d0497002d0018e2903a00f10d91994d257cd902207973fb20ba684a887c97914079ff6a8a4ea10f0b101e16ebbb2bc7d03b4da971012102193ba58ea7475219eb3eddd0d61ed86b664585bab55d01ef3afdc4b0c1a0dd4575f21c00", - "output_indexes": [1], - "output_amounts": [1000000] - } - ] - -} diff --git a/tests-legacy/data/one-to-one/p2wpkh/apdu_debug.log b/tests-legacy/data/one-to-one/p2wpkh/apdu_debug.log deleted file mode 100644 index e28db8ab7..000000000 --- a/tests-legacy/data/one-to-one/p2wpkh/apdu_debug.log +++ /dev/null @@ -1,188 +0,0 @@ -New APDU received: -E04000000D03800000548000000180000000 -pin ok -Using private component -08B5ED5B131552DA3B9CA9869E506420EDF4ABEC8C1BA5D468BE04CCC4EC4A48 -To hash -02668C624FDBF81D0E9D3601D5DA195E0D06E48E3F5021D9269774BD0A9E5F2CBE -Hash160 -DAAEB63EBF9E49ED7F25C491432B01D945E37A7C -Checksum -9B84A352 -Length to encode 25 -To encode -6FDAAEB63EBF9E49ED7F25C491432B01D945E37A7C9B84A352 -Length encoded 34 -Encoded -6E3154457534763756734C59696D575A5775333947553871736E7039694456676F6A -Length 34 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E0C4000000 -New APDU received: -E042000009000000000200000001 -Init transaction parser ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -01 -Number of inputs : 1 -Process input -New APDU received: -E04280002534AB4E0084423B9DA3B7701023A4137C362F742328C5CB6F6D5086130A88B0940000000000 -Process input ---- ADD TO HASH FULL: -34AB4E0084423B9DA3B7701023A4137C362F742328C5CB6F6D5086130A88B09400000000 ---- ADD TO HASH FULL: -00 -Script to read 0 -Process input script, remaining 0 -New APDU received: -E042800004FDFFFFFF -Process input script, remaining 0 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -New APDU received: -E04280000101 -Input hashing done ---- ADD TO HASH FULL: -01 -Number of outputs : 1 -New APDU received: -E0428000095EB58B040000000016 ---- ADD TO HASH FULL: -5EB58B0400000000 ---- ADD TO HASH FULL: -16 -Script to read 22 -Process output script, remaining 22 -New APDU received: -E04280001600142318D66F84FEF5C4875F933B038DC63831F8DA13 -Process output script, remaining 22 ---- ADD TO HASH FULL: -00142318D66F84FEF5C4875F933B038DC63831F8DA13 -Process output script, remaining 0 -New APDU received: -E042800004FBF41C00 -Process output script, remaining 0 -Output hashing done ---- ADD TO HASH FULL: -FBF41C00 -Transaction parsed -New APDU received: -E02601000101 -New APDU received: -E0440002050200000001 -Init transaction parser -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832002BD7F3DB0E87B75A0C4A113F30906230FFAD598BAA37F3AB27FC2C339DE849B9F385000000005EB58B0400000000F926369658DFFF5A19 -Process input -Trusted input used in segwit mode -====> Input HMAC: F926369658DFFF5A -====> Computed HMAC: F926369658DFFF5A -Adding amount -5EB58B0400000000 -New amount -00000000048BB55E -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A9142318D66F84FEF5C4875F933B038DC63831F8DA1388ACFDFFFFFF -Process input script, remaining 25 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done ---- ADD TO HASH FULL: -23E9829BFB4E23FBD3C4848BAA035AF15D73BCB83E510F7F097F90A21A4280D2 -hashPrevout -C7B413870810B6E08F118FBDFA1BF67FAB8F1048FC0F943E8EC91D60733CBCF4 -hashSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 -Presign ready -New APDU received: -E04AFF000100 -state=1 -New APDU received: -E04A80002001F0B48B04000000001600141347E82A037B5DBB38CF8C4759F242B1F5C7E09A -state=1 ---- ADD TO HASH FULL: -F0B48B04000000001600141347E82A037B5DBB38CF8C4759F242B1F5C7E09A -hashOutputs -62BBD24C452AAF0705EF8A79E2E3BF9A20A75A587DBFF2753FEB7B99A0FE6DC6 -Auth Hash: -E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -Segwit parsed once -New APDU received: -E0440080050200000001 -Init transaction parser -Resume SegWit hash -SEGWIT Version -02000000 -SEGWIT HashedPrevouts -C7B413870810B6E08F118FBDFA1BF67FAB8F1048FC0F943E8EC91D60733CBCF4 -SEGWIT HashedSequence -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH FULL: -02000000 ---- ADD TO HASH FULL: -C7B413870810B6E08F118FBDFA1BF67FAB8F1048FC0F943E8EC91D60733CBCF4 ---- ADD TO HASH FULL: -CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC52 ---- ADD TO HASH AUTH: -C7B413870810B6E08F118FBDFA1BF67FAB8F1048FC0F943E8EC91D60733CBCF4CAF35E5224DE16EFA3CCAF41070F6E7B9432B6F79551E629FCA9D1C03B43BC5262BBD24C452AAF0705EF8A79E2E3BF9A20A75A587DBFF2753FEB7B99A0FE6DC6 -Number of inputs : 1 -Process input -New APDU received: -E04480003B013832002BD7F3DB0E87B75A0C4A113F30906230FFAD598BAA37F3AB27FC2C339DE849B9F385000000005EB58B0400000000F926369658DFFF5A19 -Process input -Trusted input used in segwit mode -====> Input HMAC: F926369658DFFF5A -====> Computed HMAC: F926369658DFFF5A ---- ADD TO HASH FULL: -F3DB0E87B75A0C4A113F30906230FFAD598BAA37F3AB27FC2C339DE849B9F38500000000 ---- ADD TO HASH FULL: -19 -Script to read 25 -Process input script, remaining 25 -New APDU received: -E04480001D76A9142318D66F84FEF5C4875F933B038DC63831F8DA1388ACFDFFFFFF -Process input script, remaining 25 ---- ADD TO HASH FULL: -76A9142318D66F84FEF5C4875F933B038DC63831F8DA1388 -Process input script, remaining 1 -Disabling P2SH consumption ---- ADD TO HASH FULL: -AC -SEGWIT Add value -5EB58B0400000000 ---- ADD TO HASH FULL: -5EB58B0400000000 ---- ADD TO HASH FULL: -FDFFFFFF -Process input -Input hashing done -SEGWIT hashedOutputs -62BBD24C452AAF0705EF8A79E2E3BF9A20A75A587DBFF2753FEB7B99A0FE6DC6 -Sign ready -New APDU received: -E04800001B05800000548000000180000000000000000000000100001CF59001 ---- ADD TO HASH FULL: -90F51C0001000000 -Using private component -21E9C06E0D5E169EE7E6C961D2F3F3286666EF74F2CE5EFF6F6108EB6D855DA5 -Hash1 -773DB7D1831DA4C933BD8DED5563DC5E03EF752CC9601A7423059368355CFE68 -Hash2 -824F6D8B43D99E86750D9F290E26BE16CEA93ACD1B8BC045E940B34704C61D60 diff --git a/tests-legacy/data/one-to-one/p2wpkh/tx.json b/tests-legacy/data/one-to-one/p2wpkh/tx.json deleted file mode 100644 index a69ba2470..000000000 --- a/tests-legacy/data/one-to-one/p2wpkh/tx.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "txid": "10a7143a7c64c1eed7a9e8fe7b0d4016c7dfe749d4db4de1f3ec5899f2a24c2c", - "raw": "02000000000101f3db0e87b75a0c4a113f30906230ffad598baa37f3ab27fc2c339de849b9f3850000000000fdffffff01f0b48b04000000001600141347e82a037b5dbb38cf8c4759f242b1f5c7e09a0248304502210087dadcd43ba52983eb19786b20a4a8e6969e1f9330017d1ffc537effad0f7d9102200b293788f2956f6352afad08b6c9f48d213e9e96ff74ffd76a28cf448fb358a1012103455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a390f51c00", - "amount": 76264688, - "fees": 110, - "to": "tb1qzdr7s2sr0dwmkwx033r4nujzk86u0cy6fmzfjk", - "sign_paths": ["m/84'/1'/0'/0/1"], - "change_path": null, - "lock_time": 1897872, - "utxos": [ - { - "txid": "85f3b949e89d332cfc27abf337aa8b59adff306290303f114a0c5ab7870edbf3", - "raw": "0200000000010134ab4e0084423b9da3b7701023a4137c362f742328c5cb6f6d5086130a88b0940000000000fdffffff015eb58b04000000001600142318d66f84fef5c4875f933b038dc63831f8da130248304502210088e618e63f1a908022bb5fc6aaa19b454ac3eec104ffd78319acc0ccfe464378022003ce22f37a945eb2b9e70ebbc321a74c9594efd7c986a4004538b728d30ecb500121027cb75d34b005c4eb9f62bbf2c457d7638e813e757efcec8fa68677d950b63662fbf41c00", - "output_indexes": [0], - "output_amounts": [76264798] - } - ] - -} \ No newline at end of file diff --git a/tests-legacy/prepare_tests_lib.sh b/tests-legacy/prepare_tests_lib.sh deleted file mode 100755 index 39b9264eb..000000000 --- a/tests-legacy/prepare_tests_lib.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -bash ./clean_tests.sh - -# TODO: tests currently not working with DEBUG=1 - -cd .. -make clean -make -j DEBUG=0 COIN=bitcoin -mv bin/ tests-legacy/bitcoin-bin -make clean -make -j DEBUG=0 COIN=bitcoin_testnet_lib -mv bin/ tests-legacy/bitcoin-testnet-bin diff --git a/tests-legacy/prepare_tests_native.sh b/tests-legacy/prepare_tests_native.sh deleted file mode 100755 index ed3883cee..000000000 --- a/tests-legacy/prepare_tests_native.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -bash ./clean_tests.sh - -# TODO: tests currently not working with DEBUG=1 - -cd .. -make clean -make -j DEBUG=0 COIN=bitcoin_testnet -mv bin/ tests-legacy/bitcoin-testnet-bin diff --git a/tests-legacy/requirements.txt b/tests-legacy/requirements.txt deleted file mode 100644 index f8181210a..000000000 --- a/tests-legacy/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -typing-extensions>=3.7.4.3 -pytest>=6.2.1 -ledgercomm>=1.1.0 -ecdsa>=0.16.1,<0.17.0 diff --git a/tests-legacy/setup.cfg b/tests-legacy/setup.cfg deleted file mode 100644 index 5bf2168fc..000000000 --- a/tests-legacy/setup.cfg +++ /dev/null @@ -1,20 +0,0 @@ -[tool:pytest] -addopts = --strict-markers - -[pylint] -disable = C0114, # missing-module-docstring - C0115, # missing-class-docstring - C0116, # missing-function-docstring - C0103, # invalid-name - R0801, # duplicate-code - R0913 # too-many-arguments -extension-pkg-whitelist=hid - -[pycodestyle] -max-line-length = 100 - -[mypy-hid.*] -ignore_missing_imports = True - -[mypy-pytest.*] -ignore_missing_imports = True diff --git a/tests-legacy/test_get_coin_version.py b/tests-legacy/test_get_coin_version.py deleted file mode 100644 index e475a9a56..000000000 --- a/tests-legacy/test_get_coin_version.py +++ /dev/null @@ -1,9 +0,0 @@ -def test_get_coin_version(cmd): - (p2pkh_prefix, p2sh_prefix, coin_family, coin_name, coin_ticker) = cmd.get_coin_version() - - # Bitcoin app: (0x00, 0x05, 0x01, "Bitcoin", "BTC") - assert (p2pkh_prefix, - p2sh_prefix, - coin_family, - coin_name, - coin_ticker) == (0x6F, 0xC4, 0x01, "Bitcoin", "TEST") diff --git a/tests-legacy/test_get_pubkey.py b/tests-legacy/test_get_pubkey.py deleted file mode 100644 index 1dfa6939c..000000000 --- a/tests-legacy/test_get_pubkey.py +++ /dev/null @@ -1,42 +0,0 @@ -from bitcoin_client.bitcoin_base_cmd import AddrType - - -def test_get_public_key(cmd): - # legacy address - pub_key, addr, bip32_chain_code = cmd.get_public_key( - addr_type=AddrType.Legacy, - bip32_path="m/44'/1'/0'/0/0", - display=False - ) - - assert pub_key == bytes.fromhex("04" - "ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718" - "b45f5224b05ebbad09f43594b7bd8dc0eff4519a07cbab37ecc66e0001ab959a") - assert addr == "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm" - assert bip32_chain_code == bytes.fromhex("0322c8f681e7274e767cee09b8e41770e6d2afd504fd5f85dfaab3e1ff6cdfcc") - - # P2SH-P2WPKH address - pub_key, addr, bip32_chain_code = cmd.get_public_key( - addr_type=AddrType.P2SH_P2WPKH, - bip32_path="m/49'/1'/0'/0/0", - display=False - ) - - assert pub_key == bytes.fromhex("04" - "a80f007194d53d37f6f99539f635390588c4e1c328b098295f61af40d60cb28a" - "7b5649e8121c89148fbab7d693108654685b4e939d9c1bc55a71b43f389929fe") - assert addr == "2MyHkbusvLomaarGYMqyq7q9pSBYJRwWcsw" - assert bip32_chain_code == bytes.fromhex("dc699bc018541f456df1ebd4dea516a633a6260e0a701eba143449adc2ca63f3") - - # bech32 address - pub_key, addr, bip32_chain_code = cmd.get_public_key( - addr_type=AddrType.BECH32, - bip32_path="m/84'/1'/0'/0/0", - display=False - ) - - assert pub_key == bytes.fromhex("04" - "7cb75d34b005c4eb9f62bbf2c457d7638e813e757efcec8fa68677d950b63662" - "648e4f638cabc4e4383fa3fe8348456e46fa56742dcf500a5b50dc1d403492f0") - assert addr == "tb1qzdr7s2sr0dwmkwx033r4nujzk86u0cy6fmzfjk" - assert bip32_chain_code == bytes.fromhex("efd851020a3827ba0d3fd4375910f0ed55dbe8c5d740b37559e993b1d623a956") diff --git a/tests-legacy/test_get_random.py b/tests-legacy/test_get_random.py deleted file mode 100644 index a4570f4c5..000000000 --- a/tests-legacy/test_get_random.py +++ /dev/null @@ -1,15 +0,0 @@ -import pytest - -from bitcoin_client.exception import IncorrectLengthError - - -def test_random(cmd): - r: bytes = cmd.get_random(n=5) - assert len(r) == 5 - - r = cmd.get_random(n=32) - assert len(r) == 32 - - # max length is 248! - with pytest.raises(IncorrectLengthError): - cmd.get_random(n=249) diff --git a/tests-legacy/test_get_trusted_inputs.py b/tests-legacy/test_get_trusted_inputs.py deleted file mode 100644 index 7c2ff475c..000000000 --- a/tests-legacy/test_get_trusted_inputs.py +++ /dev/null @@ -1,174 +0,0 @@ -from io import BytesIO - -from bitcoin_client.hwi.serialization import CTransaction -from bitcoin_client.utils import deser_trusted_input - - -def test_get_trusted_inputs(cmd): - raw_tx: bytes = bytes.fromhex( - # Version no (4 bytes little endian) - "02000000" - # In-counter (varint 1-9 bytes) - "02" - # [1] Previous Transaction hash (32 bytes) - "40d1ae8a596b34f48b303e853c56f8f6f54c483babc16978eb182e2154d5f2ab" - # [1] Previous Txout-index (4 bytes little endian) - "00000000" - # [1] Txin-script length (varint 1-9 bytes) - "6b" - # [1] scriptSig (0x6b = 107 bytes) - "48" - "3045" - "0221" - # r - "00ca145f0694ffaedd333d3724ce3f4e44aabc0ed5128113660d11f917b3c52053" - "0220" - # s - "7bec7c66328bace92bd525f385a9aa1261b83e0f92310ea1850488b40bd25a5d" - # sighash - "01" - "21" - # compressed public key - "032006c64cdd0485e068c1e22ba0fa267ca02ca0c2b34cdc6dd08cba23796b6ee7" - # [1] sequence_no (4 bytes little endian) - "fdffffff" - # [2] Previous Transaction hash (32 bytes) - "40d1ae8a596b34f48b303e853c56f8f6f54c483babc16978eb182e2154d5f2ab" - # [2] Previous Txout-index (4 bytes little endian) - "01000000" - # [2] Txin-script length (varint 1-9 bytes) - "6a" - # [2] scriptSig (0x6a = 106 bytes) - "47" - "3044" - "0220" - # r - "2a5d54a1635a7a0ae22cef76d8144ca2a1c3c035c87e7cd0280ab43d34510906" - "0220" - # s - "0c7e07e384b3620ccd2f97b5c08f5893357c653edc2b8570f099d9ff34a0285c" - "01" - "21" - # compressed public key - "02d82f3fa29d38297db8e1879010c27f27533439c868b1cc6af27dd3d33b243dec" - # [2] sequence_no (4 bytes little endian) - "fdffffff" - # Out-counter (varint 1-9 bytes) - "01" - # [1] Value (8 bytes little endian) - "d7ee7c0100000000" # 0.24964823 BTC - # [1] Txout-script length (varint 1-9 bytes) - "19" - # [1] scriptPubKey (0x19 = 25 bytes) - "76a914" - "0ea263ff8b0da6e8d187de76f6a362beadab7811" - "88ac" - # lock_time (4 bytes little endian) - "e3691900" - ) - - bip141_raw_tx: bytes = bytes.fromhex( - # Version no (4 bytes little endian) - "02000000" - # marker (1 byte) + flag (1 byte) - "0001" - # In-counter (varint 1-9 bytes) - "02" - # [1] Previous Transaction hash (32 bytes) - "e7576f53b5d92f9880b125d0622782fef40b0121eb4555c9d3a7be54e635cd6e" - # [1] Previous Txout-index (4 bytes little endian) - "00000000" - # [1] Txin-script length (varint 1-9 bytes) - "17" - # [1] scriptSig (0x17 = 23 bytes) - "160014" - "4c9fca3fd23ae5cc1f0dfe46b446da611219c020" # hash160(pubkey) - # [1] sequence_no (4 bytes little endian) - "fdffffff" - # [2] Previous Transaction hash (32 bytes) - "4ba91d8e1cedbfecdceda7f3432f618a2f0e122c66a63fe0c53a14de6305e5dc" - # [2] Previous Txout-index (4 bytes little endian) - "01000000" - # [2] Txin-script length (varint 1-9 bytes) - "17" - # [2] scriptSig (0x17 = 23 bytes) - "160014" - "92a9159a0ae40a748c18bd486ea13da85422450c" # hash160(pubkey) - # [2] sequence_no (4 bytes little endian) - "fdffffff" - # Out-counter (varint 1-9 bytes) - "02" - # [1] Value (8 bytes little endian) - "7f19060000000000" - # [1] Txout-script length (varint 1-9 bytes) - "17" - # [1] scriptPubKey (0x17 = 23 bytes) - "a9141a56dea1ff8a3f633916560fed5942400d17080b87" - # [2] Value (8 bytes little endian) - "60ae0a0000000000" - # [2] Txout-script length (varint 1-9 bytes) - "17" - # [2] scriptPubKey (0x17 = 23 bytes) - "a9147c28b075f506d829e2e2bacf897c1b5b0d309c1a87" - # Witnesses - "02" # number of items to push on the stack - "48" # length of 1st stack item - # 1st item - "3045" - "0221" - # r - "00c791ff9a5886903fbd3c1289f281e1d5a3e330f1558ea5df725bcd780b285677" - "0220" - # s - "2d76349a78585ea66df6eef5ab48a0348cc337994a1d6357a6e4e4328a343f6d" - "01" - # 2nd item - "21" # length of 2nd stack item - # compressed public key - "02623ed09f8c192938f7a638fbdd5dd7c297f86e41be8d7dff4f1c904f0685f227" - "02" # number of items to push on the stack - # 1st item - "48" # length of 1st stack item - "3045" - "0221" - # r - "008f2f017f5fa4fdd7dcfe41c83f4a71a726626cdb490d652edcb408b9a4638b7a" - "0220" - # s - "439c15a7f7a03f2876dec5392f2247437b57b227fea294f4019d06462f938b53" - "01" - # 2nd item - "21" # length of 2nd stack item - # compressed public key - "0347e9143aa6457c72a48d85b5065edc40d3a49f319d54fc4979dc3b95de949a41" - # lock_time (4 bytes little endian) - "d7681900" - ) - tx: CTransaction - bip141_tx: CTransaction - output_index: int - trusted_input: bytes - - tx = CTransaction() - tx.deserialize(BytesIO(raw_tx)) - tx.calc_sha256() - - output_index = 0 - trusted_input = cmd.get_trusted_input(utxo=tx, output_index=output_index) - - _, _, _, prev_txid, out_index, amount, _ = deser_trusted_input(trusted_input) - assert out_index == output_index - assert prev_txid == tx.sha256.to_bytes(32, byteorder="little") - assert amount == tx.vout[out_index].nValue - - bip141_tx = CTransaction() - bip141_tx.deserialize(BytesIO(bip141_raw_tx)) - bip141_tx.calc_sha256() - - output_index = 1 - trusted_input = cmd.get_trusted_input(utxo=bip141_tx, output_index=output_index) - - _, _, _, prev_txid, out_index, amount, _ = deser_trusted_input(trusted_input) - assert out_index == output_index - assert prev_txid == bip141_tx.sha256.to_bytes(32, byteorder="little") - assert amount == bip141_tx.vout[out_index].nValue diff --git a/tests-legacy/test_sign.py b/tests-legacy/test_sign.py deleted file mode 100644 index 11fd12813..000000000 --- a/tests-legacy/test_sign.py +++ /dev/null @@ -1,89 +0,0 @@ -from hashlib import sha256 -import json -from pathlib import Path -from typing import Tuple, List, Dict, Any -import pytest - -from ecdsa.curves import SECP256k1 -from ecdsa.keys import VerifyingKey -from ecdsa.util import sigdecode_der - -from bitcoin_client.hwi.serialization import CTransaction -from bitcoin_client.exception import ConditionOfUseNotSatisfiedError -from utils import has_automation - - -def sign_from_json(cmd, filepath: Path): - tx_dct: Dict[str, Any] = json.load(open(filepath, "r")) - - raw_utxos: List[Tuple[bytes, int]] = [ - (bytes.fromhex(utxo_dct["raw"]), output_index) - for utxo_dct in tx_dct["utxos"] - for output_index in utxo_dct["output_indexes"] - ] - to_address: str = tx_dct["to"] - to_amount: int = tx_dct["amount"] - fees: int = tx_dct["fees"] - - sigs = cmd.sign_new_tx(address=to_address, - amount=to_amount, - fees=fees, - change_path=tx_dct["change_path"], - sign_paths=tx_dct["sign_paths"], - raw_utxos=raw_utxos, - lock_time=tx_dct["lock_time"]) - - expected_tx = CTransaction.from_bytes(bytes.fromhex(tx_dct["raw"])) - witnesses = expected_tx.wit.vtxinwit - for witness, (tx_hash_digest, sign_pub_key, (v, der_sig)) in zip(witnesses, sigs): - expected_der_sig, expected_pubkey = witness.scriptWitness.stack - assert expected_pubkey == sign_pub_key - assert expected_der_sig == der_sig - pk: VerifyingKey = VerifyingKey.from_string( - sign_pub_key, - curve=SECP256k1, - hashfunc=sha256 - ) - assert pk.verify_digest(signature=der_sig[:-1], # remove sighash - digest=tx_hash_digest, - sigdecode=sigdecode_der) is True - - -def test_untrusted_hash_sign_fail_nonzero_p1_p2(cmd, transport): - # payloads do not matter, should check and fail before checking it (but non-empty is required) - sw, _ = transport.exchange(0xE0, 0x48, 0x01, 0x01, None, b"\x00") - assert sw == 0x6B00, "should fail with p1 and p2 both non-zero" - sw, _ = transport.exchange(0xE0, 0x48, 0x01, 0x00, None, b"\x00") - assert sw == 0x6B00, "should fail with non-zero p1" - sw, _ = transport.exchange(0xE0, 0x48, 0x00, 0x01, None, b"\x00") - assert sw == 0x6B00, "should fail with non-zero p2" - - -def test_untrusted_hash_sign_fail_short_payload(cmd, transport): - # should fail if the payload is less than 7 bytes - sw, _ = transport.exchange(0xE0, 0x48, 0x00, 0x00, None, b"\x01\x02\x03\x04\x05\x06") - assert sw == 0x6700 - - -@has_automation("automations/accept.json") -def test_sign_p2wpkh_accept(cmd): - for filepath in Path("data").rglob("p2wpkh/tx.json"): - sign_from_json(cmd, filepath) - - -@has_automation("automations/accept.json") -def test_sign_p2sh_p2wpkh_accept(cmd): - for filepath in Path("data").rglob("p2sh-p2wpkh/tx.json"): - sign_from_json(cmd, filepath) - - -@has_automation("automations/accept.json") -def test_sign_p2pkh_accept(cmd): - for filepath in Path("data").rglob("p2pkh/tx.json"): - sign_from_json(cmd, filepath) - - -@has_automation("automations/reject.json") -def test_sign_fail_p2pkh_reject(cmd): - with pytest.raises(ConditionOfUseNotSatisfiedError): - sign_from_json(cmd, "./data/one-to-one/p2pkh/tx.json") diff --git a/tests-legacy/utils/__init__.py b/tests-legacy/utils/__init__.py deleted file mode 100644 index fbdcc5d6f..000000000 --- a/tests-legacy/utils/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ - -def has_automation(filename): - """Decorator that adds the automation_file attribute to a test function. - - When present, this filename will be used as the --automation file when creating the speculos fixture. - """ - def decorator(func): - func.automation_file = filename - return func - return decorator diff --git a/tests/README.md b/tests/README.md index d37f6078b..ddfc00847 100644 --- a/tests/README.md +++ b/tests/README.md @@ -24,17 +24,18 @@ Build the app as normal from the root folder. For convenience, you probably want DEBUG=1 make ``` -Then run all the tests from this folder with: +Then run all the tests from this folder, specifying the device: nanos, nanox, nanosp, stax, or all: ``` -pytest +pytest --device yourdevice ``` +You can enable the screen display with the option `--display` -## Launch with your Nano S/X +## Launch with your Nano S/X/SP or Stax Compile and install the app on your device as normal. -To run the tests on your Ledger Nano S/X you also need to install an optional dependency +To run the tests on your Ledger device you also need to install an optional dependency ``` pip install ledgercomm[hid] @@ -43,7 +44,5 @@ pip install ledgercomm[hid] Be sure to have you device connected through USB (without any other software interacting with it) and run ``` -pytest --hid -``` - -Please note that tests that require an automation file are meant for speculos, and will currently hang the test suite. \ No newline at end of file +pytest --device yourdevice --backend ledgercomm +``` \ No newline at end of file diff --git a/tests/automations/register_wallet_accept.json b/tests/automations/register_wallet_accept.json index 6371aa704..d96348a90 100644 --- a/tests/automations/register_wallet_accept.json +++ b/tests/automations/register_wallet_accept.json @@ -2,10 +2,19 @@ "version": 1, "rules": [ { - "regexp": "Register wallet|Policy map|Key", + "regexp": "Register wallet|Wallet name|Wallet policy|Key", "actions": [ ["button", 2, true], - ["button", 2, false] + ["button", 2, false], + [ "finger", 55, 550, true], + [ "finger", 55, 550, false] + ] + }, + { + "regexp": "Tap to continue", + "actions": [ + ["finger", 55, 550, true], + ["finger", 55, 550, false] ] }, { @@ -14,7 +23,14 @@ [ "button", 1, true ], [ "button", 2, true ], [ "button", 2, false ], - [ "button", 1, false ] + [ "button", 1, false ], + [ "finger", 55, 550, true] + ] + }, + { + "regexp": "Processing|REGISTERED", + "actions": [ + ["finger", 55, 550, false] ] } ] diff --git a/tests/automations/register_wallet_reject.json b/tests/automations/register_wallet_reject.json index 7e9c6ebb2..f6d928120 100644 --- a/tests/automations/register_wallet_reject.json +++ b/tests/automations/register_wallet_reject.json @@ -2,10 +2,12 @@ "version": 1, "rules": [ { - "regexp": "Register wallet|Policy map|Key|Approve", + "regexp": "Register wallet|Wallet name|Wallet policy|Key|Approve|Cancel", "actions": [ ["button", 2, true], - ["button", 2, false] + ["button", 2, false], + ["finger", 55, 650, true], + ["finger", 55, 650, false] ] }, { diff --git a/tests/automations/sign_message_accept.json b/tests/automations/sign_message_accept.json index 505f5e39c..710a2cf64 100644 --- a/tests/automations/sign_message_accept.json +++ b/tests/automations/sign_message_accept.json @@ -2,12 +2,31 @@ "version": 1, "rules": [ { - "regexp": "Path|Message hash", + "regexp": "Path|Message hash|Message content", "actions": [ ["button", 2, true], ["button", 2, false] ] }, + { + "regexp": "Tap to continue", + "actions": [ + ["finger", 55, 550, true], + ["finger", 55, 550, false] + ] + }, + { + "regexp": "Approve", + "actions": [ + [ "finger", 55, 550, true] + ] + }, + { + "regexp": "Processing", + "actions": [ + ["finger", 55, 550, false] + ] + }, { "regexp": "Sign", "conditions": [ diff --git a/tests/automations/sign_message_reject.json b/tests/automations/sign_message_reject.json index b33cfc335..2af845637 100644 --- a/tests/automations/sign_message_reject.json +++ b/tests/automations/sign_message_reject.json @@ -2,19 +2,37 @@ "version": 1, "rules": [ { - "regexp": "Sign|Path|Message hash|Approve", + "regexp": "Review message", + "actions": [ + ["finger", 55, 550, true], + ["finger", 55, 550, false] + ] + }, + { + "regexp": "Message hash|Message content", "actions": [ ["button", 2, true], - ["button", 2, false] + ["button", 2, false], + ["finger", 55, 650, true], + ["finger", 55, 650, false] ] }, { - "regexp": "Reject", + "regexp": "Yes, Reject|Reject", "actions": [ [ "button", 1, true ], [ "button", 2, true ], - [ "button", 2, false ], - [ "button", 1, false ] + [ "button", 2, false], + [ "button", 1, false], + [ "finger", 55, 550, true], + [ "finger", 55, 550, false] + ] + }, + { + "regexp": "Sign|Path", + "actions": [ + ["button", 2, true], + ["button", 2, false] ] } ] diff --git a/tests/automations/sign_with_default_wallet_accept.json b/tests/automations/sign_with_default_wallet_accept.json index 09352e5f8..128b4deb9 100644 --- a/tests/automations/sign_with_default_wallet_accept.json +++ b/tests/automations/sign_with_default_wallet_accept.json @@ -1,6 +1,16 @@ { "version": 1, "rules": [ + { + "regexp": "Approve|Accept|Hold to sign", + "actions": [ + [ "button", 1, true ], + [ "button", 2, true ], + [ "button", 2, false ], + [ "button", 1, false ], + [ "finger", 55, 550, true] + ] + }, { "regexp": "Review|Amount|Address|Confirm|Fees", "actions": [ @@ -9,12 +19,16 @@ ] }, { - "regexp": "Approve|Accept", + "regexp": "Tap to continue", "actions": [ - [ "button", 1, true ], - [ "button", 2, true ], - [ "button", 2, false ], - [ "button", 1, false ] + ["finger", 55, 550, true], + ["finger", 55, 550, false] + ] + }, + { + "regexp": "SIGNED", + "actions": [ + ["finger", 55, 550, false] ] } ] diff --git a/tests/automations/sign_with_default_wallet_accept_highfee.json b/tests/automations/sign_with_default_wallet_accept_highfee.json new file mode 100644 index 000000000..078de0e04 --- /dev/null +++ b/tests/automations/sign_with_default_wallet_accept_highfee.json @@ -0,0 +1,37 @@ +{ + "version": 1, + "rules": [ + { + "regexp": "Tap to continue|Fees are above 10%", + "actions": [ + ["button", 2, true], + ["button", 2, false], + ["finger", 55, 550, true], + ["finger", 55, 550, false] + ] + }, + { + "regexp": "Approve|Accept|Hold to sign|Continue", + "actions": [ + [ "button", 1, true ], + [ "button", 2, true ], + [ "button", 2, false ], + [ "button", 1, false ], + [ "finger", 55, 550, true] + ] + }, + { + "regexp": "Review|Amount|Address|Confirm|Fees", + "actions": [ + ["button", 2, true], + ["button", 2, false] + ] + }, + { + "regexp": "SIGNED", + "actions": [ + ["finger", 55, 550, false] + ] + } + ] +} diff --git a/tests/automations/sign_with_default_wallet_accept_nondefault_sighash.json b/tests/automations/sign_with_default_wallet_accept_nondefault_sighash.json new file mode 100644 index 000000000..2833c434d --- /dev/null +++ b/tests/automations/sign_with_default_wallet_accept_nondefault_sighash.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "rules": [ + { + "regexp": "Review|Amount|Address|Confirm|Fees|Non-default sighash|Reject", + "actions": [ + ["button", 2, true], + ["button", 2, false] + ] + }, + { + "regexp": "Tap to continue|Warning", + "actions": [ + ["finger", 55, 550, true], + ["finger", 55, 550, false] + ] + }, + { + "regexp": "Hold|Accept|Continue|Approve", + "actions": [ + [ "button", 1, true ], + [ "button", 2, true ], + [ "button", 2, false ], + [ "button", 1, false ], + [ "finger", 55, 550, true] + ] + }, + { + "regexp": "SIGNED", + "actions": [ + ["finger", 55, 550, false] + ] + } + ] +} diff --git a/tests/automations/sign_with_default_wallet_missing_nonwitnessutxo_accept.json b/tests/automations/sign_with_default_wallet_missing_nonwitnessutxo_accept.json new file mode 100644 index 000000000..09093e2cc --- /dev/null +++ b/tests/automations/sign_with_default_wallet_missing_nonwitnessutxo_accept.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "rules": [ + { + "regexp": "Hold to sign", + "actions": [ + ["finger", 55, 550, true] + ] + }, + { + "regexp": "Processing", + "actions": [ + ["finger", 55, 550, false] + ] + }, + { + "regexp": "Continue|Tap to continue|Approve|Accept|Warning", + "actions": [ + [ "button", 1, true ], + [ "button", 2, true ], + [ "button", 2, false ], + [ "button", 1, false ], + [ "finger", 55, 550, true], + [ "finger", 55, 550, false] + ] + }, + { + "regexp": "Unverified|Update|or third party|Review|Amount|Address|Confirm|Fees", + "actions": [ + ["button", 2, true], + ["button", 2, false] + ] + } + ] +} diff --git a/tests/automations/sign_with_wallet_accept.json b/tests/automations/sign_with_wallet_accept.json index 37a08b741..2fbf27285 100644 --- a/tests/automations/sign_with_wallet_accept.json +++ b/tests/automations/sign_with_wallet_accept.json @@ -2,19 +2,39 @@ "version": 1, "rules": [ { - "regexp": "Spend from|Review|Amount|Address|Confirm|Fees", + "regexp": "Spend from|Wallet name|Review|Amount|Address|Confirm|Fees", "actions": [ ["button", 2, true], ["button", 2, false] ] }, { - "regexp": "Approve|Accept", + "regexp": "Hold to sign", + "actions": [ + ["finger", 55, 550, true] + ] + }, + { + "regexp": "Tap to continue", + "actions": [ + ["finger", 55, 550, true], + ["finger", 55, 550, false] + ] + }, + { + "regexp": "Approve|Accept|Confirm", "actions": [ [ "button", 1, true ], [ "button", 2, true ], [ "button", 2, false ], - [ "button", 1, false ] + [ "button", 1, false ], + ["finger", 55, 550, true] + ] + }, + { + "regexp": "Processing", + "actions": [ + ["finger", 55, 550, false] ] } ] diff --git a/tests/automations/sign_with_wallet_external_inputs_accept.json b/tests/automations/sign_with_wallet_external_inputs_accept.json index d29cf62d0..90bc0e358 100644 --- a/tests/automations/sign_with_wallet_external_inputs_accept.json +++ b/tests/automations/sign_with_wallet_external_inputs_accept.json @@ -2,19 +2,39 @@ "version": 1, "rules": [ { - "regexp": "Spend from|There are|Reject if you're|Review|Amount|Address|Confirm|Fees", + "regexp": "Hold to sign", + "actions": [ + ["finger", 55, 550, true] + ] + }, + { + "regexp": "Spend from|Wallet name|There are|Reject if you['-]re|Review|Amount|Address|Confirm|Fees", "actions": [ ["button", 2, true], ["button", 2, false] ] }, + { + "regexp": "Tap to continue|Warning", + "actions": [ + ["finger", 55, 550, true], + ["finger", 55, 550, false] + ] + }, { "regexp": "Continue|Approve|Accept", "actions": [ [ "button", 1, true ], [ "button", 2, true ], [ "button", 2, false ], - [ "button", 1, false ] + [ "button", 1, false ], + [ "finger", 55, 550, true] + ] + }, + { + "regexp": "SIGNED", + "actions": [ + ["finger", 55, 550, false] ] } ] diff --git a/tests/automations/sign_with_wallet_missing_nonwitnessutxo_accept.json b/tests/automations/sign_with_wallet_missing_nonwitnessutxo_accept.json new file mode 100644 index 000000000..33fd80cad --- /dev/null +++ b/tests/automations/sign_with_wallet_missing_nonwitnessutxo_accept.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "rules": [ + { + "regexp": "Hold to sign|Confirm wallet name", + "actions": [ + ["finger", 55, 550, true] + ] + }, + { + "regexp": "Processing", + "actions": [ + ["finger", 55, 550, false] + ] + }, + { + "text": "Approve", + "actions": [ + [ "button", 1, true ], + [ "button", 2, true ], + [ "button", 2, false ], + [ "button", 1, false ], + [ "finger", 55, 550, true] + ] + }, + { + "regexp": "Continue|Tap to continue|Accept|Warning", + "actions": [ + [ "button", 1, true ], + [ "button", 2, true ], + [ "button", 2, false ], + [ "button", 1, false ], + [ "finger", 55, 550, true], + [ "finger", 55, 550, false] + ] + }, + { + "regexp": "Unverified|Update|or third party|Spend from|Wallet name|Review|Amount|Address|Confirm|Fees", + "actions": [ + ["button", 2, true], + ["button", 2, false] + ] + } + ] +} diff --git a/tests/bitcoin.conf b/tests/bitcoin.conf index 80e2fe216..515039545 100644 --- a/tests/bitcoin.conf +++ b/tests/bitcoin.conf @@ -12,4 +12,6 @@ txindex=1 # [others] daemon=1 debug=1 -fallbackfee=0.00001 \ No newline at end of file +fallbackfee=0.00001 +minrelaytxfee=0 +blockmintxfee=0 diff --git a/tests/conftest.py b/tests/conftest.py index 3b0ab4746..cffd00cf6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,12 @@ +import sys +import os + +absolute_path = os.path.dirname(os.path.abspath(__file__)) +relative_bitcoin_path = ('../bitcoin_client') +absolute_bitcoin_client_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../') +sys.path.append(os.path.join(absolute_path, relative_bitcoin_path)) + import random -import binascii -import hashlib from typing import Tuple from test_utils.fixtures import * @@ -11,9 +17,36 @@ import subprocess from time import sleep from decimal import Decimal +from pathlib import Path + +from ledger_bitcoin import Chain +from ledger_bitcoin.common import sha256 +import ledger_bitcoin._base58 as base58 + +from ragger.conftest import configuration +from ragger.backend.interface import BackendInterface +from ragger.backend import RaisePolicy +from ragger_bitcoin import createRaggerClient, RaggerClient + +########################### +### CONFIGURATION START ### +########################### + +# You can configure optional parameters by overriding the value of ragger.configuration.OPTIONAL_CONFIGURATION +# Please refer to ragger/conftest/configuration.py for their descriptions and accepted values + +MNEMONIC = "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster seven myth punch hobby comfort wild raise skin" +configuration.OPTIONAL.CUSTOM_SEED = MNEMONIC +configuration.OPTIONAL.BACKEND_SCOPE = "function" -import bitcoin_client.ledger_bitcoin._base58 as base58 -from bitcoin_client.ledger_bitcoin.common import sha256 + +######################### +### CONFIGURATION END ### +######################### +TESTS_ROOT_DIR = Path(__file__).parent + +# Pull all features from the base ragger conftest using the overridden configuration +pytest_plugins = ("ragger.conftest.base_conftest", ) random.seed(0) # make sure tests are repeatable @@ -22,7 +55,7 @@ os.environ['SPECULOS_APPNAME'] = f'Bitcoin Test:{get_app_version()}' -BITCOIN_DIRNAME = os.getenv("BITCOIN_DIRNAME", ".test_bitcoin") +BITCOIN_DIRNAME = os.getenv("BITCOIN_DIRNAME", "tests/.test_bitcoin") rpc_url = "http://%s:%s@%s:%s" % ( @@ -70,7 +103,7 @@ def run_bitcoind(): # Run bitcoind in a separate folder os.makedirs(BITCOIN_DIRNAME, exist_ok=True) - bitcoind = os.getenv("BITCOIND", "bitcoind") + bitcoind = os.getenv("BITCOIND", "/bitcoin/bin/bitcoind") shutil.copy(os.path.join(os.path.dirname(__file__), "bitcoin.conf"), BITCOIN_DIRNAME) subprocess.Popen([bitcoind, f"--datadir={BITCOIN_DIRNAME}"]) @@ -134,7 +167,8 @@ def get_unique_wallet_name() -> str: def create_new_wallet() -> Tuple[str, str]: """Creates a new descriptor-enabled wallet in bitcoin-core. Each new wallet has an increasing counter as - part of it's name in order to avoid conflicts.""" + part of it's name in order to avoid conflicts. Returns the wallet name and the xpub (dropping the key origin + information).""" wallet_name = get_unique_wallet_name() @@ -144,11 +178,14 @@ def create_new_wallet() -> Tuple[str, str]: get_rpc().createwallet(wallet_name=wallet_name, descriptors=True) wallet_rpc = get_wallet_rpc(wallet_name) + all_descriptors = wallet_rpc.listdescriptors()["descriptors"] descriptor: str = next(filter(lambda d: d["desc"].startswith( - "pkh"), wallet_rpc.listdescriptors()["descriptors"]))["desc"] + "pkh") and "/0/*" in d["desc"], all_descriptors))["desc"] + core_xpub_orig = descriptor[descriptor.index("(")+1: descriptor.index("/0/*")] + core_xpub = core_xpub_orig[core_xpub_orig.find("]") + 1:] - return wallet_name, core_xpub_orig + return wallet_name, core_xpub def generate_blocks(n): @@ -163,3 +200,18 @@ def testnet_to_regtest_addr(addr: str) -> str: if (hrp != "tb"): raise ValueError("Not a valid testnet bech32m string") return segwit_addr.bech32_encode("bcrt", data, spec) + + +@pytest.fixture +def client(bitcoin_network: str, backend: BackendInterface) -> RaggerClient: + if bitcoin_network == "main": + chain = Chain.MAIN + elif bitcoin_network == "test": + chain = Chain.TEST + else: + raise ValueError( + f'Invalid value for BITCOIN_NETWORK: {bitcoin_network}') + + backend.raise_policy = RaisePolicy.RAISE_CUSTOM + backend.whitelisted_status = [0x9000, 0xE000] + return createRaggerClient(backend, chain=chain, debug=True, screenshot_dir=TESTS_ROOT_DIR) diff --git a/tests/instructions.py b/tests/instructions.py new file mode 100644 index 000000000..ad0711b3c --- /dev/null +++ b/tests/instructions.py @@ -0,0 +1,358 @@ +from ragger.navigator import NavInsID +from ragger.firmware import Firmware + +from ragger_bitcoin.ragger_instructions import Instructions + + +def message_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.nano_skip_screen("Path") + instructions.same_request("Sign") + else: + instructions.confirm_message() + return instructions + + +def message_instruction_approve_long(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.nano_skip_screen("Path") + instructions.same_request("Processing") + instructions.new_request("Processing") + instructions.new_request("Processing") + instructions.new_request("Processing") + instructions.new_request("Sign") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_message() + return instructions + + +def message_instruction_reject(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Reject") + else: + instructions.reject_message() + + return instructions + + +def pubkey_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + else: + instructions.choice_confirm() + return instructions + + +def pubkey_instruction_reject_early(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Reject") + else: + instructions.footer_cancel() + return instructions + + +def pubkey_reject(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.nano_skip_screen("Reject") + instructions.same_request("Reject") + else: + instructions.choice_reject() + + return instructions + + +def wallet_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + else: + instructions.address_confirm() + return instructions + + +def register_wallet_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + else: + instructions.choice_confirm() + instructions.choice_confirm() + instructions.choice_confirm() + return instructions + + +def register_wallet_instruction_approve_long(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + instructions.new_request("Approve") + else: + instructions.choice_confirm() + instructions.choice_confirm() + instructions.choice_confirm() + instructions.choice_confirm() + return instructions + + +def register_wallet_instruction_approve_unusual(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Approve") + instructions.new_request("Approve") + else: + instructions.choice_confirm() + instructions.choice_confirm() + return instructions + + +def register_wallet_instruction_reject(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Reject") + else: + instructions.choice_reject() + + return instructions + + +def sign_psbt_instruction_tap(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + return instructions + + instructions.navigate_end_of_flow() + return instructions + + +def sign_psbt_instruction_approve(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_2(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Sign") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_3(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.warning_accept() + instructions.same_request_confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_4(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_5(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Sign") + else: + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_6(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Sign") + else: + instructions.confirm_wallet() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_7(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.confirm_wallet() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_8(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.confirm_wallet() + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_9(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_external_inputs(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_external_inputs_2(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.same_request("Sign") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def sign_psbt_instruction_approve_10(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue") + instructions.new_request("Continue") + instructions.new_request("Sign") + else: + instructions.warning_accept() + instructions.navigate_end_of_flow() + instructions.navigate_end_of_flow() + instructions.confirm_transaction() + return instructions + + +def e2e_register_wallet_instruction(model: Firmware, n_keys) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + for _ in range(n_keys + 1): + instructions.new_request("Approve", save_screenshot=False) + else: + for _ in range(n_keys + 1): + instructions.choice_confirm(save_screenshot=False) + return instructions + + +def e2e_sign_psbt_instruction(model: Firmware) -> Instructions: + instructions = Instructions(model) + + if model.name.startswith("nano"): + instructions.new_request("Continue", save_screenshot=False) + instructions.new_request("Continue", save_screenshot=False) + instructions.new_request("Sign", save_screenshot=False) + else: + instructions.confirm_wallet(save_screenshot=False) + instructions.navigate_end_of_flow(save_screenshot=False) + instructions.navigate_end_of_flow(save_screenshot=False) + instructions.confirm_transaction(save_screenshot=False) + return instructions diff --git a/tests/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt b/tests/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt new file mode 100644 index 000000000..91e523b70 --- /dev/null +++ b/tests/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt @@ -0,0 +1 @@ +cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSBAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIEAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA diff --git a/tests/psbt/sighash/sighash-all-sign.psbt b/tests/psbt/sighash/sighash-all-sign.psbt new file mode 100644 index 000000000..8144143d8 --- /dev/null +++ b/tests/psbt/sighash/sighash-all-sign.psbt @@ -0,0 +1 @@ +cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwQBAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBAEAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA diff --git a/tests/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt b/tests/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt new file mode 100644 index 000000000..2d24d9070 --- /dev/null +++ b/tests/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt @@ -0,0 +1 @@ +cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSCAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIIAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA diff --git a/tests/psbt/sighash/sighash-none-sign.psbt b/tests/psbt/sighash/sighash-none-sign.psbt new file mode 100644 index 000000000..e6acd46e5 --- /dev/null +++ b/tests/psbt/sighash/sighash-none-sign.psbt @@ -0,0 +1 @@ +cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwQCAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBAIAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA diff --git a/tests/psbt/sighash/sighash-single-3-ins-2-outs.psbt b/tests/psbt/sighash/sighash-single-3-ins-2-outs.psbt new file mode 100644 index 000000000..fd2fd561d --- /dev/null +++ b/tests/psbt/sighash/sighash-single-3-ins-2-outs.psbt @@ -0,0 +1 @@ +cHNidP8BAM8CAAAAAxb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///9zWd5vSCPj9qJ8WTucQPYsO8WJCiKhkJZDR4O4ZyXRtAAAAAAA/f///wLSM5cAAAAAACJRIAuOdIa8MGoK77enwArwQFVC2xrNc+7MqCdxzPX+XrYPVSYAAAAAAAAWABQTmbigmEegObA1L1jSEsGAurF/dwAAAAAAAQErjTSXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62DwEDBAMAAAAhFgKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KGQD1rML9VgAAgAEAAIAAAACAAQAAAAIAAAABFyACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCgABASsQJwAAAAAAACJRIEFGFImkPLZ+LGTzlCcnjt0wwD1tcqS4mYYUfEKPDMXxAQMEAwAAACEW6vdlNEO+xlQ326HaUvnKl+IxeFr1Zifo+cMRz3MA9t4ZAPWswv1WAACAAQAAgAAAAIAAAAAAAgAAAAEXIOr3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeAAEBK6CGAQAAAAAAIlEgaEmQnCLZJcKrtpXBQGIWvtL9aEdSTcZDdPa+sr8VmH8BAwQDAAAAIRZqOZFvW8uwbi8/4lpNmxK0CS/IGntnzSXeRuC6EKU2BxkA9azC/VYAAIABAACAAAAAgAAAAAAEAAAAARcgajmRb1vLsG4vP+JaTZsStAkvyBp7Z80l3kbguhClNgcAAQUgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwohBwKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KGQD1rML9VgAAgAEAAIAAAACAAQAAAAIAAAAAAA== diff --git a/tests/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt b/tests/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt new file mode 100644 index 000000000..6dc3e39da --- /dev/null +++ b/tests/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt @@ -0,0 +1 @@ +cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSDAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIMAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA diff --git a/tests/psbt/sighash/sighash-single-sign.psbt b/tests/psbt/sighash/sighash-single-sign.psbt new file mode 100644 index 000000000..bee912992 --- /dev/null +++ b/tests/psbt/sighash/sighash-single-sign.psbt @@ -0,0 +1 @@ +cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwQDAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBAMAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA diff --git a/tests/psbt/sighash/sighash-unsupported.psbt b/tests/psbt/sighash/sighash-unsupported.psbt new file mode 100644 index 000000000..debe29306 --- /dev/null +++ b/tests/psbt/sighash/sighash-unsupported.psbt @@ -0,0 +1 @@ +cHNidP8BAKYCAAAAAhb3mjFj7DfoPFTG4uj/bNiDjoNe863D39WWJ8lJ7CQJAAAAAAD9////+eIbh3yB4YYiVP0DlBwDkRIoVChMa7f7sR2KzcrqwLMAAAAAAP3///8C0jOXAAAAAAAiUSALjnSGvDBqCu+3p8AK8EBVQtsazXPuzKgnccz1/l62D1UmAAAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3cAAAAAAAEBK400lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg8BAwSEAAAAIRYCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAARcgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoAAQErECcAAAAAAAAiUSBBRhSJpDy2fixk85QnJ47dMMA9bXKkuJmGFHxCjwzF8QEDBIMAAAAhFur3ZTRDvsZUN9uh2lL5ypfiMXha9WYn6PnDEc9zAPbeGQD1rML9VgAAgAEAAIAAAACAAAAAAAIAAAABFyDq92U0Q77GVDfbodpS+cqX4jF4WvVmJ+j5wxHPcwD23gABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAA diff --git a/tests/psbt/singlesig/tr-1to2-sighash-all.psbt b/tests/psbt/singlesig/tr-1to2-sighash-all.psbt new file mode 100644 index 000000000..8cecafd24 --- /dev/null +++ b/tests/psbt/singlesig/tr-1to2-sighash-all.psbt @@ -0,0 +1 @@ +cHNidP8BAH0CAAAAAaG4I9IzbWlLSTTvm25bfeF6BVE9qKKdsCouy8eppv5tAQAAAAD9////AlX/pwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg+ghgEAAAAAABYAFBOZuKCYR6A5sDUvWNISwYC6sX93AAAAAAABASunhqkAAAAAACJRINj08dGJltthuxyvVCPeJdih7unJUNN+b/oCMBLV5i4NAQMEAQAAACEWIS6ihWpc8RDmaivp1zUxR5P9vLOIsrjxPytq3pguevUZAPWswv1WAACAAQAAgAAAAIABAAAAAAAAAAEXICEuooVqXPEQ5mor6dc1MUeT/byziLK48T8rat6YLnr1AAEFIAKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KIQcCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAAAA= diff --git a/tests/psbt/singlesig/tr-1to2-sighash-default.psbt b/tests/psbt/singlesig/tr-1to2-sighash-default.psbt new file mode 100644 index 000000000..c62b238f7 --- /dev/null +++ b/tests/psbt/singlesig/tr-1to2-sighash-default.psbt @@ -0,0 +1 @@ +cHNidP8BAH0CAAAAAeFoYcDSl0n1LNLt3hDLzE9ZEhBxD2QOXY4UQM6F2W3GAQAAAAD9////Ao00lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg+ghgEAAAAAABYAFBOZuKCYR6A5sDUvWNISwYC6sX93AAAAAAABASvfu5gAAAAAACJRIImQSmNI1/+aRNSduLaoB8Yi6Gg2TFR9pCbzC1piExhqAQMEAAAAACEW6cabCV7QS1Yq/I1BaRk2ulc+tvaJvCNmAL5Po5JvZBIZAPWswv1WAACAAQAAgAAAAIABAAAAAwAAAAEXIOnGmwle0EtWKvyNQWkZNrpXPrb2ibwjZgC+T6OSb2QSAAEFIAKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KIQcCkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfChkA9azC/VYAAIABAACAAAAAgAEAAAACAAAAAAA= diff --git a/tests/psbt/singlesig/tr-1to2-sighash-omitted.psbt b/tests/psbt/singlesig/tr-1to2-sighash-omitted.psbt new file mode 100644 index 000000000..046f88967 --- /dev/null +++ b/tests/psbt/singlesig/tr-1to2-sighash-omitted.psbt @@ -0,0 +1 @@ +cHNidP8BAH0CAAAAAeFoYcDSl0n1LNLt3hDLzE9ZEhBxD2QOXY4UQM6F2W3GAQAAAAD9////Ao00lwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg+ghgEAAAAAABYAFBOZuKCYR6A5sDUvWNISwYC6sX93AAAAAAABASvfu5gAAAAAACJRIImQSmNI1/+aRNSduLaoB8Yi6Gg2TFR9pCbzC1piExhqIRbpxpsJXtBLVir8jUFpGTa6Vz629om8I2YAvk+jkm9kEhkA9azC/VYAAIABAACAAAAAgAEAAAADAAAAARcg6cabCV7QS1Yq/I1BaRk2ulc+tvaJvCNmAL5Po5JvZBIAAQUgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwohBwKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KGQD1rML9VgAAgAEAAIAAAACAAQAAAAIAAAAAAA== diff --git a/tests/psbt/singlesig/tr-1to2.psbt b/tests/psbt/singlesig/tr-1to2.psbt deleted file mode 100644 index 5ae30e35d..000000000 --- a/tests/psbt/singlesig/tr-1to2.psbt +++ /dev/null @@ -1 +0,0 @@ -cHNidP8BAH0CAAAAAaG4I9IzbWlLSTTvm25bfeF6BVE9qKKdsCouy8eppv5tAQAAAAD9////AlX/pwAAAAAAIlEgC450hrwwagrvt6fACvBAVULbGs1z7syoJ3HM9f5etg+ghgEAAAAAABYAFBOZuKCYR6A5sDUvWNISwYC6sX93AAAAAAABASunhqkAAAAAACJRINj08dGJltthuxyvVCPeJdih7unJUNN+b/oCMBLV5i4NIRYhLqKFalzxEOZqK+nXNTFHk/28s4iyuPE/K2remC569RkA9azC/VYAAIABAACAAAAAgAEAAAAAAAAAARcgIS6ihWpc8RDmaivp1zUxR5P9vLOIsrjxPytq3pguevUAAQUgApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwohBwKQgezlYWqhy5kxn8SHTv7kfwjOul9gBGNsjENAml8KGQD1rML9VgAAgAEAAIAAAACAAQAAAAIAAAAAAA== diff --git a/tests/requirements.txt b/tests/requirements.txt index bf3db550a..d8554a27a 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,7 +1,9 @@ pytest>=6.1.1,<7.0.0 +pytest-timeout>=2.1.0,<3.0.0 ledgercomm>=1.1.0,<1.2.0 ecdsa>=0.16.1,<0.17.0 typing-extensions>=3.7,<4.0 -embit>=0.4.10,<0.5.0 +embit>=0.7.0,<0.8.0 mnemonic==0.20 -bip32>=2.1,<4.0 \ No newline at end of file +bip32>=3.4,<4.0 +ragger[speculos, ledgerwallet]>=1.6.0 \ No newline at end of file diff --git a/tests/setup_script.sh b/tests/setup_script.sh new file mode 100755 index 000000000..12278dae2 --- /dev/null +++ b/tests/setup_script.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +sudo apt update -y && sudo apt install -y curl +curl -o /tmp/bitcoin.tar.gz https://bitcoincore.org/bin/bitcoin-core-26.0/bitcoin-26.0-x86_64-linux-gnu.tar.gz && \ + sudo tar -xf /tmp/bitcoin.tar.gz -C / && \ + sudo mv /bitcoin-26.0 /bitcoin + +# Add bitcoin binaries to path +export PATH=/bitcoin/bin:$PATH diff --git a/tests/snapshots/nanos/test_dashboard/00000.png b/tests/snapshots/nanos/test_dashboard/00000.png new file mode 100644 index 000000000..38d9414f7 Binary files /dev/null and b/tests/snapshots/nanos/test_dashboard/00000.png differ diff --git a/tests/snapshots/nanos/test_dashboard/00001.png b/tests/snapshots/nanos/test_dashboard/00001.png new file mode 100644 index 000000000..7abb49fee Binary files /dev/null and b/tests/snapshots/nanos/test_dashboard/00001.png differ diff --git a/tests/snapshots/nanos/test_dashboard/00002.png b/tests/snapshots/nanos/test_dashboard/00002.png new file mode 100644 index 000000000..3476b972a Binary files /dev/null and b/tests/snapshots/nanos/test_dashboard/00002.png differ diff --git a/tests/snapshots/nanos/test_dashboard/00003.png b/tests/snapshots/nanos/test_dashboard/00003.png new file mode 100644 index 000000000..e2279803e Binary files /dev/null and b/tests/snapshots/nanos/test_dashboard/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00000.png new file mode 100644 index 000000000..e61c4b75c Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00001.png new file mode 100644 index 000000000..62e8de085 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00002.png new file mode 100644 index 000000000..e36c073da Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00003.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00004.png new file mode 100644 index 000000000..2df7fd975 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00005.png new file mode 100644 index 000000000..d22ca87ba Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00006.png new file mode 100644 index 000000000..cdffcde45 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00007.png new file mode 100644 index 000000000..927c7c465 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00008.png new file mode 100644 index 000000000..791553636 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00009.png new file mode 100644 index 000000000..ee353e2af Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00010.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00010.png new file mode 100644 index 000000000..d5084a9de Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00010.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00011.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00011.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_0_0/00011.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00000.png new file mode 100644 index 000000000..e61c4b75c Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00001.png new file mode 100644 index 000000000..62e8de085 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00002.png new file mode 100644 index 000000000..df6ab5dfc Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00003.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00000.png new file mode 100644 index 000000000..fa09ac376 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00001.png new file mode 100644 index 000000000..a694b49ee Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00002.png new file mode 100644 index 000000000..509768f3f Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00003.png new file mode 100644 index 000000000..70a3e246e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00004.png new file mode 100644 index 000000000..503893b77 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00005.png new file mode 100644 index 000000000..65c8634a8 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00006.png new file mode 100644 index 000000000..1f385f203 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00007.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00008.png new file mode 100644 index 000000000..9c7e7049c Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_0_1/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png new file mode 100644 index 000000000..e61c4b75c Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png new file mode 100644 index 000000000..62e8de085 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png new file mode 100644 index 000000000..df6ab5dfc Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png new file mode 100644 index 000000000..5ba8eab69 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png new file mode 100644 index 000000000..bc4c11808 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png new file mode 100644 index 000000000..8dea2c22b Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png new file mode 100644 index 000000000..9238bfd04 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png new file mode 100644 index 000000000..3e66ba977 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png new file mode 100644 index 000000000..2b8c3f1cc Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00006.png new file mode 100644 index 000000000..3c5e67b23 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00007.png new file mode 100644 index 000000000..976616e57 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00008.png new file mode 100644 index 000000000..a01e71747 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00009.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png new file mode 100644 index 000000000..5ba8eab69 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png new file mode 100644 index 000000000..1d4f62e46 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png new file mode 100644 index 000000000..8dea2c22b Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png new file mode 100644 index 000000000..df33b1427 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png new file mode 100644 index 000000000..c401bb3cc Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png new file mode 100644 index 000000000..37a2e110a Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00006.png new file mode 100644 index 000000000..305afb2df Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00007.png new file mode 100644 index 000000000..0d25d922a Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00008.png new file mode 100644 index 000000000..738c7f0d4 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00009.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png new file mode 100644 index 000000000..5ba8eab69 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png new file mode 100644 index 000000000..764943b80 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png new file mode 100644 index 000000000..4633ed885 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png new file mode 100644 index 000000000..6a8c23973 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png new file mode 100644 index 000000000..cc7117303 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png new file mode 100644 index 000000000..66e224541 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00006.png new file mode 100644 index 000000000..90364c2bb Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00007.png new file mode 100644 index 000000000..79172927e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00008.png new file mode 100644 index 000000000..311f43a95 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00009.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png new file mode 100644 index 000000000..5ba8eab69 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png new file mode 100644 index 000000000..c0e9ce050 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png new file mode 100644 index 000000000..10ded984e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png new file mode 100644 index 000000000..8d86eb764 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png new file mode 100644 index 000000000..d68d18e3c Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png new file mode 100644 index 000000000..7cdea93c9 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00006.png new file mode 100644 index 000000000..afa613540 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00007.png new file mode 100644 index 000000000..718107f9a Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00008.png new file mode 100644 index 000000000..f00de43f7 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00009.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png new file mode 100644 index 000000000..5ba8eab69 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png new file mode 100644 index 000000000..a3f1303bd Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png new file mode 100644 index 000000000..d84ab2d8a Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png new file mode 100644 index 000000000..a81de684e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png new file mode 100644 index 000000000..0a862b64d Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png new file mode 100644 index 000000000..55816bb07 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00006.png new file mode 100644 index 000000000..f675b7fac Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00007.png new file mode 100644 index 000000000..a27c31c48 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00008.png new file mode 100644 index 000000000..9b9429563 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00009.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png new file mode 100644 index 000000000..5ba8eab69 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png new file mode 100644 index 000000000..a003218b9 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png new file mode 100644 index 000000000..7c54fdeb4 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png new file mode 100644 index 000000000..5437be487 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png new file mode 100644 index 000000000..a74c8e8d4 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png new file mode 100644 index 000000000..188d64046 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00006.png new file mode 100644 index 000000000..55996d682 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00007.png new file mode 100644 index 000000000..6b22e704b Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00008.png new file mode 100644 index 000000000..807bfe449 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00009.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png new file mode 100644 index 000000000..5ba8eab69 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png new file mode 100644 index 000000000..ccbe69dc0 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png new file mode 100644 index 000000000..2bf618ef1 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png new file mode 100644 index 000000000..c7fab7a46 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png new file mode 100644 index 000000000..2f25076ca Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png new file mode 100644 index 000000000..dac807df9 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00006.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00006.png new file mode 100644 index 000000000..9ada3a41e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00007.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00007.png new file mode 100644 index 000000000..9549ea566 Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00008.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00008.png new file mode 100644 index 000000000..bbd751dcb Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00009.png b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00009.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png new file mode 100644 index 000000000..06873a14f Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png new file mode 100644 index 000000000..8712e685f Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png new file mode 100644 index 000000000..fb25c9706 Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00004.png b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png new file mode 100644 index 000000000..931a29c14 Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png new file mode 100644 index 000000000..b57f67c87 Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00002.png b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00002.png new file mode 100644 index 000000000..2fa440b21 Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00003.png b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00003.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png new file mode 100644 index 000000000..5025a8420 Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png new file mode 100644 index 000000000..19b7b9146 Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00002.png b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00002.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png new file mode 100644 index 000000000..ec975a255 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png new file mode 100644 index 000000000..edc71f63b Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png new file mode 100644 index 000000000..a2871f219 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png new file mode 100644 index 000000000..0bff2d457 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png new file mode 100644 index 000000000..28fa8c93d Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png new file mode 100644 index 000000000..ec8e2d1e9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00004.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00004.png new file mode 100644 index 000000000..e44e0e42a Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00005.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00005.png new file mode 100644 index 000000000..137eae538 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00006.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00006.png new file mode 100644 index 000000000..26a4bb7e2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00007.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00007.png new file mode 100644 index 000000000..a0e7aab05 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00008.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png new file mode 100644 index 000000000..60493dda2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png new file mode 100644 index 000000000..c5beb4fd6 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png new file mode 100644 index 000000000..1c0ba6470 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00004.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png new file mode 100644 index 000000000..a2871f219 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png new file mode 100644 index 000000000..0bff2d457 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png new file mode 100644 index 000000000..28fa8c93d Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png new file mode 100644 index 000000000..ec8e2d1e9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00004.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00004.png new file mode 100644 index 000000000..e44e0e42a Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00005.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00005.png new file mode 100644 index 000000000..137eae538 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00006.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00006.png new file mode 100644 index 000000000..26a4bb7e2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00007.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00007.png new file mode 100644 index 000000000..a0e7aab05 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00008.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png new file mode 100644 index 000000000..5cf20064c Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png new file mode 100644 index 000000000..c5beb4fd6 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png new file mode 100644 index 000000000..eca1d8152 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00004.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png new file mode 100644 index 000000000..a2871f219 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png new file mode 100644 index 000000000..0bff2d457 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png new file mode 100644 index 000000000..28fa8c93d Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png new file mode 100644 index 000000000..ec8e2d1e9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00004.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00004.png new file mode 100644 index 000000000..e44e0e42a Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00005.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00005.png new file mode 100644 index 000000000..137eae538 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00006.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00006.png new file mode 100644 index 000000000..26a4bb7e2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00007.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00007.png new file mode 100644 index 000000000..a0e7aab05 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00008.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png new file mode 100644 index 000000000..f361cbe00 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png new file mode 100644 index 000000000..b9ebcc983 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png new file mode 100644 index 000000000..a2871f219 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png new file mode 100644 index 000000000..0bff2d457 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png new file mode 100644 index 000000000..28fa8c93d Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png new file mode 100644 index 000000000..ec8e2d1e9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00004.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00004.png new file mode 100644 index 000000000..e44e0e42a Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00005.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00005.png new file mode 100644 index 000000000..137eae538 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00006.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00006.png new file mode 100644 index 000000000..26a4bb7e2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00007.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00007.png new file mode 100644 index 000000000..a0e7aab05 Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00008.png b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00002.png new file mode 100644 index 000000000..14a8a2ffb Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00003.png new file mode 100644 index 000000000..7c127d28f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00000.png new file mode 100644 index 000000000..65857e619 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00001.png new file mode 100644 index 000000000..ba47815fc Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00002.png new file mode 100644 index 000000000..89dc24e44 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00003.png new file mode 100644 index 000000000..c38e1acf5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00004.png new file mode 100644 index 000000000..2406ea8d9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00005.png new file mode 100644 index 000000000..5d7126988 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00006.png new file mode 100644 index 000000000..fb2e06671 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00007.png new file mode 100644 index 000000000..ea5bf06ef Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00000.png new file mode 100644 index 000000000..698b65ccf Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00001.png new file mode 100644 index 000000000..27e17988c Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00002.png new file mode 100644 index 000000000..f43be1b9b Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00003.png new file mode 100644 index 000000000..e192870a6 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00004.png new file mode 100644 index 000000000..3d26cdfa9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00005.png new file mode 100644 index 000000000..c52b8c837 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00006.png new file mode 100644 index 000000000..4dea81f92 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00007.png new file mode 100644 index 000000000..c5a5ed09f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00002.png new file mode 100644 index 000000000..309ca3c24 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00003.png new file mode 100644 index 000000000..623fc5ef3 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00000.png new file mode 100644 index 000000000..65857e619 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00001.png new file mode 100644 index 000000000..ba47815fc Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00002.png new file mode 100644 index 000000000..89dc24e44 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00003.png new file mode 100644 index 000000000..c38e1acf5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00004.png new file mode 100644 index 000000000..2406ea8d9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00005.png new file mode 100644 index 000000000..5d7126988 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00006.png new file mode 100644 index 000000000..fb2e06671 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00007.png new file mode 100644 index 000000000..050c2d279 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00000.png new file mode 100644 index 000000000..698b65ccf Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00001.png new file mode 100644 index 000000000..27e17988c Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00002.png new file mode 100644 index 000000000..f43be1b9b Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00003.png new file mode 100644 index 000000000..e192870a6 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00004.png new file mode 100644 index 000000000..3d26cdfa9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00005.png new file mode 100644 index 000000000..c52b8c837 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00006.png new file mode 100644 index 000000000..4dea81f92 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00007.png new file mode 100644 index 000000000..bfe1f0c54 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_legacy_v1_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00002.png new file mode 100644 index 000000000..890692eb2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00003.png new file mode 100644 index 000000000..d99bf8b97 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00000.png new file mode 100644 index 000000000..726b3321f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00001.png new file mode 100644 index 000000000..53c08bb46 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00002.png new file mode 100644 index 000000000..1f2cc7127 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00003.png new file mode 100644 index 000000000..1ed1275ac Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00004.png new file mode 100644 index 000000000..4f40555b9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00005.png new file mode 100644 index 000000000..1c2a0bd43 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00006.png new file mode 100644 index 000000000..beeb4d3a6 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00007.png new file mode 100644 index 000000000..562d8f6bd Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00000.png new file mode 100644 index 000000000..f42feee35 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00001.png new file mode 100644 index 000000000..481c8bea3 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00002.png new file mode 100644 index 000000000..1afd99e33 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00003.png new file mode 100644 index 000000000..a9a4243c1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00004.png new file mode 100644 index 000000000..992f561f9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00005.png new file mode 100644 index 000000000..ed19982c7 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00006.png new file mode 100644 index 000000000..e564207a7 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00007.png new file mode 100644 index 000000000..bad67b0f7 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00002.png new file mode 100644 index 000000000..890692eb2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00003.png new file mode 100644 index 000000000..d0112b867 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00000.png new file mode 100644 index 000000000..726b3321f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00001.png new file mode 100644 index 000000000..53c08bb46 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00002.png new file mode 100644 index 000000000..1f2cc7127 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00003.png new file mode 100644 index 000000000..1ed1275ac Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00004.png new file mode 100644 index 000000000..4f40555b9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00005.png new file mode 100644 index 000000000..1c2a0bd43 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00006.png new file mode 100644 index 000000000..beeb4d3a6 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00007.png new file mode 100644 index 000000000..638dab853 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00000.png new file mode 100644 index 000000000..f42feee35 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00001.png new file mode 100644 index 000000000..481c8bea3 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00002.png new file mode 100644 index 000000000..1afd99e33 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00003.png new file mode 100644 index 000000000..a9a4243c1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00004.png new file mode 100644 index 000000000..992f561f9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00005.png new file mode 100644 index 000000000..ed19982c7 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00006.png new file mode 100644 index 000000000..e564207a7 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00007.png new file mode 100644 index 000000000..82c3afa0a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_sh_wit_v1_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00002.png new file mode 100644 index 000000000..037fe2f3a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00003.png new file mode 100644 index 000000000..237ea0c52 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00000.png new file mode 100644 index 000000000..60d2ec5b4 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00001.png new file mode 100644 index 000000000..af2268f0d Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00002.png new file mode 100644 index 000000000..625080cd5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00003.png new file mode 100644 index 000000000..7ad4e9e81 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00004.png new file mode 100644 index 000000000..afc3b3b90 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00005.png new file mode 100644 index 000000000..961297c4e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00006.png new file mode 100644 index 000000000..39ecfdf08 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00007.png new file mode 100644 index 000000000..d0274d3af Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00000.png new file mode 100644 index 000000000..f66be86c1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00001.png new file mode 100644 index 000000000..f4987bbc5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00002.png new file mode 100644 index 000000000..246693e07 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00003.png new file mode 100644 index 000000000..7e7997079 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00004.png new file mode 100644 index 000000000..712276031 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00005.png new file mode 100644 index 000000000..8136b498a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00006.png new file mode 100644 index 000000000..ecf30dacb Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00007.png new file mode 100644 index 000000000..d18c5c464 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00002.png new file mode 100644 index 000000000..037fe2f3a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00003.png new file mode 100644 index 000000000..0791dd23f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00000.png new file mode 100644 index 000000000..60d2ec5b4 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00001.png new file mode 100644 index 000000000..af2268f0d Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00002.png new file mode 100644 index 000000000..625080cd5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00003.png new file mode 100644 index 000000000..7ad4e9e81 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00004.png new file mode 100644 index 000000000..afc3b3b90 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00005.png new file mode 100644 index 000000000..961297c4e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00006.png new file mode 100644 index 000000000..39ecfdf08 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00007.png new file mode 100644 index 000000000..4c3affd93 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00000.png new file mode 100644 index 000000000..f66be86c1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00001.png new file mode 100644 index 000000000..f4987bbc5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00002.png new file mode 100644 index 000000000..246693e07 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00003.png new file mode 100644 index 000000000..7e7997079 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00004.png new file mode 100644 index 000000000..712276031 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00005.png new file mode 100644 index 000000000..8136b498a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00006.png new file mode 100644 index 000000000..ecf30dacb Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00007.png new file mode 100644 index 000000000..bbd7af6d4 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_accept_wit_v1_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00002.png new file mode 100644 index 000000000..037fe2f3a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00003.png new file mode 100644 index 000000000..237ea0c52 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_invalid_pubkey_version_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00002.png new file mode 100644 index 000000000..037fe2f3a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00003.png new file mode 100644 index 000000000..237ea0c52 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00005.png b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00005.png new file mode 100644 index 000000000..9c7e7049c Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00002.png new file mode 100644 index 000000000..037fe2f3a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00003.png new file mode 100644 index 000000000..0791dd23f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00005.png b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00005.png new file mode 100644 index 000000000..9c7e7049c Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_reject_header_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00001.png new file mode 100644 index 000000000..d400e7534 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00002.png new file mode 100644 index 000000000..eeffd7817 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00003.png new file mode 100644 index 000000000..cbe590e86 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00004.png new file mode 100644 index 000000000..0b4ab99a0 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00005.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00000.png new file mode 100644 index 000000000..60d2ec5b4 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00001.png new file mode 100644 index 000000000..af2268f0d Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00002.png new file mode 100644 index 000000000..625080cd5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00003.png new file mode 100644 index 000000000..7ad4e9e81 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00004.png new file mode 100644 index 000000000..afc3b3b90 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00005.png new file mode 100644 index 000000000..961297c4e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00006.png new file mode 100644 index 000000000..39ecfdf08 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00007.png new file mode 100644 index 000000000..d0274d3af Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00000.png new file mode 100644 index 000000000..f66be86c1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00001.png new file mode 100644 index 000000000..f4987bbc5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00002.png new file mode 100644 index 000000000..246693e07 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00003.png new file mode 100644 index 000000000..7e7997079 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00004.png new file mode 100644 index 000000000..712276031 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00005.png new file mode 100644 index 000000000..8136b498a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00006.png new file mode 100644 index 000000000..ecf30dacb Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00007.png new file mode 100644 index 000000000..d18c5c464 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_pk_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00001.png new file mode 100644 index 000000000..c54e61f58 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00002.png new file mode 100644 index 000000000..cae166d86 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00003.png new file mode 100644 index 000000000..b4723ce71 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00004.png new file mode 100644 index 000000000..11ed9b26a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00005.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00000.png new file mode 100644 index 000000000..d9e85f6c4 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00001.png new file mode 100644 index 000000000..2c7f9ef50 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00002.png new file mode 100644 index 000000000..e06e178e1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00003.png new file mode 100644 index 000000000..59dab84a9 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00004.png new file mode 100644 index 000000000..7781a966f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00005.png new file mode 100644 index 000000000..309d52fbc Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00006.png new file mode 100644 index 000000000..13f10caf0 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00007.png new file mode 100644 index 000000000..0c612c431 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00000.png new file mode 100644 index 000000000..394dde9dc Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00001.png new file mode 100644 index 000000000..13e011621 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00002.png new file mode 100644 index 000000000..dc8e51489 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00003.png new file mode 100644 index 000000000..8230ee65c Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00004.png new file mode 100644 index 000000000..4ad8620a0 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00005.png new file mode 100644 index 000000000..0f7218a2a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00006.png new file mode 100644 index 000000000..75589c5e0 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00007.png new file mode 100644 index 000000000..737b76a72 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00000.png new file mode 100644 index 000000000..0a0f3a70e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00001.png new file mode 100644 index 000000000..bb4af12ad Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00002.png new file mode 100644 index 000000000..c48bc11a0 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00003.png new file mode 100644 index 000000000..e3406449f Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00004.png new file mode 100644 index 000000000..319775c11 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00005.png new file mode 100644 index 000000000..2bceffb76 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00006.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00006.png new file mode 100644 index 000000000..7e77d300b Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00007.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00007.png new file mode 100644 index 000000000..b91f3ab69 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00008.png b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_script_sortedmulti_3_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00001.png new file mode 100644 index 000000000..189549f33 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00002.png new file mode 100644 index 000000000..b4f2c375c Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00003.png new file mode 100644 index 000000000..0b4ab99a0 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00004.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00000.png new file mode 100644 index 000000000..50d6c7a12 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00001.png new file mode 100644 index 000000000..dd7a1d950 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00002.png new file mode 100644 index 000000000..3b9491b85 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00003.png new file mode 100644 index 000000000..138741dfa Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00004.png new file mode 100644 index 000000000..155f5bbec Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00005.png new file mode 100644 index 000000000..aeb007bf1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00006.png new file mode 100644 index 000000000..5911c8620 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00007.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00000.png new file mode 100644 index 000000000..f66be86c1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00001.png new file mode 100644 index 000000000..f4987bbc5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00002.png new file mode 100644 index 000000000..246693e07 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00003.png new file mode 100644 index 000000000..7e7997079 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00004.png new file mode 100644 index 000000000..712276031 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00005.png new file mode 100644 index 000000000..8136b498a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00006.png new file mode 100644 index 000000000..ecf30dacb Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00007.png new file mode 100644 index 000000000..d18c5c464 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_tr_with_nums_keypath_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00000.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00000.png new file mode 100644 index 000000000..dddd4a222 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00001.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00001.png new file mode 100644 index 000000000..58f6577a2 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00002.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00002.png new file mode 100644 index 000000000..84bb094ce Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00003.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00003.png new file mode 100644 index 000000000..57523ce26 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00004.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00004.png new file mode 100644 index 000000000..2ab557f93 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00005.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00005.png new file mode 100644 index 000000000..037fe2f3a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00006.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00006.png new file mode 100644 index 000000000..237ea0c52 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00007.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00007.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00000.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00000.png new file mode 100644 index 000000000..60d2ec5b4 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00001.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00001.png new file mode 100644 index 000000000..af2268f0d Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00002.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00002.png new file mode 100644 index 000000000..625080cd5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00003.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00003.png new file mode 100644 index 000000000..7ad4e9e81 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00004.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00004.png new file mode 100644 index 000000000..afc3b3b90 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00005.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00005.png new file mode 100644 index 000000000..961297c4e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00006.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00006.png new file mode 100644 index 000000000..39ecfdf08 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00007.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00007.png new file mode 100644 index 000000000..d0274d3af Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00008.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_1_0/00008.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00000.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00000.png new file mode 100644 index 000000000..f66be86c1 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00001.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00001.png new file mode 100644 index 000000000..f4987bbc5 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00002.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00002.png new file mode 100644 index 000000000..246693e07 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00003.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00003.png new file mode 100644 index 000000000..7e7997079 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00004.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00004.png new file mode 100644 index 000000000..712276031 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00005.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00005.png new file mode 100644 index 000000000..8136b498a Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00006.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00006.png new file mode 100644 index 000000000..ecf30dacb Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00007.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00007.png new file mode 100644 index 000000000..d18c5c464 Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00008.png b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00008.png new file mode 100644 index 000000000..66c411c2e Binary files /dev/null and b/tests/snapshots/nanos/test_register_wallet_with_long_name_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00004.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00005.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00004.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00005.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..13ac88e55 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00002.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00002.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00003.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00004.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00005.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_1/00000.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_1/00001.png b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00000.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00001.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00002.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00003.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00004.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00005.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_1/00000.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_input_modified_0_1/00001.png b/tests/snapshots/nanos/test_sighash_all_input_modified_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_input_modified_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00000.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00001.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00002.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00003.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00004.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00005.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_1/00000.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_1/00000.png new file mode 100644 index 000000000..13ac88e55 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_output_modified_0_1/00001.png b/tests/snapshots/nanos/test_sighash_all_output_modified_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_output_modified_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00000.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00001.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00002.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00003.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00004.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00005.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_1/00000.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_1/00001.png b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_all_sign_psbt_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00004.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00005.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00004.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00005.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..13ac88e55 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00002.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00002.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00003.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00004.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00005.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_1/00000.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_1/00001.png b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00000.png b/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00001.png b/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00002.png b/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00000.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00001.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00002.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00003.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00004.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00005.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_1/00000.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_input_modified_1_1/00001.png b/tests/snapshots/nanos/test_sighash_none_input_modified_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_input_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00000.png b/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00001.png b/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00002.png b/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00000.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00001.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00002.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00003.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00004.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00005.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_1/00000.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_1/00000.png new file mode 100644 index 000000000..13ac88e55 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_output_modified_1_1/00001.png b/tests/snapshots/nanos/test_sighash_none_output_modified_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_output_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00000.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00001.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00002.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00000.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00001.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00002.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00003.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00004.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00005.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_1/00000.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_1/00001.png b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_none_sign_psbt_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00003.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00004.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_1_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_1_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_1_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00003.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00004.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_2_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_2_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_2_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_2_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash2_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00003.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00004.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_2_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_2_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_2_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_2_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash3_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00003.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00004.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_2_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_2_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_2_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_2_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash81_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00003.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00004.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_2_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_2_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_2_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_2_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash82_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00002.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00003.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00004.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_2_0/00000.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_2_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_2_0/00001.png b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_2_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_segwitv0_sighash83_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00004.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00005.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00004.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00005.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..13ac88e55 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00002.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00002.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00003.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00004.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00005.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_1/00000.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_1/00001.png b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00000.png b/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00001.png b/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00002.png b/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00000.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00001.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00002.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00003.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00004.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00005.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_1/00000.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_input_modified_1_1/00001.png b/tests/snapshots/nanos/test_sighash_single_input_modified_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_input_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00000.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00001.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00002.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00000.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00001.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00001.png new file mode 100644 index 000000000..f489e17fd Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00002.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00003.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00004.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00005.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_1/00000.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_1/00000.png new file mode 100644 index 000000000..13ac88e55 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_1/00001.png b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_different_index_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00000.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00001.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00002.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00000.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00001.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00002.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00003.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00004.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00005.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_1/00000.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_1/00000.png new file mode 100644 index 000000000..13ac88e55 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_1/00001.png b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_output_same_index_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00000.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..7372ec63f Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00001.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00002.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00000.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00001.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00002.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00003.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00004.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00005.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_1/00000.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_1/00001.png b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_single_sign_psbt_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_0/00000.png b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_0/00001.png b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_0/00002.png b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_0/00003.png b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_0/00004.png b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_0/00005.png b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_1/00000.png b/tests/snapshots/nanos/test_sighash_unsupported_0_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_0_1/00001.png b/tests/snapshots/nanos/test_sighash_unsupported_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00000.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00001.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00001.png new file mode 100644 index 000000000..604eaf435 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00002.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00003.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00004.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00005.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_1/00000.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_1/00000.png new file mode 100644 index 000000000..63bc77614 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_1/00001.png b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sighash_unsupported_for_segwitv0_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_0/00000.png b/tests/snapshots/nanos/test_sign_message_0_0/00000.png new file mode 100644 index 000000000..ab16f62bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_0/00001.png b/tests/snapshots/nanos/test_sign_message_0_0/00001.png new file mode 100644 index 000000000..83c9daf24 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_1/00000.png b/tests/snapshots/nanos/test_sign_message_0_1/00000.png new file mode 100644 index 000000000..7e2c5bffe Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_1/00001.png b/tests/snapshots/nanos/test_sign_message_0_1/00001.png new file mode 100644 index 000000000..dc3e2e0f3 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_1/00002.png b/tests/snapshots/nanos/test_sign_message_0_1/00002.png new file mode 100644 index 000000000..0bf552335 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_1/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_1/00003.png b/tests/snapshots/nanos/test_sign_message_0_1/00003.png new file mode 100644 index 000000000..f1f1a3987 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_1/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_1/00004.png b/tests/snapshots/nanos/test_sign_message_0_1/00004.png new file mode 100644 index 000000000..691ea69a8 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_1/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_0_1/00005.png b/tests/snapshots/nanos/test_sign_message_0_1/00005.png new file mode 100644 index 000000000..8fbe2b11e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_0_1/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_0_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_0_0/00000.png new file mode 100644 index 000000000..ab16f62bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_0_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_0_0/00001.png new file mode 100644 index 000000000..aece7c669 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_0_1/00000.png b/tests/snapshots/nanos/test_sign_message_accept_0_1/00000.png new file mode 100644 index 000000000..6401348ff Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_0_1/00001.png b/tests/snapshots/nanos/test_sign_message_accept_0_1/00001.png new file mode 100644 index 000000000..8fbe2b11e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_0/00000.png new file mode 100644 index 000000000..ab16f62bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_0/00001.png new file mode 100644 index 000000000..5f012ff72 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00000.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00000.png new file mode 100644 index 000000000..8b8e1fe61 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00001.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00001.png new file mode 100644 index 000000000..7747353fd Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00002.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00002.png new file mode 100644 index 000000000..d0c71fa08 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00003.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00003.png new file mode 100644 index 000000000..42200e3bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00004.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00004.png new file mode 100644 index 000000000..d56b02c1a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00005.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00005.png new file mode 100644 index 000000000..3b854bfd1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00006.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00006.png new file mode 100644 index 000000000..d1739b5ce Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00007.png b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00007.png new file mode 100644 index 000000000..6af664846 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_0_1/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00000.png new file mode 100644 index 000000000..17da9d4de Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00001.png new file mode 100644 index 000000000..e96b73e1f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00002.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00002.png new file mode 100644 index 000000000..0cb9a76cd Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00003.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00003.png new file mode 100644 index 000000000..8eb39720f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00004.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00004.png new file mode 100644 index 000000000..01a1758c6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00005.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00005.png new file mode 100644 index 000000000..90014bef5 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00006.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00006.png new file mode 100644 index 000000000..59c33ecf6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00007.png b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00007.png new file mode 100644 index 000000000..6af664846 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_1_0/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00000.png new file mode 100644 index 000000000..77257342e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00001.png new file mode 100644 index 000000000..0785da854 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00002.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00002.png new file mode 100644 index 000000000..b4fd10388 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00003.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00003.png new file mode 100644 index 000000000..973db7dfd Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00004.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00004.png new file mode 100644 index 000000000..130341173 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00005.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00005.png new file mode 100644 index 000000000..a380871a7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00006.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00006.png new file mode 100644 index 000000000..01aefa0ef Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00007.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00007.png new file mode 100644 index 000000000..027e89d6b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00008.png b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00008.png new file mode 100644 index 000000000..6af664846 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_2_0/00008.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00000.png new file mode 100644 index 000000000..c8f8944f3 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00001.png new file mode 100644 index 000000000..176dda3b8 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00002.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00002.png new file mode 100644 index 000000000..8beb0eea7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00003.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00003.png new file mode 100644 index 000000000..3815cb3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00004.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00004.png new file mode 100644 index 000000000..112fb9241 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00005.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00005.png new file mode 100644 index 000000000..7c4773181 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00006.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00006.png new file mode 100644 index 000000000..4ad1e08a6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00007.png b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00007.png new file mode 100644 index 000000000..6af664846 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_3_0/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00000.png new file mode 100644 index 000000000..bfba69511 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00001.png new file mode 100644 index 000000000..754d07d9a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00002.png b/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00002.png new file mode 100644 index 000000000..8fbe2b11e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_long_4_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_0/00000.png new file mode 100644 index 000000000..ab16f62bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_0/00001.png new file mode 100644 index 000000000..5f012ff72 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00000.png b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00000.png new file mode 100644 index 000000000..8a3a1a569 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00001.png b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00001.png new file mode 100644 index 000000000..1595214cb Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00002.png b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00002.png new file mode 100644 index 000000000..e49d18f2b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00003.png b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00003.png new file mode 100644 index 000000000..ab1518e49 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00004.png b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00004.png new file mode 100644 index 000000000..f5c2d6774 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_non_ascii_0_1/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_too_long_0_0/00000.png b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_0/00000.png new file mode 100644 index 000000000..ab16f62bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_too_long_0_0/00001.png b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_0/00001.png new file mode 100644 index 000000000..5f012ff72 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00000.png b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00000.png new file mode 100644 index 000000000..86240866c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00001.png b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00001.png new file mode 100644 index 000000000..82788317c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00002.png b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00002.png new file mode 100644 index 000000000..50a9e4087 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00003.png b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00003.png new file mode 100644 index 000000000..a8062d395 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00004.png b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00004.png new file mode 100644 index 000000000..f5c2d6774 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_accept_too_long_0_1/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00000.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00000.png new file mode 100644 index 000000000..ab16f62bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00001.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00001.png new file mode 100644 index 000000000..83c9daf24 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00002.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00002.png new file mode 100644 index 000000000..8a3a1a569 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00003.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00003.png new file mode 100644 index 000000000..1595214cb Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00004.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00004.png new file mode 100644 index 000000000..e49d18f2b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00005.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00005.png new file mode 100644 index 000000000..ab1518e49 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00006.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00006.png new file mode 100644 index 000000000..f5c2d6774 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00007.png b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00007.png new file mode 100644 index 000000000..9c7e7049c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_hash_reject_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_message_reject_0_0/00000.png b/tests/snapshots/nanos/test_sign_message_reject_0_0/00000.png new file mode 100644 index 000000000..ab16f62bf Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_message_reject_0_0/00001.png b/tests/snapshots/nanos/test_sign_message_reject_0_0/00001.png new file mode 100644 index 000000000..83c9daf24 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_message_reject_0_0/00002.png b/tests/snapshots/nanos/test_sign_message_reject_0_0/00002.png new file mode 100644 index 000000000..e1920f70a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_message_reject_0_0/00003.png b/tests/snapshots/nanos/test_sign_message_reject_0_0/00003.png new file mode 100644 index 000000000..8fbe2b11e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_message_reject_0_0/00004.png b/tests/snapshots/nanos/test_sign_message_reject_0_0/00004.png new file mode 100644 index 000000000..9c7e7049c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_message_reject_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png new file mode 100644 index 000000000..4f6ae5978 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png new file mode 100644 index 000000000..1b5bb48f8 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png new file mode 100644 index 000000000..01287b1d6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00003.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png new file mode 100644 index 000000000..8c353ac45 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png new file mode 100644 index 000000000..ec6a7529b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png new file mode 100644 index 000000000..2aa6c98d1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00004.png new file mode 100644 index 000000000..a1748d3d3 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png new file mode 100644 index 000000000..00cb376b9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00001.png new file mode 100644 index 000000000..25ff62f2e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00002.png new file mode 100644 index 000000000..67cc27f5c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00003.png new file mode 100644 index 000000000..54a169e0b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00004.png new file mode 100644 index 000000000..34ac2789d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_highfee_1_0/00000.png new file mode 100644 index 000000000..2001b7bea Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_highfee_1_0/00001.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_highfee_1_1/00000.png new file mode 100644 index 000000000..368c431be Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_highfee_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_highfee_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_highfee_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00000.png new file mode 100644 index 000000000..4f6ae5978 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00001.png new file mode 100644 index 000000000..f33bb930d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00002.png new file mode 100644 index 000000000..8c87abb51 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00003.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00001.png new file mode 100644 index 000000000..8c353ac45 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00002.png new file mode 100644 index 000000000..ad73a90a6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00003.png new file mode 100644 index 000000000..0d8b94741 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00004.png new file mode 100644 index 000000000..8e0035da9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_1/00000.png new file mode 100644 index 000000000..1c4947d5a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_miniscript_multikey_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00000.png new file mode 100644 index 000000000..4f6ae5978 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00001.png new file mode 100644 index 000000000..3e2e69253 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00002.png new file mode 100644 index 000000000..4113ba06c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00003.png new file mode 100644 index 000000000..f73f8967a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_1/00000.png new file mode 100644 index 000000000..150191ed9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..4f6ae5978 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..f43aea1c0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png new file mode 100644 index 000000000..daf511fa4 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png new file mode 100644 index 000000000..d8e9b0063 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png new file mode 100644 index 000000000..3e2e69253 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png new file mode 100644 index 000000000..4113ba06c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png new file mode 100644 index 000000000..f73f8967a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png new file mode 100644 index 000000000..150191ed9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00000.png new file mode 100644 index 000000000..4f6ae5978 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00001.png new file mode 100644 index 000000000..1bce76e72 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00002.png new file mode 100644 index 000000000..67cc27f5c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00003.png new file mode 100644 index 000000000..54a169e0b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00004.png new file mode 100644 index 000000000..34ac2789d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_2_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_2_0/00000.png new file mode 100644 index 000000000..3a5ef5f56 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_2_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_2_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00000.png new file mode 100644 index 000000000..4f6ae5978 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00001.png new file mode 100644 index 000000000..fcf54452e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00002.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00001.png new file mode 100644 index 000000000..1bce76e72 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00002.png new file mode 100644 index 000000000..67cc27f5c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00003.png new file mode 100644 index 000000000..54a169e0b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00004.png new file mode 100644 index 000000000..34ac2789d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_2_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_2_0/00000.png new file mode 100644 index 000000000..3a5ef5f56 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_2_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_2_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_multisig_wsh_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00001.png new file mode 100644 index 000000000..15460bf25 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00002.png new file mode 100644 index 000000000..62c5c93b1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00003.png new file mode 100644 index 000000000..265c5506d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00004.png new file mode 100644 index 000000000..593b9d039 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_1/00000.png new file mode 100644 index 000000000..b744c60a2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png new file mode 100644 index 000000000..15460bf25 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png new file mode 100644 index 000000000..62c5c93b1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png new file mode 100644 index 000000000..265c5506d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00004.png new file mode 100644 index 000000000..593b9d039 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png new file mode 100644 index 000000000..b744c60a2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png new file mode 100644 index 000000000..826adf1f2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png new file mode 100644 index 000000000..2a3a9cfe7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png new file mode 100644 index 000000000..c86e1f50f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png new file mode 100644 index 000000000..274a5c125 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png new file mode 100644 index 000000000..826adf1f2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png new file mode 100644 index 000000000..2a3a9cfe7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png new file mode 100644 index 000000000..c86e1f50f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png new file mode 100644 index 000000000..274a5c125 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png new file mode 100644 index 000000000..826adf1f2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png new file mode 100644 index 000000000..2a3a9cfe7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png new file mode 100644 index 000000000..c86e1f50f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png new file mode 100644 index 000000000..274a5c125 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png new file mode 100644 index 000000000..826adf1f2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png new file mode 100644 index 000000000..2a3a9cfe7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png new file mode 100644 index 000000000..c86e1f50f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png new file mode 100644 index 000000000..274a5c125 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..e2db0cf30 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png new file mode 100644 index 000000000..67cc27f5c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png new file mode 100644 index 000000000..54a169e0b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00004.png new file mode 100644 index 000000000..34ac2789d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..12073c912 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..e2db0cf30 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..67cc27f5c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..54a169e0b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00004.png new file mode 100644 index 000000000..34ac2789d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..12073c912 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..9333a4225 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png new file mode 100644 index 000000000..6c697ed85 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png new file mode 100644 index 000000000..cf1220727 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png new file mode 100644 index 000000000..a0201ce2a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png new file mode 100644 index 000000000..f41b1fcfa Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00005.png new file mode 100644 index 000000000..c6bf2b204 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00006.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png new file mode 100644 index 000000000..62cb4623c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..f43aea1c0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png new file mode 100644 index 000000000..daf511fa4 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png new file mode 100644 index 000000000..d8e9b0063 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png new file mode 100644 index 000000000..6c697ed85 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png new file mode 100644 index 000000000..cf1220727 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png new file mode 100644 index 000000000..a0201ce2a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png new file mode 100644 index 000000000..f41b1fcfa Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00005.png new file mode 100644 index 000000000..c6bf2b204 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00006.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png new file mode 100644 index 000000000..62cb4623c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png new file mode 100644 index 000000000..6c697ed85 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png new file mode 100644 index 000000000..cf1220727 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png new file mode 100644 index 000000000..a0201ce2a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png new file mode 100644 index 000000000..f41b1fcfa Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00005.png new file mode 100644 index 000000000..c6bf2b204 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00006.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png new file mode 100644 index 000000000..62cb4623c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png new file mode 100644 index 000000000..7f75368c7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png new file mode 100644 index 000000000..62c5c93b1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png new file mode 100644 index 000000000..265c5506d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00004.png new file mode 100644 index 000000000..593b9d039 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png new file mode 100644 index 000000000..a63982497 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png new file mode 100644 index 000000000..03d00da6a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png new file mode 100644 index 000000000..cad37cf5e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png new file mode 100644 index 000000000..3e11559bc Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00004.png new file mode 100644 index 000000000..062e5e811 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png new file mode 100644 index 000000000..57a28b5d5 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png new file mode 100644 index 000000000..7f75368c7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png new file mode 100644 index 000000000..62c5c93b1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png new file mode 100644 index 000000000..265c5506d Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00004.png new file mode 100644 index 000000000..593b9d039 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png new file mode 100644 index 000000000..a63982497 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png new file mode 100644 index 000000000..03d00da6a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png new file mode 100644 index 000000000..cad37cf5e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png new file mode 100644 index 000000000..3e11559bc Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00004.png new file mode 100644 index 000000000..062e5e811 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png new file mode 100644 index 000000000..57a28b5d5 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png new file mode 100644 index 000000000..6e53386f7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png new file mode 100644 index 000000000..e1d899316 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png new file mode 100644 index 000000000..811aacbc9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png new file mode 100644 index 000000000..5a885f5e6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png new file mode 100644 index 000000000..811aacbc9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png new file mode 100644 index 000000000..5a885f5e6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png new file mode 100644 index 000000000..811aacbc9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png new file mode 100644 index 000000000..5a885f5e6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..811aacbc9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_1/00000.png new file mode 100644 index 000000000..5a885f5e6 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_taproot_1to2_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png new file mode 100644 index 000000000..4f6ae5978 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png new file mode 100644 index 000000000..d400e7534 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png new file mode 100644 index 000000000..eeffd7817 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00003.png new file mode 100644 index 000000000..cbe590e86 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png new file mode 100644 index 000000000..e5f851114 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png new file mode 100644 index 000000000..bc54d295c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png new file mode 100644 index 000000000..53339b3c5 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00004.png new file mode 100644 index 000000000..7389b566f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png new file mode 100644 index 000000000..c8ee3a900 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00000.png new file mode 100644 index 000000000..10c895733 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00002.png new file mode 100644 index 000000000..9eded7d0f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00001.png new file mode 100644 index 000000000..811aacbc9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00000.png new file mode 100644 index 000000000..a63982497 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00001.png new file mode 100644 index 000000000..50d7a3258 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00002.png new file mode 100644 index 000000000..3c12ed1d3 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00003.png new file mode 100644 index 000000000..27301e1e1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00004.png new file mode 100644 index 000000000..5cfcac846 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00005.png new file mode 100644 index 000000000..7fbd5c205 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00006.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00000.png new file mode 100644 index 000000000..92b7bdd49 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00001.png new file mode 100644 index 000000000..826adf1f2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00002.png new file mode 100644 index 000000000..2a3a9cfe7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00003.png new file mode 100644 index 000000000..c86e1f50f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_3_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00000.png new file mode 100644 index 000000000..4c7fa789f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00001.png new file mode 100644 index 000000000..246d839d4 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00002.png new file mode 100644 index 000000000..cea6f75b4 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00003.png new file mode 100644 index 000000000..f2f8b41d8 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00004.png new file mode 100644 index 000000000..a6374e723 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_4_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00000.png new file mode 100644 index 000000000..9fe8a7eba Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_1/00000.png new file mode 100644 index 000000000..142b6bd02 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_0_5_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00000.png new file mode 100644 index 000000000..10c895733 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00002.png new file mode 100644 index 000000000..9eded7d0f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00001.png new file mode 100644 index 000000000..811aacbc9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00000.png new file mode 100644 index 000000000..a63982497 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00001.png new file mode 100644 index 000000000..826adf1f2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00002.png new file mode 100644 index 000000000..2a3a9cfe7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00003.png new file mode 100644 index 000000000..c86e1f50f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00000.png new file mode 100644 index 000000000..92b7bdd49 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00001.png new file mode 100644 index 000000000..246d839d4 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00002.png new file mode 100644 index 000000000..cea6f75b4 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00003.png new file mode 100644 index 000000000..f2f8b41d8 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00004.png new file mode 100644 index 000000000..a6374e723 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_3_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00000.png new file mode 100644 index 000000000..4c7fa789f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_1/00000.png new file mode 100644 index 000000000..142b6bd02 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_1_4_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00000.png new file mode 100644 index 000000000..10c895733 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00001.png new file mode 100644 index 000000000..81fc3c1f9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00002.png new file mode 100644 index 000000000..9eded7d0f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00001.png new file mode 100644 index 000000000..811aacbc9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00002.png new file mode 100644 index 000000000..748ff7f09 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00003.png new file mode 100644 index 000000000..9124a230b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00004.png new file mode 100644 index 000000000..542cb49a9 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00005.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_1_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00000.png new file mode 100644 index 000000000..a63982497 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00001.png new file mode 100644 index 000000000..50d7a3258 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00002.png new file mode 100644 index 000000000..3c12ed1d3 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00003.png new file mode 100644 index 000000000..27301e1e1 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00004.png new file mode 100644 index 000000000..5cfcac846 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00005.png new file mode 100644 index 000000000..7fbd5c205 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00006.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_2_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00000.png new file mode 100644 index 000000000..92b7bdd49 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00001.png new file mode 100644 index 000000000..826adf1f2 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00002.png new file mode 100644 index 000000000..2a3a9cfe7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00003.png new file mode 100644 index 000000000..c86e1f50f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_3_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00000.png new file mode 100644 index 000000000..4c7fa789f Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00001.png new file mode 100644 index 000000000..92924defc Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00002.png new file mode 100644 index 000000000..50ddea3ab Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00003.png new file mode 100644 index 000000000..9f3ae167e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00004.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_1/00000.png new file mode 100644 index 000000000..142b6bd02 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_external_inputs_2_4_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00001.png new file mode 100644 index 000000000..3870d6e7a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00002.png new file mode 100644 index 000000000..a04ab94bd Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00003.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_1_0/00000.png new file mode 100644 index 000000000..d959f7e4a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_naked_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00001.png new file mode 100644 index 000000000..3870d6e7a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00002.png new file mode 100644 index 000000000..6e3ee423a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00003.png new file mode 100644 index 000000000..6ab726a5e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00004.png new file mode 100644 index 000000000..f4a306a83 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00005.png new file mode 100644 index 000000000..1cf427053 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00006.png new file mode 100644 index 000000000..923349e68 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00007.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00007.png new file mode 100644 index 000000000..eb10394eb Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00008.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00008.png new file mode 100644 index 000000000..8018a1a00 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00009.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00009.png new file mode 100644 index 000000000..d9e33c514 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00010.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00010.png new file mode 100644 index 000000000..8b4c6dd02 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00010.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00011.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00011.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_0_0/00011.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_1_0/00000.png new file mode 100644 index 000000000..d959f7e4a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00001.png new file mode 100644 index 000000000..3870d6e7a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00002.png new file mode 100644 index 000000000..6e3ee423a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00003.png new file mode 100644 index 000000000..6ab726a5e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00004.png new file mode 100644 index 000000000..f4a306a83 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00005.png new file mode 100644 index 000000000..1cf427053 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00006.png new file mode 100644 index 000000000..923349e68 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00007.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00007.png new file mode 100644 index 000000000..eb10394eb Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00008.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00008.png new file mode 100644 index 000000000..8018a1a00 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00008.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00009.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00009.png new file mode 100644 index 000000000..d9e33c514 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00009.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00010.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00010.png new file mode 100644 index 000000000..8b4c6dd02 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00010.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00011.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00011.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_0_0/00011.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_1_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_1_0/00000.png new file mode 100644 index 000000000..d959f7e4a Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_1_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_1_0/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_opreturn_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00001.png new file mode 100644 index 000000000..8c353ac45 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00002.png new file mode 100644 index 000000000..430c04c9e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00003.png new file mode 100644 index 000000000..1477899ae Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00004.png new file mode 100644 index 000000000..e707ed3a5 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00005.png new file mode 100644 index 000000000..6effa69fa Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00006.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_1/00000.png new file mode 100644 index 000000000..c947508d7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_0_1/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png new file mode 100644 index 000000000..cd4545e54 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png new file mode 100644 index 000000000..8c353ac45 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png new file mode 100644 index 000000000..430c04c9e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png new file mode 100644 index 000000000..1477899ae Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png new file mode 100644 index 000000000..e707ed3a5 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00005.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00005.png new file mode 100644 index 000000000..6effa69fa Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00006.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00006.png new file mode 100644 index 000000000..12b06a78e Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_0/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png new file mode 100644 index 000000000..c947508d7 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png new file mode 100644 index 000000000..9ccdcd1f0 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_dashboard/00000.png b/tests/snapshots/nanosp/test_dashboard/00000.png new file mode 100644 index 000000000..d73deb1de Binary files /dev/null and b/tests/snapshots/nanosp/test_dashboard/00000.png differ diff --git a/tests/snapshots/nanosp/test_dashboard/00001.png b/tests/snapshots/nanosp/test_dashboard/00001.png new file mode 100644 index 000000000..afefa4f5d Binary files /dev/null and b/tests/snapshots/nanosp/test_dashboard/00001.png differ diff --git a/tests/snapshots/nanosp/test_dashboard/00002.png b/tests/snapshots/nanosp/test_dashboard/00002.png new file mode 100644 index 000000000..7e1a28c65 Binary files /dev/null and b/tests/snapshots/nanosp/test_dashboard/00002.png differ diff --git a/tests/snapshots/nanosp/test_dashboard/00003.png b/tests/snapshots/nanosp/test_dashboard/00003.png new file mode 100644 index 000000000..bcb20c683 Binary files /dev/null and b/tests/snapshots/nanosp/test_dashboard/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00000.png new file mode 100644 index 000000000..8583e39ef Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00001.png new file mode 100644 index 000000000..fb5c9c673 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00002.png new file mode 100644 index 000000000..3f9a1347f Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00003.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00004.png new file mode 100644 index 000000000..e16fbcdd9 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00005.png new file mode 100644 index 000000000..45d9d59ac Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00006.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00006.png new file mode 100644 index 000000000..80dbc0c24 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00006.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00007.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00007.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_0_0/00007.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00000.png new file mode 100644 index 000000000..8583e39ef Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00001.png new file mode 100644 index 000000000..fb5c9c673 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00002.png new file mode 100644 index 000000000..690fd0268 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00003.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00000.png new file mode 100644 index 000000000..82b819028 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00001.png new file mode 100644 index 000000000..d5267289a Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00002.png new file mode 100644 index 000000000..258f26fa8 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_0_1/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png new file mode 100644 index 000000000..8583e39ef Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png new file mode 100644 index 000000000..fb5c9c673 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png new file mode 100644 index 000000000..690fd0268 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png new file mode 100644 index 000000000..8b32f1dfd Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png new file mode 100644 index 000000000..e7191ca07 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png new file mode 100644 index 000000000..da12b9ef3 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png new file mode 100644 index 000000000..cac752667 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png new file mode 100644 index 000000000..d3ffef10d Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png new file mode 100644 index 000000000..49973feea Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png new file mode 100644 index 000000000..c06925f25 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png new file mode 100644 index 000000000..44531783a Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png new file mode 100644 index 000000000..4830a91db Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png new file mode 100644 index 000000000..1e331c7b1 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png new file mode 100644 index 000000000..5a41bfd00 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png new file mode 100644 index 000000000..f497f8005 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png new file mode 100644 index 000000000..4fe72c3ac Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png new file mode 100644 index 000000000..f10cf514b Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png new file mode 100644 index 000000000..88aec018b Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png new file mode 100644 index 000000000..82e479d22 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png new file mode 100644 index 000000000..6b083fcee Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png new file mode 100644 index 000000000..d237bbc41 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png new file mode 100644 index 000000000..ed56331c4 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png new file mode 100644 index 000000000..dd175d24f Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png new file mode 100644 index 000000000..fbc75a1c4 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png new file mode 100644 index 000000000..4dd921265 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png new file mode 100644 index 000000000..5d1f60c03 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png new file mode 100644 index 000000000..1676c3c5d Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png new file mode 100644 index 000000000..6879d2565 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png new file mode 100644 index 000000000..8f1f1533c Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png new file mode 100644 index 000000000..61c426415 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png new file mode 100644 index 000000000..05fe79b70 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png new file mode 100644 index 000000000..0fa6de2ec Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png new file mode 100644 index 000000000..877ce1d16 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png new file mode 100644 index 000000000..db51fe6fb Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png new file mode 100644 index 000000000..d27090254 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00000.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00001.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00001.png new file mode 100644 index 000000000..34b19f06a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00002.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00002.png new file mode 100644 index 000000000..ebc70b370 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00003.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00003.png new file mode 100644 index 000000000..1c32a0ec6 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00004.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00004.png new file mode 100644 index 000000000..4d6b41690 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00005.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00005.png new file mode 100644 index 000000000..621f3009e Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00006.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00006.png new file mode 100644 index 000000000..abb5d00c9 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00006.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00007.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00007.png new file mode 100644 index 000000000..36f9c90a0 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00007.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00008.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00008.png new file mode 100644 index 000000000..c34650f12 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00008.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00009.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00009.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_0_0/00009.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00000.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00000.png new file mode 100644 index 000000000..e7a9ab02a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00001.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00001.png new file mode 100644 index 000000000..e7cd9faf5 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00002.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00002.png new file mode 100644 index 000000000..e82fc9df1 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00003.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00000.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00000.png new file mode 100644 index 000000000..75ab7fd53 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00001.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00001.png new file mode 100644 index 000000000..29c873c3d Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00002.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00002.png new file mode 100644 index 000000000..0e6e6938a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00003.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00000.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00000.png new file mode 100644 index 000000000..873e3ecdb Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00001.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00001.png new file mode 100644 index 000000000..1ee8b13fb Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00002.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00002.png new file mode 100644 index 000000000..f4ab6a928 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00003.png b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_miniscript_long_policy_3_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png new file mode 100644 index 000000000..ff7deb115 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png new file mode 100644 index 000000000..0e6597e6b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png new file mode 100644 index 000000000..01ef6720c Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png new file mode 100644 index 000000000..2ca9ffae4 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png new file mode 100644 index 000000000..967e6cb03 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png new file mode 100644 index 000000000..472e5a38a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png new file mode 100644 index 000000000..bdedda85d Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png new file mode 100644 index 000000000..f0ce5be13 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00002.png new file mode 100644 index 000000000..f9e9870f1 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00000.png new file mode 100644 index 000000000..31fa5daac Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00001.png new file mode 100644 index 000000000..c9b7dc727 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00002.png new file mode 100644 index 000000000..037a81b22 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00000.png new file mode 100644 index 000000000..aab07893b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00001.png new file mode 100644 index 000000000..f9f7eb594 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00002.png new file mode 100644 index 000000000..4d184932c Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00002.png new file mode 100644 index 000000000..524252318 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00000.png new file mode 100644 index 000000000..31fa5daac Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00001.png new file mode 100644 index 000000000..c9b7dc727 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00002.png new file mode 100644 index 000000000..dfbf1104d Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00000.png new file mode 100644 index 000000000..aab07893b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00001.png new file mode 100644 index 000000000..f9f7eb594 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00002.png new file mode 100644 index 000000000..878bfd46a Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_legacy_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00002.png new file mode 100644 index 000000000..e2cc15b33 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00000.png new file mode 100644 index 000000000..1b89108a0 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00001.png new file mode 100644 index 000000000..ef7366d45 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00002.png new file mode 100644 index 000000000..267b42fbe Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00000.png new file mode 100644 index 000000000..0c08224ff Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00001.png new file mode 100644 index 000000000..91a9f0afa Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00002.png new file mode 100644 index 000000000..783378ba4 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00002.png new file mode 100644 index 000000000..e1a0c8938 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00000.png new file mode 100644 index 000000000..1b89108a0 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00001.png new file mode 100644 index 000000000..ef7366d45 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00002.png new file mode 100644 index 000000000..2d9753768 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00000.png new file mode 100644 index 000000000..0c08224ff Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00001.png new file mode 100644 index 000000000..91a9f0afa Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00002.png new file mode 100644 index 000000000..1ae7061fd Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_sh_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00002.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00002.png new file mode 100644 index 000000000..d283f94fd Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00002.png new file mode 100644 index 000000000..1507a4c67 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00002.png new file mode 100644 index 000000000..96d456c54 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00002.png new file mode 100644 index 000000000..d3ea50bde Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_accept_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00002.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_invalid_pubkey_version_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00002.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00004.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00002.png new file mode 100644 index 000000000..1507a4c67 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00004.png b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_reject_header_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00001.png new file mode 100644 index 000000000..a0a62f0ab Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00002.png new file mode 100644 index 000000000..c4d79c7ec Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00002.png new file mode 100644 index 000000000..d283f94fd Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_pk_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00001.png new file mode 100644 index 000000000..4cdfa3ad2 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00002.png new file mode 100644 index 000000000..071d74207 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00000.png new file mode 100644 index 000000000..8799e48cc Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00001.png new file mode 100644 index 000000000..0352ca6d0 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00002.png new file mode 100644 index 000000000..d705c90af Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00000.png new file mode 100644 index 000000000..bd5a55fcb Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00001.png new file mode 100644 index 000000000..d5b4078d1 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00002.png new file mode 100644 index 000000000..b5be705cd Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00000.png new file mode 100644 index 000000000..fde65799f Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00001.png new file mode 100644 index 000000000..3e2e8b443 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00002.png new file mode 100644 index 000000000..5d5f21de7 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_script_sortedmulti_3_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00001.png new file mode 100644 index 000000000..b70468c08 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00002.png new file mode 100644 index 000000000..c4d79c7ec Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00000.png new file mode 100644 index 000000000..2cd5813c4 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00001.png new file mode 100644 index 000000000..e11f5332e Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00002.png new file mode 100644 index 000000000..6af0cbdd0 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_tr_with_nums_keypath_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00001.png new file mode 100644 index 000000000..7edc7de31 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00002.png new file mode 100644 index 000000000..748c3b048 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00003.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00004.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00004.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00002.png new file mode 100644 index 000000000..d283f94fd Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00000.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00001.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00002.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00003.png b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanosp/test_register_wallet_with_long_name_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00003.png b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_input_modified_0_1/00000.png b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_input_modified_0_1/00001.png b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_input_modified_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00003.png b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_output_modified_0_1/00000.png b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_output_modified_0_1/00001.png b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_output_modified_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00003.png b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_1/00000.png b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_1/00001.png b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_all_sign_psbt_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_input_modified_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_input_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_output_modified_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_output_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_none_sign_psbt_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00003.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_1_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_2_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_2_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash2_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_2_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_2_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash3_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_2_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_2_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash81_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_2_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_2_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash82_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_2_0/00000.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_2_0/00001.png b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_segwitv0_sighash83_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_input_modified_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_input_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00001.png new file mode 100644 index 000000000..c9bd49ec6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_different_index_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_output_same_index_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00000.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00001.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00002.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00003.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_1/00000.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_1/00001.png b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_single_sign_psbt_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00003.png b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_0_1/00000.png b/tests/snapshots/nanosp/test_sighash_unsupported_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_0_1/00001.png b/tests/snapshots/nanosp/test_sighash_unsupported_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00000.png b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00001.png b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00002.png b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00003.png b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_1/00000.png b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_1/00001.png b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sighash_unsupported_for_segwitv0_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_0_0/00000.png b/tests/snapshots/nanosp/test_sign_message_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_0_0/00001.png b/tests/snapshots/nanosp/test_sign_message_0_0/00001.png new file mode 100644 index 000000000..71c80df0e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_0_1/00000.png b/tests/snapshots/nanosp/test_sign_message_0_1/00000.png new file mode 100644 index 000000000..04bb6f43c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_0_1/00001.png b/tests/snapshots/nanosp/test_sign_message_0_1/00001.png new file mode 100644 index 000000000..098d70fca Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_0_1/00002.png b/tests/snapshots/nanosp/test_sign_message_0_1/00002.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_0_1/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_0_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_0_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_0_0/00001.png new file mode 100644 index 000000000..3ecf97960 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_0_1/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_0_1/00000.png new file mode 100644 index 000000000..8cb0af7a9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_0_1/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_0_1/00001.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_0_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_long_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_0_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_long_0_0/00001.png new file mode 100644 index 000000000..7d7359362 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00000.png new file mode 100644 index 000000000..8a9399404 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00001.png new file mode 100644 index 000000000..075903f58 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00002.png b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00002.png new file mode 100644 index 000000000..703d0beb5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00003.png b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_0_1/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00000.png new file mode 100644 index 000000000..b1e001b7b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00001.png new file mode 100644 index 000000000..905fbc9db Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00002.png b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00002.png new file mode 100644 index 000000000..0b161d12d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00003.png b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00000.png new file mode 100644 index 000000000..f1c7143a1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00001.png new file mode 100644 index 000000000..23f59cb33 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00002.png b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00002.png new file mode 100644 index 000000000..03fafd9da Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00003.png b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00000.png new file mode 100644 index 000000000..9403307a5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00001.png new file mode 100644 index 000000000..3da5a411a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00002.png b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00002.png new file mode 100644 index 000000000..f5db5b49b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00003.png b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_3_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_4_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_long_4_0/00000.png new file mode 100644 index 000000000..80943953e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_4_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_long_4_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_long_4_0/00001.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_long_4_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_0/00001.png new file mode 100644 index 000000000..7d7359362 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00000.png new file mode 100644 index 000000000..263a4391e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00001.png new file mode 100644 index 000000000..a8cf42f7d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00002.png b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00002.png new file mode 100644 index 000000000..e7ffedd90 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_non_ascii_0_1/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_0/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_0/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_0/00001.png new file mode 100644 index 000000000..7d7359362 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00000.png b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00000.png new file mode 100644 index 000000000..17edb9d30 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00001.png b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00001.png new file mode 100644 index 000000000..28456ca06 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00002.png b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00002.png new file mode 100644 index 000000000..e7ffedd90 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_accept_too_long_0_1/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00000.png b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00001.png b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00001.png new file mode 100644 index 000000000..71c80df0e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00002.png b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00002.png new file mode 100644 index 000000000..263a4391e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00003.png b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00003.png new file mode 100644 index 000000000..a8cf42f7d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00004.png b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00004.png new file mode 100644 index 000000000..e7ffedd90 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00005.png b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00005.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_hash_reject_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_reject_0_0/00000.png b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_reject_0_0/00001.png b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00001.png new file mode 100644 index 000000000..71c80df0e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_reject_0_0/00002.png b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00002.png new file mode 100644 index 000000000..d20a083f5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_reject_0_0/00003.png b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00003.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_reject_0_0/00004.png b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_reject_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png new file mode 100644 index 000000000..66ec5c34a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png new file mode 100644 index 000000000..317b8b9ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png new file mode 100644 index 000000000..1562d40df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00001.png new file mode 100644 index 000000000..00a5104dc Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_0/00000.png new file mode 100644 index 000000000..3fa33760f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_0/00001.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_1/00000.png new file mode 100644 index 000000000..7a139ba8e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_highfee_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_highfee_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00001.png new file mode 100644 index 000000000..6f55ea25d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00002.png new file mode 100644 index 000000000..f67a3aa62 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_1/00000.png new file mode 100644 index 000000000..184d002d4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_miniscript_multikey_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00001.png new file mode 100644 index 000000000..84d33c06e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00002.png new file mode 100644 index 000000000..fdc5b5dc5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_1/00000.png new file mode 100644 index 000000000..78a269ed2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..e0409140b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png new file mode 100644 index 000000000..3c91cc8f1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png new file mode 100644 index 000000000..c6b820aa9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png new file mode 100644 index 000000000..84d33c06e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png new file mode 100644 index 000000000..fdc5b5dc5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png new file mode 100644 index 000000000..78a269ed2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00001.png new file mode 100644 index 000000000..c94c15959 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_2_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_2_0/00000.png new file mode 100644 index 000000000..73bcd7be8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_2_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00001.png new file mode 100644 index 000000000..c94c15959 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_2_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_2_0/00000.png new file mode 100644 index 000000000..73bcd7be8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_2_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_multisig_wsh_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00001.png new file mode 100644 index 000000000..75480f35c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_1/00000.png new file mode 100644 index 000000000..905cdba55 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png new file mode 100644 index 000000000..75480f35c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png new file mode 100644 index 000000000..905cdba55 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..760425304 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..f71b3a662 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..760425304 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..f71b3a662 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png new file mode 100644 index 000000000..700453f77 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png new file mode 100644 index 000000000..6c57d4dd5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png new file mode 100644 index 000000000..562b8c3a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png new file mode 100644 index 000000000..7a3d318ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..e0409140b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png new file mode 100644 index 000000000..3c91cc8f1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png new file mode 100644 index 000000000..c6b820aa9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png new file mode 100644 index 000000000..700453f77 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png new file mode 100644 index 000000000..6c57d4dd5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png new file mode 100644 index 000000000..562b8c3a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png new file mode 100644 index 000000000..7a3d318ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png new file mode 100644 index 000000000..700453f77 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png new file mode 100644 index 000000000..6c57d4dd5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png new file mode 100644 index 000000000..562b8c3a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png new file mode 100644 index 000000000..7a3d318ff Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png new file mode 100644 index 000000000..9e01669d1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png new file mode 100644 index 000000000..c2b649401 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png new file mode 100644 index 000000000..037d61c4d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png new file mode 100644 index 000000000..25f4bd2ba Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png new file mode 100644 index 000000000..9e01669d1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png new file mode 100644 index 000000000..c2b649401 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png new file mode 100644 index 000000000..037d61c4d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png new file mode 100644 index 000000000..25f4bd2ba Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png new file mode 100644 index 000000000..822b63031 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png new file mode 100644 index 000000000..0bcbec3dd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_taproot_1to2_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png new file mode 100644 index 000000000..a0a62f0ab Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png new file mode 100644 index 000000000..90fa38fae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png new file mode 100644 index 000000000..22ac6174c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png new file mode 100644 index 000000000..6af6144d6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00000.png new file mode 100644 index 000000000..3016710d2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00002.png new file mode 100644 index 000000000..58dbded53 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00001.png new file mode 100644 index 000000000..55624c5d6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00002.png new file mode 100644 index 000000000..8c57f8c13 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00003.png new file mode 100644 index 000000000..0da504c40 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_2_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00000.png new file mode 100644 index 000000000..311e4672e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_3_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00000.png new file mode 100644 index 000000000..2ed1dca44 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00001.png new file mode 100644 index 000000000..e9fb0b0ab Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00002.png new file mode 100644 index 000000000..dd90178a3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_4_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00000.png new file mode 100644 index 000000000..e1fa952d6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_1/00000.png new file mode 100644 index 000000000..223063c8a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_0_5_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00000.png new file mode 100644 index 000000000..3016710d2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00002.png new file mode 100644 index 000000000..58dbded53 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00000.png new file mode 100644 index 000000000..311e4672e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00001.png new file mode 100644 index 000000000..e9fb0b0ab Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00002.png new file mode 100644 index 000000000..dd90178a3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_3_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00000.png new file mode 100644 index 000000000..2ed1dca44 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_1/00000.png new file mode 100644 index 000000000..223063c8a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_1_4_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00000.png new file mode 100644 index 000000000..3016710d2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00002.png new file mode 100644 index 000000000..58dbded53 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_1_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00001.png new file mode 100644 index 000000000..55624c5d6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00002.png new file mode 100644 index 000000000..8c57f8c13 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00003.png new file mode 100644 index 000000000..0da504c40 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_2_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00000.png new file mode 100644 index 000000000..311e4672e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_3_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00000.png new file mode 100644 index 000000000..2ed1dca44 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_1/00000.png new file mode 100644 index 000000000..223063c8a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_external_inputs_2_4_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00001.png new file mode 100644 index 000000000..4c3776135 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00002.png new file mode 100644 index 000000000..8b83bf203 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_1_0/00000.png new file mode 100644 index 000000000..80f6eb6a4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_naked_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00001.png new file mode 100644 index 000000000..4c3776135 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00002.png new file mode 100644 index 000000000..85912cd4e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00003.png new file mode 100644 index 000000000..339d2a8ee Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00004.png new file mode 100644 index 000000000..9770af436 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00005.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00005.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_1_0/00000.png new file mode 100644 index 000000000..80f6eb6a4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00001.png new file mode 100644 index 000000000..4c3776135 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00002.png new file mode 100644 index 000000000..85912cd4e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00003.png new file mode 100644 index 000000000..339d2a8ee Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00004.png new file mode 100644 index 000000000..9770af436 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00005.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00005.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_1_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_1_0/00000.png new file mode 100644 index 000000000..80f6eb6a4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_1_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_opreturn_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00002.png new file mode 100644 index 000000000..687e6d5cd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00003.png new file mode 100644 index 000000000..72bc053e6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_1/00000.png new file mode 100644 index 000000000..cc1210f5c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_0_1/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png new file mode 100644 index 000000000..687e6d5cd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png new file mode 100644 index 000000000..72bc053e6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png new file mode 100644 index 000000000..cc1210f5c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_dashboard/00000.png b/tests/snapshots/nanox/test_dashboard/00000.png new file mode 100644 index 000000000..d73deb1de Binary files /dev/null and b/tests/snapshots/nanox/test_dashboard/00000.png differ diff --git a/tests/snapshots/nanox/test_dashboard/00001.png b/tests/snapshots/nanox/test_dashboard/00001.png new file mode 100644 index 000000000..afefa4f5d Binary files /dev/null and b/tests/snapshots/nanox/test_dashboard/00001.png differ diff --git a/tests/snapshots/nanox/test_dashboard/00002.png b/tests/snapshots/nanox/test_dashboard/00002.png new file mode 100644 index 000000000..7e1a28c65 Binary files /dev/null and b/tests/snapshots/nanox/test_dashboard/00002.png differ diff --git a/tests/snapshots/nanox/test_dashboard/00003.png b/tests/snapshots/nanox/test_dashboard/00003.png new file mode 100644 index 000000000..bcb20c683 Binary files /dev/null and b/tests/snapshots/nanox/test_dashboard/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00000.png new file mode 100644 index 000000000..8583e39ef Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00001.png new file mode 100644 index 000000000..fb5c9c673 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00002.png new file mode 100644 index 000000000..3f9a1347f Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00003.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00004.png new file mode 100644 index 000000000..e16fbcdd9 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00005.png new file mode 100644 index 000000000..45d9d59ac Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00006.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00006.png new file mode 100644 index 000000000..80dbc0c24 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00006.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00007.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00007.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_0_0/00007.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00000.png new file mode 100644 index 000000000..8583e39ef Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00001.png new file mode 100644 index 000000000..fb5c9c673 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00002.png new file mode 100644 index 000000000..690fd0268 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00003.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00000.png new file mode 100644 index 000000000..82b819028 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00001.png new file mode 100644 index 000000000..d5267289a Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00002.png new file mode 100644 index 000000000..258f26fa8 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_0_1/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png new file mode 100644 index 000000000..8583e39ef Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png new file mode 100644 index 000000000..fb5c9c673 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png new file mode 100644 index 000000000..690fd0268 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png new file mode 100644 index 000000000..8b32f1dfd Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png new file mode 100644 index 000000000..e7191ca07 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png new file mode 100644 index 000000000..da12b9ef3 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png new file mode 100644 index 000000000..cac752667 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png new file mode 100644 index 000000000..d3ffef10d Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png new file mode 100644 index 000000000..49973feea Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png new file mode 100644 index 000000000..c06925f25 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png new file mode 100644 index 000000000..44531783a Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png new file mode 100644 index 000000000..4830a91db Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png new file mode 100644 index 000000000..1e331c7b1 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png new file mode 100644 index 000000000..5a41bfd00 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png new file mode 100644 index 000000000..f497f8005 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png new file mode 100644 index 000000000..4fe72c3ac Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png new file mode 100644 index 000000000..f10cf514b Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png new file mode 100644 index 000000000..88aec018b Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png new file mode 100644 index 000000000..82e479d22 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png new file mode 100644 index 000000000..6b083fcee Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png new file mode 100644 index 000000000..d237bbc41 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png new file mode 100644 index 000000000..ed56331c4 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png new file mode 100644 index 000000000..dd175d24f Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png new file mode 100644 index 000000000..fbc75a1c4 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png new file mode 100644 index 000000000..4dd921265 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png new file mode 100644 index 000000000..5d1f60c03 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png new file mode 100644 index 000000000..1676c3c5d Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png new file mode 100644 index 000000000..f6ee4a895 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png new file mode 100644 index 000000000..6879d2565 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png new file mode 100644 index 000000000..8f1f1533c Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png new file mode 100644 index 000000000..61c426415 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png new file mode 100644 index 000000000..05fe79b70 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png new file mode 100644 index 000000000..0fa6de2ec Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png new file mode 100644 index 000000000..877ce1d16 Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png new file mode 100644 index 000000000..db51fe6fb Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png new file mode 100644 index 000000000..d27090254 Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00000.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00001.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00001.png new file mode 100644 index 000000000..34b19f06a Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00002.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00002.png new file mode 100644 index 000000000..ebc70b370 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00003.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00003.png new file mode 100644 index 000000000..1c32a0ec6 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00004.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00004.png new file mode 100644 index 000000000..4d6b41690 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00005.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00005.png new file mode 100644 index 000000000..621f3009e Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00006.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00006.png new file mode 100644 index 000000000..abb5d00c9 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00006.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00007.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00007.png new file mode 100644 index 000000000..36f9c90a0 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00007.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00008.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00008.png new file mode 100644 index 000000000..c34650f12 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00008.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00009.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00009.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_0_0/00009.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00000.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00000.png new file mode 100644 index 000000000..e7a9ab02a Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00001.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00001.png new file mode 100644 index 000000000..e7cd9faf5 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00002.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00002.png new file mode 100644 index 000000000..e82fc9df1 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00003.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00000.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00000.png new file mode 100644 index 000000000..75ab7fd53 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00001.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00001.png new file mode 100644 index 000000000..29c873c3d Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00002.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00002.png new file mode 100644 index 000000000..0e6e6938a Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00003.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00000.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00000.png new file mode 100644 index 000000000..873e3ecdb Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00001.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00001.png new file mode 100644 index 000000000..1ee8b13fb Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00002.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00002.png new file mode 100644 index 000000000..f4ab6a928 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00003.png b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_miniscript_long_policy_3_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png new file mode 100644 index 000000000..ff7deb115 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png new file mode 100644 index 000000000..0e6597e6b Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png new file mode 100644 index 000000000..01ef6720c Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png new file mode 100644 index 000000000..2ca9ffae4 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png new file mode 100644 index 000000000..967e6cb03 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png new file mode 100644 index 000000000..472e5a38a Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png new file mode 100644 index 000000000..bdedda85d Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png new file mode 100644 index 000000000..f0ce5be13 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png new file mode 100644 index 000000000..cd47be1a7 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png new file mode 100644 index 000000000..e7162e36a Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png new file mode 100644 index 000000000..ebac5e511 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00002.png new file mode 100644 index 000000000..f9e9870f1 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00000.png new file mode 100644 index 000000000..31fa5daac Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00001.png new file mode 100644 index 000000000..c9b7dc727 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00002.png new file mode 100644 index 000000000..037a81b22 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00000.png new file mode 100644 index 000000000..aab07893b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00001.png new file mode 100644 index 000000000..f9f7eb594 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00002.png new file mode 100644 index 000000000..4d184932c Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00002.png new file mode 100644 index 000000000..524252318 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00000.png new file mode 100644 index 000000000..31fa5daac Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00001.png new file mode 100644 index 000000000..c9b7dc727 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00002.png new file mode 100644 index 000000000..dfbf1104d Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00000.png new file mode 100644 index 000000000..aab07893b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00001.png new file mode 100644 index 000000000..f9f7eb594 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00002.png new file mode 100644 index 000000000..878bfd46a Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_legacy_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00002.png new file mode 100644 index 000000000..e2cc15b33 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00000.png new file mode 100644 index 000000000..1b89108a0 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00001.png new file mode 100644 index 000000000..ef7366d45 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00002.png new file mode 100644 index 000000000..267b42fbe Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00000.png new file mode 100644 index 000000000..0c08224ff Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00001.png new file mode 100644 index 000000000..91a9f0afa Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00002.png new file mode 100644 index 000000000..783378ba4 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00002.png new file mode 100644 index 000000000..e1a0c8938 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00000.png new file mode 100644 index 000000000..1b89108a0 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00001.png new file mode 100644 index 000000000..ef7366d45 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00002.png new file mode 100644 index 000000000..2d9753768 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00000.png new file mode 100644 index 000000000..0c08224ff Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00001.png new file mode 100644 index 000000000..91a9f0afa Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00002.png new file mode 100644 index 000000000..1ae7061fd Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_sh_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00002.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00002.png new file mode 100644 index 000000000..d283f94fd Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00002.png new file mode 100644 index 000000000..1507a4c67 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00002.png new file mode 100644 index 000000000..96d456c54 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00002.png new file mode 100644 index 000000000..d3ea50bde Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_accept_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00002.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_invalid_pubkey_version_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00002.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00004.png b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00002.png new file mode 100644 index 000000000..1507a4c67 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00004.png b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_reject_header_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00001.png new file mode 100644 index 000000000..a0a62f0ab Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00002.png new file mode 100644 index 000000000..c4d79c7ec Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00002.png new file mode 100644 index 000000000..d283f94fd Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_pk_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00001.png new file mode 100644 index 000000000..4cdfa3ad2 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00002.png new file mode 100644 index 000000000..071d74207 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00000.png new file mode 100644 index 000000000..8799e48cc Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00001.png new file mode 100644 index 000000000..0352ca6d0 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00002.png new file mode 100644 index 000000000..d705c90af Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00000.png new file mode 100644 index 000000000..bd5a55fcb Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00001.png new file mode 100644 index 000000000..d5b4078d1 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00002.png new file mode 100644 index 000000000..b5be705cd Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00000.png new file mode 100644 index 000000000..fde65799f Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00001.png new file mode 100644 index 000000000..3e2e8b443 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00002.png new file mode 100644 index 000000000..5d5f21de7 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_script_sortedmulti_3_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00001.png new file mode 100644 index 000000000..b70468c08 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00002.png new file mode 100644 index 000000000..c4d79c7ec Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00000.png new file mode 100644 index 000000000..2cd5813c4 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00001.png new file mode 100644 index 000000000..e11f5332e Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00002.png new file mode 100644 index 000000000..6af0cbdd0 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_tr_with_nums_keypath_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00000.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00000.png new file mode 100644 index 000000000..a9be96a1b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00001.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00001.png new file mode 100644 index 000000000..7edc7de31 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00002.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00002.png new file mode 100644 index 000000000..748c3b048 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00003.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00003.png new file mode 100644 index 000000000..b18a7f9da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00004.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00004.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00000.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00000.png new file mode 100644 index 000000000..6c7223c00 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00001.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00001.png new file mode 100644 index 000000000..ef611217b Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00002.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00002.png new file mode 100644 index 000000000..d283f94fd Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00003.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00000.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00000.png new file mode 100644 index 000000000..3641bc0da Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00001.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00001.png new file mode 100644 index 000000000..795449674 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00002.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00002.png new file mode 100644 index 000000000..9e49cd05d Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00003.png b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00003.png new file mode 100644 index 000000000..53ae65195 Binary files /dev/null and b/tests/snapshots/nanox/test_register_wallet_with_long_name_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00002.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00002.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00003.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_1/00000.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_1/00001.png b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00000.png b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00001.png b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00002.png b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00003.png b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_input_modified_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_input_modified_0_1/00000.png b/tests/snapshots/nanox/test_sighash_all_input_modified_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_input_modified_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_input_modified_0_1/00001.png b/tests/snapshots/nanox/test_sighash_all_input_modified_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_input_modified_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00000.png b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00001.png b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00002.png b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00003.png b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_output_modified_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_output_modified_0_1/00000.png b/tests/snapshots/nanox/test_sighash_all_output_modified_0_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_output_modified_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_output_modified_0_1/00001.png b/tests/snapshots/nanox/test_sighash_all_output_modified_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_output_modified_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00000.png b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00001.png b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00002.png b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00003.png b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_1/00000.png b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_1/00001.png b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_all_sign_psbt_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00002.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00002.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00003.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_1/00000.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_1/00001.png b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00000.png b/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00001.png b/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00002.png b/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00000.png b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00001.png b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00002.png b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00003.png b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_1_1/00000.png b/tests/snapshots/nanox/test_sighash_none_input_modified_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_input_modified_1_1/00001.png b/tests/snapshots/nanox/test_sighash_none_input_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_input_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00000.png b/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00001.png b/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00002.png b/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00000.png b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00001.png b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00002.png b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00003.png b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_1_1/00000.png b/tests/snapshots/nanox/test_sighash_none_output_modified_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_output_modified_1_1/00001.png b/tests/snapshots/nanox/test_sighash_none_output_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_output_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00000.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00001.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00002.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00000.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00001.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00002.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00003.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_1/00000.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_1/00001.png b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_none_sign_psbt_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00003.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_1_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_1_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_1_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00003.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_2_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_2_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash2_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00003.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_2_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_2_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash3_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00003.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_2_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_2_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash81_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00003.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_2_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_2_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash82_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00002.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00003.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_2_0/00000.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_2_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_2_0/00001.png b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_segwitv0_sighash83_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00002.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00002.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00003.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_1/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_1/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_input_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00002.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00002.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00003.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_1/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_1/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_output_changed_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00002.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00002.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00003.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_1/00000.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_1/00001.png b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_anyone_sign_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00000.png b/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00001.png b/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00002.png b/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00000.png b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00001.png b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00002.png b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00003.png b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_1_1/00000.png b/tests/snapshots/nanox/test_sighash_single_input_modified_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_input_modified_1_1/00001.png b/tests/snapshots/nanox/test_sighash_single_input_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_input_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00000.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00001.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00002.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00000.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00001.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00001.png new file mode 100644 index 000000000..c9bd49ec6 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00002.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00003.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_1/00000.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_1/00001.png b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_different_index_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00000.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00001.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00002.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00000.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00001.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00002.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00003.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_1/00000.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_1/00000.png new file mode 100644 index 000000000..4033d25ff Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_1/00001.png b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_output_same_index_modified_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00000.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..43b3c1533 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00001.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00002.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00000.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00001.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00002.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00003.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_1/00000.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_1/00001.png b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_single_sign_psbt_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_0_0/00000.png b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_0_0/00001.png b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_0_0/00002.png b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_0_0/00003.png b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_0_1/00000.png b/tests/snapshots/nanox/test_sighash_unsupported_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_0_1/00001.png b/tests/snapshots/nanox/test_sighash_unsupported_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00000.png b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00001.png b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00001.png new file mode 100644 index 000000000..5f31f04cb Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00002.png b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00003.png b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_1/00000.png b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_1/00000.png new file mode 100644 index 000000000..937df53d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_1/00001.png b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sighash_unsupported_for_segwitv0_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_0_0/00000.png b/tests/snapshots/nanox/test_sign_message_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_0_0/00001.png b/tests/snapshots/nanox/test_sign_message_0_0/00001.png new file mode 100644 index 000000000..71c80df0e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_0_1/00000.png b/tests/snapshots/nanox/test_sign_message_0_1/00000.png new file mode 100644 index 000000000..04bb6f43c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_0_1/00001.png b/tests/snapshots/nanox/test_sign_message_0_1/00001.png new file mode 100644 index 000000000..098d70fca Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_0_1/00002.png b/tests/snapshots/nanox/test_sign_message_0_1/00002.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_0_1/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_0_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_0_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_0_0/00001.png new file mode 100644 index 000000000..3ecf97960 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_0_1/00000.png b/tests/snapshots/nanox/test_sign_message_accept_0_1/00000.png new file mode 100644 index 000000000..8cb0af7a9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_0_1/00001.png b/tests/snapshots/nanox/test_sign_message_accept_0_1/00001.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_0_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_long_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_0_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_long_0_0/00001.png new file mode 100644 index 000000000..7d7359362 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00000.png b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00000.png new file mode 100644 index 000000000..8a9399404 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00001.png b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00001.png new file mode 100644 index 000000000..075903f58 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00002.png b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00002.png new file mode 100644 index 000000000..703d0beb5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00003.png b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_0_1/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00000.png new file mode 100644 index 000000000..b1e001b7b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00001.png new file mode 100644 index 000000000..905fbc9db Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00002.png b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00002.png new file mode 100644 index 000000000..0b161d12d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00003.png b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00000.png new file mode 100644 index 000000000..f1c7143a1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00001.png new file mode 100644 index 000000000..23f59cb33 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00002.png b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00002.png new file mode 100644 index 000000000..03fafd9da Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00003.png b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00000.png new file mode 100644 index 000000000..9403307a5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00001.png new file mode 100644 index 000000000..3da5a411a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00002.png b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00002.png new file mode 100644 index 000000000..f5db5b49b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00003.png b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00003.png new file mode 100644 index 000000000..93bf9bc96 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_3_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_4_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_long_4_0/00000.png new file mode 100644 index 000000000..80943953e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_4_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_long_4_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_long_4_0/00001.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_long_4_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_0/00001.png new file mode 100644 index 000000000..7d7359362 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00000.png b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00000.png new file mode 100644 index 000000000..263a4391e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00001.png b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00001.png new file mode 100644 index 000000000..a8cf42f7d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00002.png b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00002.png new file mode 100644 index 000000000..e7ffedd90 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_non_ascii_0_1/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_too_long_0_0/00000.png b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_too_long_0_0/00001.png b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_0/00001.png new file mode 100644 index 000000000..7d7359362 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00000.png b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00000.png new file mode 100644 index 000000000..17edb9d30 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00001.png b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00001.png new file mode 100644 index 000000000..28456ca06 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00002.png b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00002.png new file mode 100644 index 000000000..e7ffedd90 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_accept_too_long_0_1/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00000.png b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00001.png b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00001.png new file mode 100644 index 000000000..71c80df0e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00002.png b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00002.png new file mode 100644 index 000000000..263a4391e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00003.png b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00003.png new file mode 100644 index 000000000..a8cf42f7d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00004.png b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00004.png new file mode 100644 index 000000000..e7ffedd90 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00005.png b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00005.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_hash_reject_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_message_reject_0_0/00000.png b/tests/snapshots/nanox/test_sign_message_reject_0_0/00000.png new file mode 100644 index 000000000..1b271542d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_reject_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_reject_0_0/00001.png b/tests/snapshots/nanox/test_sign_message_reject_0_0/00001.png new file mode 100644 index 000000000..71c80df0e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_reject_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_reject_0_0/00002.png b/tests/snapshots/nanox/test_sign_message_reject_0_0/00002.png new file mode 100644 index 000000000..d20a083f5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_reject_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_reject_0_0/00003.png b/tests/snapshots/nanox/test_sign_message_reject_0_0/00003.png new file mode 100644 index 000000000..c52ba9085 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_reject_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_reject_0_0/00004.png b/tests/snapshots/nanox/test_sign_message_reject_0_0/00004.png new file mode 100644 index 000000000..e90cd9db3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_reject_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png new file mode 100644 index 000000000..66ec5c34a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png new file mode 100644 index 000000000..317b8b9ff Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png new file mode 100644 index 000000000..1562d40df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_against_wrong_tapleaf_hash_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00001.png new file mode 100644 index 000000000..00a5104dc Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_highfee_1_0/00000.png new file mode 100644 index 000000000..3fa33760f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_highfee_1_0/00001.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_highfee_1_1/00000.png new file mode 100644 index 000000000..7a139ba8e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_highfee_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_highfee_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_highfee_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00001.png new file mode 100644 index 000000000..6f55ea25d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00002.png new file mode 100644 index 000000000..f67a3aa62 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_1/00000.png new file mode 100644 index 000000000..184d002d4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_miniscript_multikey_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00001.png new file mode 100644 index 000000000..84d33c06e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00002.png new file mode 100644 index 000000000..fdc5b5dc5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_1/00000.png new file mode 100644 index 000000000..78a269ed2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..e0409140b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png new file mode 100644 index 000000000..3c91cc8f1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png new file mode 100644 index 000000000..c6b820aa9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png new file mode 100644 index 000000000..84d33c06e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png new file mode 100644 index 000000000..fdc5b5dc5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png new file mode 100644 index 000000000..78a269ed2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00001.png new file mode 100644 index 000000000..c94c15959 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_2_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_2_0/00000.png new file mode 100644 index 000000000..73bcd7be8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_2_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00001.png new file mode 100644 index 000000000..4d888576f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00001.png new file mode 100644 index 000000000..c94c15959 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_2_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_2_0/00000.png new file mode 100644 index 000000000..73bcd7be8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_2_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_2_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_multisig_wsh_v1_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00001.png new file mode 100644 index 000000000..75480f35c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_1/00000.png new file mode 100644 index 000000000..905cdba55 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png new file mode 100644 index 000000000..75480f35c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png new file mode 100644 index 000000000..905cdba55 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_large_amount_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png new file mode 100644 index 000000000..70f4c453d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_pkh_1to1_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..760425304 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..f71b3a662 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..760425304 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..fda030d56 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..f71b3a662 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..97499543f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png new file mode 100644 index 000000000..700453f77 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png new file mode 100644 index 000000000..6c57d4dd5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png new file mode 100644 index 000000000..562b8c3a7 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png new file mode 100644 index 000000000..7a3d318ff Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..e0409140b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png new file mode 100644 index 000000000..3c91cc8f1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png new file mode 100644 index 000000000..c6b820aa9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png new file mode 100644 index 000000000..700453f77 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png new file mode 100644 index 000000000..6c57d4dd5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png new file mode 100644 index 000000000..562b8c3a7 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png new file mode 100644 index 000000000..7a3d318ff Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png new file mode 100644 index 000000000..700453f77 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png new file mode 100644 index 000000000..6c57d4dd5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png new file mode 100644 index 000000000..562b8c3a7 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png new file mode 100644 index 000000000..7a3d318ff Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_2to2_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png new file mode 100644 index 000000000..9e01669d1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png new file mode 100644 index 000000000..c2b649401 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png new file mode 100644 index 000000000..037d61c4d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png new file mode 100644 index 000000000..25f4bd2ba Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png new file mode 100644 index 000000000..9e01669d1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png new file mode 100644 index 000000000..99f53dd6c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png new file mode 100644 index 000000000..c2b649401 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png new file mode 100644 index 000000000..037d61c4d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png new file mode 100644 index 000000000..25f4bd2ba Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_4to3_v1_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png new file mode 100644 index 000000000..822b63031 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png new file mode 100644 index 000000000..0bcbec3dd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_all_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_0_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_sighash_default_1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_1/00000.png new file mode 100644 index 000000000..43fdfdd3c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_taproot_1to2_v1_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png new file mode 100644 index 000000000..fb4c21061 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png new file mode 100644 index 000000000..a0a62f0ab Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png new file mode 100644 index 000000000..90fa38fae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png new file mode 100644 index 000000000..22ac6174c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png new file mode 100644 index 000000000..6af6144d6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_tr_script_pk_sighash_all_1_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00000.png new file mode 100644 index 000000000..3016710d2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00002.png new file mode 100644 index 000000000..58dbded53 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00001.png new file mode 100644 index 000000000..55624c5d6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00002.png new file mode 100644 index 000000000..8c57f8c13 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00003.png new file mode 100644 index 000000000..0da504c40 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_2_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00000.png new file mode 100644 index 000000000..311e4672e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_3_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00000.png new file mode 100644 index 000000000..2ed1dca44 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00001.png new file mode 100644 index 000000000..e9fb0b0ab Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00002.png new file mode 100644 index 000000000..dd90178a3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_4_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00000.png new file mode 100644 index 000000000..e1fa952d6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_1/00000.png new file mode 100644 index 000000000..223063c8a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_0_5_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00000.png new file mode 100644 index 000000000..3016710d2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00002.png new file mode 100644 index 000000000..58dbded53 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00000.png new file mode 100644 index 000000000..311e4672e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00001.png new file mode 100644 index 000000000..e9fb0b0ab Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00002.png new file mode 100644 index 000000000..dd90178a3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_3_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00000.png new file mode 100644 index 000000000..2ed1dca44 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_1/00000.png new file mode 100644 index 000000000..223063c8a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_1_4_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00000.png new file mode 100644 index 000000000..3016710d2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00001.png new file mode 100644 index 000000000..2b51f30cf Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00002.png new file mode 100644 index 000000000..58dbded53 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00001.png new file mode 100644 index 000000000..ba54aafe9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00002.png new file mode 100644 index 000000000..eebc8645e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_1_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00000.png new file mode 100644 index 000000000..b0e1548fa Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00001.png new file mode 100644 index 000000000..55624c5d6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00002.png new file mode 100644 index 000000000..8c57f8c13 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00003.png new file mode 100644 index 000000000..0da504c40 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_2_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00000.png new file mode 100644 index 000000000..311e4672e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00001.png new file mode 100644 index 000000000..05ccaac79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00002.png new file mode 100644 index 000000000..9d8efd1df Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_3_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00000.png new file mode 100644 index 000000000..2ed1dca44 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00001.png new file mode 100644 index 000000000..073b4ff59 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00002.png new file mode 100644 index 000000000..dcf25e82e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_1/00000.png new file mode 100644 index 000000000..223063c8a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_external_inputs_2_4_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00001.png new file mode 100644 index 000000000..4c3776135 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00002.png new file mode 100644 index 000000000..8b83bf203 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00003.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_1_0/00000.png new file mode 100644 index 000000000..80f6eb6a4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_naked_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00001.png new file mode 100644 index 000000000..4c3776135 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00002.png new file mode 100644 index 000000000..85912cd4e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00003.png new file mode 100644 index 000000000..339d2a8ee Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00004.png new file mode 100644 index 000000000..9770af436 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00005.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00005.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_1_0/00000.png new file mode 100644 index 000000000..80f6eb6a4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00001.png new file mode 100644 index 000000000..4c3776135 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00002.png new file mode 100644 index 000000000..85912cd4e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00003.png new file mode 100644 index 000000000..339d2a8ee Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00004.png new file mode 100644 index 000000000..9770af436 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00005.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00005.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_0_0/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_1_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_1_0/00000.png new file mode 100644 index 000000000..80f6eb6a4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_1_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_1_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_1_0/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_opreturn_v1_1_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00002.png new file mode 100644 index 000000000..687e6d5cd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00003.png new file mode 100644 index 000000000..72bc053e6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_1/00000.png new file mode 100644 index 000000000..cc1210f5c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_0_1/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png new file mode 100644 index 000000000..73cd118fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png new file mode 100644 index 000000000..527667d52 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png new file mode 100644 index 000000000..687e6d5cd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png new file mode 100644 index 000000000..72bc053e6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png new file mode 100644 index 000000000..adea5145e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_0/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png new file mode 100644 index 000000000..cc1210f5c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_1/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png new file mode 100644 index 000000000..1366e8dae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_psbt_with_segwit_v16_v1_0_1/00001.png differ diff --git a/tests/snapshots/stax/test_dashboard/00000.png b/tests/snapshots/stax/test_dashboard/00000.png new file mode 100644 index 000000000..d0f65a916 Binary files /dev/null and b/tests/snapshots/stax/test_dashboard/00000.png differ diff --git a/tests/snapshots/stax/test_dashboard/00001.png b/tests/snapshots/stax/test_dashboard/00001.png new file mode 100644 index 000000000..dc49af16e Binary files /dev/null and b/tests/snapshots/stax/test_dashboard/00001.png differ diff --git a/tests/snapshots/stax/test_dashboard/00002.png b/tests/snapshots/stax/test_dashboard/00002.png new file mode 100644 index 000000000..d0f65a916 Binary files /dev/null and b/tests/snapshots/stax/test_dashboard/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00001.png new file mode 100644 index 000000000..7f4d17e4b Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00002.png new file mode 100644 index 000000000..51718a0c1 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00003.png new file mode 100644 index 000000000..bfea18008 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00004.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00004.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_0_0/00004.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00001.png new file mode 100644 index 000000000..7f4d17e4b Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00002.png new file mode 100644 index 000000000..a080b8d67 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00003.png new file mode 100644 index 000000000..abef66338 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00004.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00004.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_0_0/00004.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png new file mode 100644 index 000000000..7f4d17e4b Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png new file mode 100644 index 000000000..a080b8d67 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png new file mode 100644 index 000000000..abef66338 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00004.png b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00004.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_non_standard_reject_early_0_0/00004.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png new file mode 100644 index 000000000..5cdab244f Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png new file mode 100644 index 000000000..dc3a95ca9 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/0'_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png new file mode 100644 index 000000000..5853dbd5f Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png new file mode 100644 index 000000000..ca54005c5 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/10'_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png new file mode 100644 index 000000000..163e845e9 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png new file mode 100644 index 000000000..0e96bccf3 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/44'/1'/2'/1/42_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png new file mode 100644 index 000000000..f6c957cf1 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png new file mode 100644 index 000000000..3ef07c8ca Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/48'/1'/4'/1'/0/7_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png new file mode 100644 index 000000000..4ac8011b0 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png new file mode 100644 index 000000000..39f550005 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/49'/1'/1'/1/3_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png new file mode 100644 index 000000000..ef4e7a408 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png new file mode 100644 index 000000000..b928ec76e Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/84'/1'/2'/0/10_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png new file mode 100644 index 000000000..ea1af86e4 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png new file mode 100644 index 000000000..79145c9c0 Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png new file mode 100644 index 000000000..2b60f9f9b Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png new file mode 100644 index 000000000..d237b94bf Binary files /dev/null and b/tests/snapshots/stax/test_get_extended_pubkey_standard_display_m/86'/1'/4'/1/12_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png b/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png new file mode 100644 index 000000000..d2e9e991b Binary files /dev/null and b/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png b/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png new file mode 100644 index 000000000..99b673ce7 Binary files /dev/null and b/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png b/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png new file mode 100644 index 000000000..5755bc9c9 Binary files /dev/null and b/tests/snapshots/stax/test_get_wallet_address_multisig_legacy_v1_ui_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png new file mode 100644 index 000000000..300268993 Binary files /dev/null and b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png new file mode 100644 index 000000000..6b7c4de42 Binary files /dev/null and b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_0_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png new file mode 100644 index 000000000..300268993 Binary files /dev/null and b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png new file mode 100644 index 000000000..445a4b0f4 Binary files /dev/null and b/tests/snapshots/stax/test_get_wallet_address_singlesig_legacy_v1_ui_1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00000.png b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00001.png b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00001.png new file mode 100644 index 000000000..5cf0c3c19 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00002.png b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00002.png new file mode 100644 index 000000000..0aea8a378 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00003.png b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00003.png new file mode 100644 index 000000000..50f8ca2ca Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_0_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00000.png b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00001.png b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00001.png new file mode 100644 index 000000000..40cab4247 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00002.png b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00002.png new file mode 100644 index 000000000..c703c4897 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00003.png b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00000.png b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00001.png b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00001.png new file mode 100644 index 000000000..0c4941468 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00002.png b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00002.png new file mode 100644 index 000000000..a8c26d386 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00003.png b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00000.png b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00001.png b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00001.png new file mode 100644 index 000000000..a521a56be Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00002.png b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00002.png new file mode 100644 index 000000000..23809c712 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00003.png b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_miniscript_long_policy_3_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png new file mode 100644 index 000000000..9e2f810a7 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png new file mode 100644 index 000000000..40cab4247 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png new file mode 100644 index 000000000..434a102d1 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Legacy_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png new file mode 100644 index 000000000..e90b2bb8f Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png new file mode 100644 index 000000000..40cab4247 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png new file mode 100644 index 000000000..434a102d1 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Native_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png new file mode 100644 index 000000000..39c86f915 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png new file mode 100644 index 000000000..40cab4247 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png new file mode 100644 index 000000000..434a102d1 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Nested_Segwit_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png new file mode 100644 index 000000000..2c961e487 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png new file mode 100644 index 000000000..40cab4247 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png new file mode 100644 index 000000000..434a102d1 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_unusual_singlesig_accounts_Unusual_Taproot_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00001.png new file mode 100644 index 000000000..502335564 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00002.png new file mode 100644 index 000000000..27eaa5e9f Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00002.png new file mode 100644 index 000000000..2a0de0b74 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00001.png new file mode 100644 index 000000000..1bd62f7ff Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00002.png new file mode 100644 index 000000000..4882e790d Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00002.png new file mode 100644 index 000000000..b6518b389 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_legacy_v1_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00001.png new file mode 100644 index 000000000..6fedf31cc Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00002.png new file mode 100644 index 000000000..74f466b1b Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00002.png new file mode 100644 index 000000000..c7c4ec3a5 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00001.png new file mode 100644 index 000000000..5d2dd0714 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00002.png new file mode 100644 index 000000000..5f1840688 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00002.png new file mode 100644 index 000000000..3659f40e1 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_sh_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00001.png new file mode 100644 index 000000000..1d6d1024a Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00002.png new file mode 100644 index 000000000..a105b1fe9 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00002.png new file mode 100644 index 000000000..c703c4897 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00001.png new file mode 100644 index 000000000..c9b431885 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00002.png new file mode 100644 index 000000000..be5443d82 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00002.png new file mode 100644 index 000000000..9c0542acf Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_accept_wit_v1_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00001.png new file mode 100644 index 000000000..1d6d1024a Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00001.png new file mode 100644 index 000000000..2d7408bdf Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00002.png new file mode 100644 index 000000000..78ec3da74 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00001.png new file mode 100644 index 000000000..976d0604f Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00002.png new file mode 100644 index 000000000..c703c4897 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_invalid_pubkey_version_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00001.png new file mode 100644 index 000000000..1d6d1024a Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_reject_header_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00001.png new file mode 100644 index 000000000..c9b431885 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_reject_header_v1_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00001.png new file mode 100644 index 000000000..b41874f80 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00002.png new file mode 100644 index 000000000..a105b1fe9 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00002.png new file mode 100644 index 000000000..c703c4897 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_pk_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00001.png new file mode 100644 index 000000000..ba8cd19e1 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00001.png new file mode 100644 index 000000000..40cab4247 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00002.png new file mode 100644 index 000000000..c7c4ec3a5 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00001.png new file mode 100644 index 000000000..0c4941468 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00002.png new file mode 100644 index 000000000..a105b1fe9 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00001.png new file mode 100644 index 000000000..ffe4d17c8 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00002.png new file mode 100644 index 000000000..c703c4897 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00003.png b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_script_sortedmulti_3_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00001.png new file mode 100644 index 000000000..a79e36fde Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00001.png new file mode 100644 index 000000000..005a6ba0b Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00002.png new file mode 100644 index 000000000..ac99afb9d Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00002.png new file mode 100644 index 000000000..c703c4897 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_tr_with_nums_keypath_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00000.png b/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00000.png new file mode 100644 index 000000000..13cf02337 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00001.png b/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00001.png new file mode 100644 index 000000000..3ee790be5 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00002.png b/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00002.png new file mode 100644 index 000000000..345ce91d4 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00000.png b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00001.png b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00001.png new file mode 100644 index 000000000..cb1605435 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00002.png b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00002.png new file mode 100644 index 000000000..a105b1fe9 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00003.png b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_1_0/00003.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00000.png b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00000.png new file mode 100644 index 000000000..825d88301 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00001.png b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00001.png new file mode 100644 index 000000000..0dcdcce99 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00002.png b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00002.png new file mode 100644 index 000000000..c703c4897 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00003.png b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00003.png new file mode 100644 index 000000000..055f6df69 Binary files /dev/null and b/tests/snapshots/stax/test_register_wallet_with_long_name_2_0/00003.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_input_changed_0_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_input_changed_1_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_input_changed_1_0/00001.png b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00001.png b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00002.png b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_input_changed_3_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_input_changed_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_output_changed_0_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_output_changed_1_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_output_changed_1_0/00001.png b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00001.png b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00001.png new file mode 100644 index 000000000..7d0653538 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00002.png b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_output_changed_3_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_output_changed_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_sign_0_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_sign_1_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_sign_1_0/00001.png b/tests/snapshots/stax/test_sighash_all_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00001.png b/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00002.png b/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_sign_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_all_anyone_sign_3_0/00000.png b/tests/snapshots/stax/test_sighash_all_anyone_sign_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_anyone_sign_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_input_modified_0_0/00000.png b/tests/snapshots/stax/test_sighash_all_input_modified_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_input_modified_0_0/00001.png b/tests/snapshots/stax/test_sighash_all_input_modified_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_input_modified_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00000.png b/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00001.png b/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00002.png b/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_input_modified_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_all_input_modified_2_0/00000.png b/tests/snapshots/stax/test_sighash_all_input_modified_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_input_modified_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_output_modified_0_0/00000.png b/tests/snapshots/stax/test_sighash_all_output_modified_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_output_modified_0_0/00001.png b/tests/snapshots/stax/test_sighash_all_output_modified_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_output_modified_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00000.png b/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00001.png b/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00001.png new file mode 100644 index 000000000..7d0653538 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00002.png b/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_output_modified_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_all_output_modified_2_0/00000.png b/tests/snapshots/stax/test_sighash_all_output_modified_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_output_modified_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_sign_psbt_0_0/00000.png b/tests/snapshots/stax/test_sighash_all_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_sign_psbt_0_0/00001.png b/tests/snapshots/stax/test_sighash_all_sign_psbt_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_sign_psbt_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00000.png b/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00001.png b/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00002.png b/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_sign_psbt_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_all_sign_psbt_2_0/00000.png b/tests/snapshots/stax/test_sighash_all_sign_psbt_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_all_sign_psbt_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_input_changed_0_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_input_changed_1_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_input_changed_1_0/00001.png b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00001.png b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00002.png b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_input_changed_3_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_input_changed_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_output_changed_0_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_output_changed_1_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_output_changed_1_0/00001.png b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00001.png b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00001.png new file mode 100644 index 000000000..7d0653538 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00002.png b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_output_changed_3_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_output_changed_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_sign_0_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_sign_1_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_sign_1_0/00001.png b/tests/snapshots/stax/test_sighash_none_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00001.png b/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00002.png b/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_sign_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_none_anyone_sign_3_0/00000.png b/tests/snapshots/stax/test_sighash_none_anyone_sign_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_anyone_sign_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_input_modified_0_0/00000.png b/tests/snapshots/stax/test_sighash_none_input_modified_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_input_modified_1_0/00000.png b/tests/snapshots/stax/test_sighash_none_input_modified_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_input_modified_1_0/00001.png b/tests/snapshots/stax/test_sighash_none_input_modified_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00000.png b/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00001.png b/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00002.png b/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_input_modified_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_none_input_modified_3_0/00000.png b/tests/snapshots/stax/test_sighash_none_input_modified_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_input_modified_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_output_modified_0_0/00000.png b/tests/snapshots/stax/test_sighash_none_output_modified_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_output_modified_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_output_modified_1_0/00000.png b/tests/snapshots/stax/test_sighash_none_output_modified_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_output_modified_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_output_modified_1_0/00001.png b/tests/snapshots/stax/test_sighash_none_output_modified_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_output_modified_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00000.png b/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00001.png b/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00001.png new file mode 100644 index 000000000..7d0653538 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00002.png b/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_output_modified_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_none_output_modified_3_0/00000.png b/tests/snapshots/stax/test_sighash_none_output_modified_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_output_modified_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_sign_psbt_0_0/00000.png b/tests/snapshots/stax/test_sighash_none_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_sign_psbt_1_0/00000.png b/tests/snapshots/stax/test_sighash_none_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_sign_psbt_1_0/00001.png b/tests/snapshots/stax/test_sighash_none_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00000.png b/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00001.png b/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00002.png b/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_sign_psbt_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_none_sign_psbt_3_0/00000.png b/tests/snapshots/stax/test_sighash_none_sign_psbt_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_none_sign_psbt_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash1_0_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash1_0_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash1_1_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_1_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash1_1_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash1_2_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_2_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash1_2_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash1_3_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash1_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_0_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_1_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_1_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_2_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_2_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_2_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_3_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_3_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_3_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_3_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash2_4_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash2_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_0_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_1_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_1_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_2_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_2_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_2_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_3_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_3_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_3_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_3_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash3_4_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash3_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_0_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_1_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_1_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_2_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_2_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_2_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_3_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_3_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_3_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_3_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash81_4_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash81_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_0_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_1_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_1_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_2_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_2_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_2_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_3_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_3_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_3_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_3_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash82_4_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash82_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_0_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_1_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_1_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_2_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_2_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_2_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_3_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_3_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_3_0/00001.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_3_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_segwitv0_sighash83_4_0/00000.png b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_segwitv0_sighash83_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_input_changed_0_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_input_changed_1_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_input_changed_1_0/00001.png b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00001.png b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00002.png b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_input_changed_3_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_input_changed_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_output_changed_0_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_output_changed_1_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_output_changed_1_0/00001.png b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00001.png b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00001.png new file mode 100644 index 000000000..7d0653538 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00002.png b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_output_changed_3_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_output_changed_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_sign_0_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_sign_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_sign_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_sign_1_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_sign_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_sign_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_sign_1_0/00001.png b/tests/snapshots/stax/test_sighash_single_anyone_sign_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_sign_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00001.png b/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00002.png b/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_sign_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_single_anyone_sign_3_0/00000.png b/tests/snapshots/stax/test_sighash_single_anyone_sign_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_anyone_sign_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_input_modified_0_0/00000.png b/tests/snapshots/stax/test_sighash_single_input_modified_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_input_modified_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_input_modified_1_0/00000.png b/tests/snapshots/stax/test_sighash_single_input_modified_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_input_modified_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_input_modified_1_0/00001.png b/tests/snapshots/stax/test_sighash_single_input_modified_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_input_modified_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00000.png b/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00001.png b/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00002.png b/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_input_modified_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_single_input_modified_3_0/00000.png b/tests/snapshots/stax/test_sighash_single_input_modified_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_input_modified_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_different_index_modified_0_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_different_index_modified_1_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_different_index_modified_1_0/00001.png b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00000.png new file mode 100644 index 000000000..6efdc63a7 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00001.png b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00001.png new file mode 100644 index 000000000..7d0653538 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00002.png b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_different_index_modified_3_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_different_index_modified_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_same_index_modified_0_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_same_index_modified_1_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_same_index_modified_1_0/00001.png b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00001.png b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00001.png new file mode 100644 index 000000000..7d0653538 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00002.png b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_single_output_same_index_modified_3_0/00000.png b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_output_same_index_modified_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_sign_psbt_0_0/00000.png b/tests/snapshots/stax/test_sighash_single_sign_psbt_0_0/00000.png new file mode 100644 index 000000000..56f1c4952 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_sign_psbt_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_sign_psbt_1_0/00000.png b/tests/snapshots/stax/test_sighash_single_sign_psbt_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_sign_psbt_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_sign_psbt_1_0/00001.png b/tests/snapshots/stax/test_sighash_single_sign_psbt_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_sign_psbt_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00000.png b/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00001.png b/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00002.png b/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_sign_psbt_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_single_sign_psbt_3_0/00000.png b/tests/snapshots/stax/test_sighash_single_sign_psbt_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_single_sign_psbt_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_0_0/00000.png b/tests/snapshots/stax/test_sighash_unsupported_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_0_0/00001.png b/tests/snapshots/stax/test_sighash_unsupported_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_1_0/00000.png b/tests/snapshots/stax/test_sighash_unsupported_1_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_1_0/00001.png b/tests/snapshots/stax/test_sighash_unsupported_1_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_1_0/00002.png b/tests/snapshots/stax/test_sighash_unsupported_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_2_0/00000.png b/tests/snapshots/stax/test_sighash_unsupported_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_0_0/00000.png b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_0_0/00001.png b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00000.png b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00000.png new file mode 100644 index 000000000..97524e00d Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00001.png b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00001.png new file mode 100644 index 000000000..e72ba2fa6 Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00002.png b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_2_0/00000.png b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sighash_unsupported_for_segwitv0_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_0_0/00000.png b/tests/snapshots/stax/test_sign_message_0_0/00000.png new file mode 100644 index 000000000..c42ad9a36 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_0_0/00001.png b/tests/snapshots/stax/test_sign_message_0_0/00001.png new file mode 100644 index 000000000..7acd8425e Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_0_0/00002.png b/tests/snapshots/stax/test_sign_message_0_0/00002.png new file mode 100644 index 000000000..e50bf611c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_0_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_0_0/00000.png new file mode 100644 index 000000000..c42ad9a36 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_0_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_0_0/00001.png new file mode 100644 index 000000000..331444038 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_0_0/00002.png b/tests/snapshots/stax/test_sign_message_accept_0_0/00002.png new file mode 100644 index 000000000..e50bf611c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_0_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_long_0_0/00000.png new file mode 100644 index 000000000..c42ad9a36 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_0_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_long_0_0/00001.png new file mode 100644 index 000000000..695fc7c4c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_0_0/00002.png b/tests/snapshots/stax/test_sign_message_accept_long_0_0/00002.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_1_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_long_1_0/00000.png new file mode 100644 index 000000000..7ec24f1e3 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_1_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_long_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_2_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_long_2_0/00000.png new file mode 100644 index 000000000..c8a1a6d94 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_2_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_long_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_3_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_long_3_0/00000.png new file mode 100644 index 000000000..d5673beef Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_3_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_long_3_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_4_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_long_4_0/00000.png new file mode 100644 index 000000000..823fe9147 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_long_4_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_long_4_0/00001.png new file mode 100644 index 000000000..e50bf611c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_long_4_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_non_ascii_0_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_non_ascii_0_0/00000.png new file mode 100644 index 000000000..731541c9c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_non_ascii_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_non_ascii_0_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_non_ascii_0_0/00001.png new file mode 100644 index 000000000..e50bf611c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_non_ascii_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_too_long_0_0/00000.png b/tests/snapshots/stax/test_sign_message_accept_too_long_0_0/00000.png new file mode 100644 index 000000000..f59ea32ec Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_too_long_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_accept_too_long_0_0/00001.png b/tests/snapshots/stax/test_sign_message_accept_too_long_0_0/00001.png new file mode 100644 index 000000000..e50bf611c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_accept_too_long_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_hash_reject_0_0/00000.png b/tests/snapshots/stax/test_sign_message_hash_reject_0_0/00000.png new file mode 100644 index 000000000..83a99df7b Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_hash_reject_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_hash_reject_0_0/00001.png b/tests/snapshots/stax/test_sign_message_hash_reject_0_0/00001.png new file mode 100644 index 000000000..e50bf611c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_hash_reject_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_hash_reject_0_1/00000.png b/tests/snapshots/stax/test_sign_message_hash_reject_0_1/00000.png new file mode 100644 index 000000000..751b367f2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_hash_reject_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_reject_0_0/00000.png b/tests/snapshots/stax/test_sign_message_reject_0_0/00000.png new file mode 100644 index 000000000..c42ad9a36 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_reject_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_reject_0_0/00001.png b/tests/snapshots/stax/test_sign_message_reject_0_0/00001.png new file mode 100644 index 000000000..d375d66b7 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_reject_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_reject_0_0/00002.png b/tests/snapshots/stax/test_sign_message_reject_0_0/00002.png new file mode 100644 index 000000000..e50bf611c Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_reject_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_message_reject_0_1/00000.png b/tests/snapshots/stax/test_sign_message_reject_0_1/00000.png new file mode 100644 index 000000000..751b367f2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_reject_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png new file mode 100644 index 000000000..fe8259008 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png new file mode 100644 index 000000000..f0bfb1ca7 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png new file mode 100644 index 000000000..ec47288ae Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_1/00000.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_1/00000.png new file mode 100644 index 000000000..adccaeb16 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00000.png new file mode 100644 index 000000000..49e98e252 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00001.png new file mode 100644 index 000000000..f56cc38ba Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00002.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_against_wrong_tapleaf_hash_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_fail_11_changes_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_fail_11_changes_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_fail_11_changes_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_fail_11_changes_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_fail_11_changes_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_highfee_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_highfee_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_highfee_1_0/00000.png new file mode 100644 index 000000000..dc7955b79 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_highfee_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_highfee_2_0/00000.png new file mode 100644 index 000000000..a2b8b9f2b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_2_1/00000.png b/tests/snapshots/stax/test_sign_psbt_highfee_2_1/00000.png new file mode 100644 index 000000000..4bace98bf Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_2_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_2_1/00001.png b/tests/snapshots/stax/test_sign_psbt_highfee_2_1/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_2_1/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_highfee_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_highfee_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_highfee_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00000.png new file mode 100644 index 000000000..fe8259008 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00001.png new file mode 100644 index 000000000..1bcaeea79 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00002.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00002.png new file mode 100644 index 000000000..ec47288ae Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_1/00000.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_1/00000.png new file mode 100644 index 000000000..adccaeb16 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00000.png new file mode 100644 index 000000000..b35016dec Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00001.png new file mode 100644 index 000000000..e5d77cf1d Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00002.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_miniscript_multikey_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00000.png new file mode 100644 index 000000000..fe8259008 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00001.png new file mode 100644 index 000000000..48f68a44d Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00002.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00002.png new file mode 100644 index 000000000..ec47288ae Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_1/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_1/00000.png new file mode 100644 index 000000000..adccaeb16 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00000.png new file mode 100644 index 000000000..fdbf91e7a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00001.png new file mode 100644 index 000000000..ad4daa734 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00002.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..fe8259008 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png new file mode 100644 index 000000000..48f68a44d Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png new file mode 100644 index 000000000..ec47288ae Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_1/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_1/00000.png new file mode 100644 index 000000000..adccaeb16 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..ba590bdfd Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00000.png new file mode 100644 index 000000000..fdbf91e7a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00001.png new file mode 100644 index 000000000..ad4daa734 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00002.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_3_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_4_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00000.png new file mode 100644 index 000000000..fe8259008 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00001.png new file mode 100644 index 000000000..48f68a44d Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00002.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00002.png new file mode 100644 index 000000000..ec47288ae Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_1/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_1/00000.png new file mode 100644 index 000000000..adccaeb16 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_2_0/00000.png new file mode 100644 index 000000000..caa284a71 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_3_0/00000.png new file mode 100644 index 000000000..08a0fc72e Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_3_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_3_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_4_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00000.png new file mode 100644 index 000000000..fe8259008 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00001.png new file mode 100644 index 000000000..48f68a44d Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00002.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00002.png new file mode 100644 index 000000000..ec47288ae Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_1/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_1/00000.png new file mode 100644 index 000000000..adccaeb16 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_2_0/00000.png new file mode 100644 index 000000000..caa284a71 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_3_0/00000.png new file mode 100644 index 000000000..08a0fc72e Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_3_0/00001.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_3_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_4_0/00000.png b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_4_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_multisig_wsh_v1_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00000.png new file mode 100644 index 000000000..f89bec723 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00001.png new file mode 100644 index 000000000..adb6a691b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00000.png new file mode 100644 index 000000000..f89bec723 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00001.png new file mode 100644 index 000000000..adb6a691b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_large_amount_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00000.png new file mode 100644 index 000000000..17b5e632c Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00001.png new file mode 100644 index 000000000..d2e7752c2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00000.png new file mode 100644 index 000000000..17b5e632c Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00001.png new file mode 100644 index 000000000..d2e7752c2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_0_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00000.png new file mode 100644 index 000000000..17b5e632c Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00001.png new file mode 100644 index 000000000..d2e7752c2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_other_encodings_1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00000.png new file mode 100644 index 000000000..17b5e632c Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00001.png new file mode 100644 index 000000000..d2e7752c2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_pkh_1to1_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..1277ee8fc Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_2_0/00000.png new file mode 100644 index 000000000..918961ee3 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..1277ee8fc Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_2_0/00000.png new file mode 100644 index 000000000..918961ee3 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_sh_wpkh_1to2_v1_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_2_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..3a71fd4de Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_2_0/00000.png new file mode 100644 index 000000000..06f5cd200 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_1to2_v1_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00000.png new file mode 100644 index 000000000..23c3a90cf Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00001.png new file mode 100644 index 000000000..099a93882 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png new file mode 100644 index 000000000..ba590bdfd Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00000.png new file mode 100644 index 000000000..23c3a90cf Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00001.png new file mode 100644 index 000000000..099a93882 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00000.png new file mode 100644 index 000000000..23c3a90cf Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00001.png new file mode 100644 index 000000000..099a93882 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_2to2_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png new file mode 100644 index 000000000..0b6df0c48 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00000.png new file mode 100644 index 000000000..9aa0dfef1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00001.png new file mode 100644 index 000000000..d86e844f4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00002.png new file mode 100644 index 000000000..cdbaa25e5 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png new file mode 100644 index 000000000..0b6df0c48 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00000.png new file mode 100644 index 000000000..9aa0dfef1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00001.png new file mode 100644 index 000000000..d86e844f4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00002.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00002.png new file mode 100644 index 000000000..cdbaa25e5 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_4to3_v1_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_1_0/00000.png new file mode 100644 index 000000000..9c8b1c9dc Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_1_0/00001.png new file mode 100644 index 000000000..8cb4c4c47 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_singlesig_wpkh_selftransfer_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00000.png new file mode 100644 index 000000000..5b406ed2a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00001.png new file mode 100644 index 000000000..72ad42f3b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_all_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00000.png new file mode 100644 index 000000000..5b406ed2a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00001.png new file mode 100644 index 000000000..72ad42f3b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_0_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00000.png new file mode 100644 index 000000000..5b406ed2a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00001.png new file mode 100644 index 000000000..72ad42f3b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_sighash_default_1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00000.png new file mode 100644 index 000000000..5b406ed2a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00001.png new file mode 100644 index 000000000..72ad42f3b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_taproot_1to2_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png new file mode 100644 index 000000000..fe8259008 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png new file mode 100644 index 000000000..e22ababe8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png new file mode 100644 index 000000000..ec47288ae Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_1/00000.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_1/00000.png new file mode 100644 index 000000000..adccaeb16 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_0_1/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00000.png new file mode 100644 index 000000000..be3f449b3 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00001.png new file mode 100644 index 000000000..836808a6e Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00002.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_2_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_tr_script_pk_sighash_all_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_0_0/00000.png new file mode 100644 index 000000000..c37ecbb2e Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_2_0/00000.png new file mode 100644 index 000000000..ea5fb3d81 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_3_0/00000.png new file mode 100644 index 000000000..42106b4bb Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_3_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_3_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_4_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_4_0/00000.png new file mode 100644 index 000000000..7c1a7e9e2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_4_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_4_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_4_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_5_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_5_0/00000.png new file mode 100644 index 000000000..6ee137310 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_5_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_5_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_5_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_5_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00000.png new file mode 100644 index 000000000..418e6ef8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00001.png new file mode 100644 index 000000000..eea1e44ab Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00002.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00002.png new file mode 100644 index 000000000..e527647ea Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_6_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_7_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_7_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_0_7_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_0_0/00000.png new file mode 100644 index 000000000..c37ecbb2e Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_2_0/00000.png new file mode 100644 index 000000000..28e9f1f67 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_3_0/00000.png new file mode 100644 index 000000000..d107be40b Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_3_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_3_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_4_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_4_0/00000.png new file mode 100644 index 000000000..702835c99 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_4_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_4_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_4_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00000.png new file mode 100644 index 000000000..46c831e49 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00001.png new file mode 100644 index 000000000..03e7b30e1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00002.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00002.png new file mode 100644 index 000000000..71a9455bd Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_5_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_6_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_6_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_1_6_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_0_0/00000.png new file mode 100644 index 000000000..c37ecbb2e Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_1_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_2_0/00000.png new file mode 100644 index 000000000..28e9f1f67 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_2_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_3_0/00000.png new file mode 100644 index 000000000..a0d52ebea Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_3_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_3_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_3_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_4_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_4_0/00000.png new file mode 100644 index 000000000..87b4d22b2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_4_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_4_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_4_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_4_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00000.png new file mode 100644 index 000000000..46c831e49 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00001.png new file mode 100644 index 000000000..03e7b30e1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00002.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00002.png new file mode 100644 index 000000000..71a9455bd Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_5_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_6_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_6_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_external_inputs_2_6_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_1_0/00000.png new file mode 100644 index 000000000..3df60593c Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_2_0/00000.png new file mode 100644 index 000000000..766686653 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_naked_opreturn_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_1_0/00000.png new file mode 100644 index 000000000..480e7557f Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_2_0/00000.png new file mode 100644 index 000000000..766686653 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_1_0/00000.png new file mode 100644 index 000000000..480e7557f Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_1_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_2_0/00000.png new file mode 100644 index 000000000..766686653 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_2_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_2_0/00001.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_2_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_3_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_3_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_opreturn_v1_3_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00000.png new file mode 100644 index 000000000..6ccba4e59 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00001.png new file mode 100644 index 000000000..1c41bd804 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_2_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png new file mode 100644 index 000000000..14439c154 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_0_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png new file mode 100644 index 000000000..ddae32d91 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_0_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00000.png new file mode 100644 index 000000000..6ccba4e59 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00000.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00001.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00001.png new file mode 100644 index 000000000..1c41bd804 Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00001.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00002.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00002.png new file mode 100644 index 000000000..1f9f7a35a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_1_0/00002.png differ diff --git a/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_2_0/00000.png b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_2_0/00000.png new file mode 100644 index 000000000..a21279c8a Binary files /dev/null and b/tests/snapshots/stax/test_sign_psbt_with_segwit_v16_v1_2_0/00000.png differ diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index e969632ba..c5027e8fd 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -1,24 +1,24 @@ -import pytest +from ragger.firmware import Firmware +from ragger.navigator import NavInsID, Navigator +from pathlib import Path -from speculos.client import SpeculosClient +ROOT_SCREENSHOT_PATH = Path(__file__).parent.resolve() -def test_dashboard(comm: SpeculosClient, is_speculos: bool, app_version: str): +def test_dashboard(navigator: Navigator, firmware: Firmware, test_name: str): # Tests that the text shown in the dashboard screens are the expected ones - if not is_speculos: - pytest.skip("Requires speculos") - - comm.press_and_release("right") - comm.wait_for_text_event("Version") - comm.wait_for_text_event(app_version) - - comm.press_and_release("right") - comm.wait_for_text_event("About") - - comm.press_and_release("right") - comm.wait_for_text_event("Quit") - - comm.press_and_release("right") - comm.wait_for_text_event("Bitcoin Testnet") - comm.wait_for_text_event("is ready") + if firmware.device.startswith("nano"): + instructions = [ + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK + ] + else: + instructions = [ + NavInsID.USE_CASE_HOME_INFO, + NavInsID.USE_CASE_SETTINGS_SINGLE_PAGE_EXIT + ] + + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, test_name, instructions, + screen_change_before_first_instruction=False) diff --git a/tests/test_e2e_miniscript.py b/tests/test_e2e_miniscript.py new file mode 100644 index 000000000..2aac5c0b1 --- /dev/null +++ b/tests/test_e2e_miniscript.py @@ -0,0 +1,463 @@ +import pytest + +from typing import List, Union + +import hmac +from hashlib import sha256 +from decimal import Decimal + +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import WalletPolicy + +from test_utils import SpeculosGlobals, get_internal_xpub, count_internal_keys + +from ragger_bitcoin import RaggerClient +from ragger_bitcoin.ragger_instructions import Instructions +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU + +from .instructions import e2e_register_wallet_instruction, e2e_sign_psbt_instruction + +from .conftest import create_new_wallet, generate_blocks, get_unique_wallet_name, get_wallet_rpc, testnet_to_regtest_addr as T +from .conftest import AuthServiceProxy + + +def run_test_e2e(navigator: Navigator, client: RaggerClient, wallet_policy: WalletPolicy, core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, speculos_globals: SpeculosGlobals, + instructions_register_wallet: Instructions, + instructions_sign_psbt: Instructions, test_name: str): + wallet_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions_register_wallet, testname=f"{test_name}_register") + + assert wallet_id == wallet_policy.id + + assert hmac.compare_digest( + hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), + wallet_hmac, + ) + + address_hww = client.get_wallet_address(wallet_policy, wallet_hmac, 0, 3, False) + + # ==> verify the address matches what bitcoin-core computes + receive_descriptor = wallet_policy.get_descriptor(change=False) + receive_descriptor_info = rpc.getdescriptorinfo(receive_descriptor) + # bitcoin-core adds the checksum, and requires it for other calls + receive_descriptor_chk = receive_descriptor_info["descriptor"] + address_core = rpc.deriveaddresses(receive_descriptor_chk, [3, 3])[0] + + assert T(address_hww) == address_core + + # also get the change descriptor for later + change_descriptor = wallet_policy.get_descriptor(change=True) + change_descriptor_info = rpc.getdescriptorinfo(change_descriptor) + change_descriptor_chk = change_descriptor_info["descriptor"] + + # ==> import wallet in bitcoin-core + + multisig_wallet_name = get_unique_wallet_name() + rpc.createwallet( + wallet_name=multisig_wallet_name, + disable_private_keys=True, + descriptors=True, + ) + multisig_rpc = get_wallet_rpc(multisig_wallet_name) + multisig_rpc.importdescriptors([{ + "desc": receive_descriptor_chk, + "active": True, + "internal": False, + "timestamp": "now" + }, { + "desc": change_descriptor_chk, + "active": True, + "internal": True, + "timestamp": "now" + }]) + + # ==> fund the multisig wallet and get prevout info + + rpc_test_wallet.sendtoaddress(T(address_hww), "0.1") + generate_blocks(1) + + assert multisig_rpc.getwalletinfo()["balance"] == Decimal("0.1") + + # ==> prepare a psbt spending from the wallet + + out_address = rpc_test_wallet.getnewaddress() + + result = multisig_rpc.walletcreatefundedpsbt( + outputs={ + out_address: Decimal("0.01") + }, + options={ + "changePosition": 1 # We need a fixed position to be able to know how to navigate in the flows + } + ) + + psbt_b64 = result["psbt"] + + # ==> sign it with the hww + + psbt = PSBT() + psbt.deserialize(psbt_b64) + + hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac, navigator, + instructions=instructions_sign_psbt, + testname=f"{test_name}_sign") + + n_internal_keys = count_internal_keys(speculos_globals.seed, "test", wallet_policy) + assert len(hww_sigs) == n_internal_keys * len(psbt.inputs) # should be true as long as all inputs are internal + + for i, part_sig in hww_sigs: + psbt.inputs[i].partial_sigs[part_sig.pubkey] = part_sig.signature + + signed_psbt_hww_b64 = psbt.serialize() + + # ==> sign it with bitcoin-core + partial_psbts = [signed_psbt_hww_b64] + + for core_wallet_name in core_wallet_names: + partial_psbts.append(get_wallet_rpc(core_wallet_name).walletprocesspsbt(psbt_b64)["psbt"]) + + # ==> finalize the psbt, extract tx and broadcast + combined_psbt = rpc.combinepsbt(partial_psbts) + result = rpc.finalizepsbt(combined_psbt) + + assert result["complete"] == True + rawtx = result["hex"] + + # make sure the transaction is valid by broadcasting it (would fail if rejected) + rpc.sendrawtransaction(rawtx) + + +def run_test_invalid(client: RaggerClient, descriptor_template: str, keys_info: List[str]): + wallet_policy = WalletPolicy( + name="Invalid wallet", + descriptor_template=descriptor_template, + keys_info=keys_info) + + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet_policy) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError or DeviceException.exc.get( + e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + +def test_e2e_miniscript_one_of_two_1(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # One of two keys (equally likely) + # or(pk(key_1),pk(key_2)) + + path = "499'/1'/0'" + _, core_xpub_orig = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Joint account", + descriptor_template="wsh(or_b(pk(@0/**),s:pk(@1/**)))", + keys_info=[ + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_one_of_two_2(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # One of two keys (one likely, one unlikely) + # or(99@pk(key_likely),pk(key_unlikely)) + + path = "499'/1'/0'" + _, core_xpub_orig = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Joint account", + descriptor_template="wsh(or_d(pk(@0/**),pkh(@1/**)))", + keys_info=[ + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [_], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_2fa(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # A user and a 2FA service need to sign off, but after 90 days the user alone is enough + # and(pk(key_user),or(99@pk(key_service),older(12960))) + + path = "48'/1'/0'/2'" + core_wallet_name, core_xpub_orig = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="2FA wallet", + descriptor_template="wsh(and_v(v:pk(@0/**),or_d(pk(@1/**),older(12960))))", + keys_info=[ + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_decaying_3of3(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # A 3-of-3 that becomes a 2-of-3 after 90 days + # thresh(3,pk(key_1),pk(key_2),pk(key_3),older(12960)) + + path = "48'/1'/0'/2'" + core_wallet_name1, core_xpub_orig1 = create_new_wallet() + core_wallet_name2, core_xpub_orig2 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Decaying 3of3", + descriptor_template="wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@2/**),sln:older(12960)))", + keys_info=[ + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_bolt3_offered_htlc(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # The BOLT #3 offered HTLC policy + # or(pk(key_revocation),and(pk(key_remote),or(pk(key_local),hash160(H)))) + + path = "48'/1'/0'/2'" + core_wallet_name1, core_xpub_orig1 = create_new_wallet() + core_wallet_name2, core_xpub_orig2 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + H = "395e368b267d64945f30e4b71de1054f364c9473" # random + wallet_policy = WalletPolicy( + name="BOLT #3 offered", + descriptor_template=f"wsh(t:or_c(pk(@0/**),and_v(v:pk(@1/**),or_c(pk(@2/**),v:hash160({H})))))", + keys_info=[ + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_bolt3_received_htlc(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # The BOLT #3 received HTLC policy + # andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation)) + + path = "48'/1'/0'/2'" + core_wallet_name1, core_xpub_orig1 = create_new_wallet() + core_wallet_name2, core_xpub_orig2 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + H = "395e368b267d64945f30e4b71de1054f364c9473" # random + wallet_policy = WalletPolicy( + name="BOLT #3 received", + descriptor_template=f"wsh(andor(pk(@0/**),or_i(and_v(v:pkh(@1/**),hash160({H})),older(1008)),pk(@2/**)))", + keys_info=[ + f"{core_xpub_orig1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig2}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_me_or_3of5(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + path = "48'/1'/0'/2'" + _, core_xpub_orig1 = create_new_wallet() + _, core_xpub_orig2 = create_new_wallet() + _, core_xpub_orig3 = create_new_wallet() + _, core_xpub_orig4 = create_new_wallet() + _, core_xpub_orig5 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + internal_xpub_orig = f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}" + + wallet_policy = WalletPolicy( + name="Me or them", + descriptor_template="wsh(or_d(pk(@0/**),multi(3,@1/**,@2/**,@3/**,@4/**,@5/**)))", + keys_info=[ + internal_xpub_orig, + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + f"{core_xpub_orig3}", + f"{core_xpub_orig4}", + f"{core_xpub_orig5}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_me_large_vault(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + if (firmware.name == "nanos"): + pytest.skip("Not supported on Nano S due to memory limitations") + + path = "48'/1'/0'/2'" + _, core_xpub_orig1 = create_new_wallet() + _, core_xpub_orig2 = create_new_wallet() + _, core_xpub_orig3 = create_new_wallet() + _, core_xpub_orig4 = create_new_wallet() + _, core_xpub_orig5 = create_new_wallet() + _, core_xpub_orig6 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + internal_xpub_orig = f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}" + + wallet_policy = WalletPolicy( + name="Large vault", + descriptor_template="wsh(or_d(pk(@0/**),andor(thresh(1,utv:thresh(1,pkh(@1/**),a:pkh(@2/**)),autv:thresh(1,pkh(@3/**),a:pkh(@4/**))),after(1685577600),and_v(v:and_v(v:pkh(@5/**),thresh(1,pkh(@6/**))),after(1685318400)))))", + keys_info=[ + internal_xpub_orig, + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + f"{core_xpub_orig3}", + f"{core_xpub_orig4}", + f"{core_xpub_orig5}", + f"{core_xpub_orig6}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_me_and_bob_or_me_and_carl_1(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # policy: or(and(pk(A1), pk(B)),and(pk(A2), pk(C))) + # where A1 and A2 are both internal keys; therefore, two signatures per input must be returned + + core_wallet_name1, core_xpub_orig1 = create_new_wallet() + _, core_xpub_orig2 = create_new_wallet() + + path1 = "44'/1'/0'" + path2 = "44'/1'/1'" + internal_xpub_1 = get_internal_xpub(speculos_globals.seed, path1) + internal_xpub_orig_1 = f"[{speculos_globals.master_key_fingerprint.hex()}/{path1}]{internal_xpub_1}" + internal_xpub_2 = get_internal_xpub(speculos_globals.seed, path2) + internal_xpub_orig_2 = f"[{speculos_globals.master_key_fingerprint.hex()}/{path2}]{internal_xpub_2}" + + wallet_policy = WalletPolicy( + name="Me and Bob or me and Carl", + descriptor_template="wsh(c:andor(pk(@0/**),pk_k(@1/**),and_v(v:pk(@2/**),pk_k(@3/**))))", + keys_info=[ + internal_xpub_orig_1, + f"{core_xpub_orig1}", + internal_xpub_orig_2, + f"{core_xpub_orig2}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_nanos_large_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # Nano S has much tighter memory limits. + # The policy in this test requires 304 bytes after is parsed, which is larger than the previous 276. + # However, it is a kind of policy in the style of the Liana wallet, that it would be nice to support. + + # reported by pythcoiner + + if firmware.name != "nanos": + pytest.skip("Test only for Nano S") + + core_wallet_name1, core_xpub_orig1 = create_new_wallet() + core_wallet_name2, core_xpub_orig2 = create_new_wallet() + core_wallet_name3, core_xpub_orig3 = create_new_wallet() + + path = "44'/1'/0'" + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + internal_xpub_orig = f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}" + + wallet_policy = WalletPolicy( + name="Memory-intensive", + descriptor_template="wsh(or_d(multi(4,@0/<0;1>/*,@1/<0;1>/*,@2/<0;1>/*,@3/<0;1>/*),and_v(v:thresh(3,pkh(@0/<2;3>/*),a:pkh(@1/<2;3>/*),a:pkh(@2/<2;3>/*),a:pkh(@3/<2;3>/*)),older(65535))))", + keys_info=[ + internal_xpub_orig, + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + f"{core_xpub_orig3}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name1, core_wallet_name2, + core_wallet_name3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_miniscript_policy_with_a(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # versions 2.1.0 and 2.1.1 of the app incorrectly compiled the 'a:' wrapper, producing incorrect addresses + + _, core_xpub_orig1 = create_new_wallet() + _, core_xpub_orig2 = create_new_wallet() + core_wallet_name3, core_xpub_orig3 = create_new_wallet() + _, core_xpub_orig4 = create_new_wallet() + _, core_xpub_orig5 = create_new_wallet() + + path = "48'/1'/0'/2'" + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + internal_xpub_orig = f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}" + + wallet_policy = WalletPolicy( + name="Policy with a:", + descriptor_template="wsh(or_i(and_v(v:pkh(@0/**),older(65535)),or_d(multi(2,@1/**,@3/**),and_v(v:thresh(1,pkh(@4/**),a:pkh(@5/**)),older(64231)))))", + keys_info=[ + f"{core_xpub_orig1}", + internal_xpub_orig, + f"{core_xpub_orig2}", + f"{core_xpub_orig3}", + f"{core_xpub_orig4}", + f"{core_xpub_orig5}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_invalid_miniscript(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, speculos_globals: SpeculosGlobals): + path = "48'/1'/0'/2'" + _, core_xpub_orig1 = create_new_wallet() + _, core_xpub_orig2 = create_new_wallet() + _, core_xpub_orig3 = create_new_wallet() + _, core_xpub_orig4 = create_new_wallet() + _, core_xpub_orig5 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + internal_xpub_orig = f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}" + + # sh(sh(...)), wsh(sh(...)), wsh(wsh(...)) are invalid + run_test_invalid(client, "sh(sh(pkh(@0/**)))", [internal_xpub_orig]) + run_test_invalid(client, "wsh(sh(pkh(@0/**)))", [internal_xpub_orig]) + run_test_invalid(client, "wsh(wsh(pkh(@0/**)))", [internal_xpub_orig]) + + # sh(wsh(...)) is meaningful with valid miniscript, but current implementation of miniscript assumes wsh(...) + run_test_invalid(client, "sh(wsh(or_d(pk(@0/**),pkh(@1/**))))", [internal_xpub_orig, core_xpub_orig1]) + + # tr must be top-level + run_test_invalid(client, "wsh(tr(pk(@0/**)))", [internal_xpub_orig]) + run_test_invalid(client, "sh(tr(pk(@0/**)))", [internal_xpub_orig]) + + # valid miniscript must be inside wsh() + run_test_invalid(client, "or_d(pk(@0/**),pkh(@1/**))", [internal_xpub_orig, core_xpub_orig1]) + run_test_invalid(client, "sh(or_d(pk(@0/**),pkh(@1/**)))", [internal_xpub_orig, core_xpub_orig1]) + + # sortedmulti is not valid miniscript, can only be used as a descriptor inside sh or wsh + run_test_invalid(client, "wsh(or_d(pk(@0/**),sortedmulti(3,@1/**,@2/**,@3/**,@4/**,@5/**)))", + [ + internal_xpub_orig, + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + f"{core_xpub_orig3}", + f"{core_xpub_orig4}", + f"{core_xpub_orig5}", + ]) diff --git a/tests/test_e2e_multisig.py b/tests/test_e2e_multisig.py index 34d4c31b2..f0a35fddf 100644 --- a/tests/test_e2e_multisig.py +++ b/tests/test_e2e_multisig.py @@ -1,116 +1,52 @@ import pytest -from typing import Dict +from typing import List, Union + import hmac from hashlib import sha256 from decimal import Decimal -from bip32 import BIP32 - -from bitcoin_client.ledger_bitcoin import Client, MultisigWallet, AddressType -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from bitcoin_client.ledger_bitcoin._script import is_p2sh, is_p2wsh, parse_multisig +from ledger_bitcoin import Client, MultisigWallet, AddressType +from ledger_bitcoin.client_base import TransportClient +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import WalletPolicy -from test_utils import SpeculosGlobals +from test_utils import SpeculosGlobals, get_internal_xpub, count_internal_keys from speculos.client import SpeculosClient -from test_utils.speculos import automation + +from ragger_bitcoin import RaggerClient +from ragger_bitcoin.ragger_instructions import Instructions +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware + +from .instructions import e2e_register_wallet_instruction, e2e_sign_psbt_instruction from .conftest import create_new_wallet, generate_blocks, get_unique_wallet_name, get_wallet_rpc, testnet_to_regtest_addr as T +from .conftest import AuthServiceProxy -def extract_our_pubkeys(psbt: PSBT, master_fp: bytes) -> Dict[int, bytes]: - # It only works for standard wallets and simple multisig; won't generalize to miniscript - # based on code in bitcoin-core/HWI - - psbt2 = PSBT() - psbt2.deserialize(psbt.serialize()) - if (psbt2.version is None or psbt.version == 0): - psbt2.convert_to_v2() - - pubkeys: Dict[int, bytes] = {} - for input_num, psbt_in in enumerate(psbt2.inputs): - utxo = None - scriptcode = b"" - if psbt_in.witness_utxo: - utxo = psbt_in.witness_utxo - if psbt_in.non_witness_utxo: - assert psbt_in.prev_out is not None - utxo = psbt_in.non_witness_utxo.vout[psbt_in.prev_out] - if utxo is None: - continue - scriptcode = utxo.scriptPubKey - - if is_p2sh(scriptcode): - if len(psbt_in.redeem_script) == 0: - continue - scriptcode = psbt_in.redeem_script - - # Check if P2WSH - if is_p2wsh(scriptcode): - if len(psbt_in.witness_script) == 0: - continue - scriptcode = psbt_in.witness_script - - multisig = parse_multisig(scriptcode) - if multisig is not None: - _, ms_pubkeys = multisig - - our_keys = 0 - for pub in ms_pubkeys: - if pub in psbt_in.hd_keypaths: - pk_origin = psbt_in.hd_keypaths[pub] - if pk_origin.fingerprint == master_fp: - our_keys += 1 - pubkeys[input_num] = pub - - if our_keys > 1: - raise ValueError("Cannot have more than 1 internal key in a supported wallet") - else: - for key, origin in psbt_in.hd_keypaths.items(): - if origin.fingerprint == master_fp: - pubkeys[input_num] = key - - for key, (_, origin) in psbt_in.tap_bip32_paths.items(): - if key == psbt_in.tap_internal_key and origin.fingerprint == master_fp: - pubkeys[input_num] = key - return pubkeys - - -@pytest.mark.skip(reason="Fails on host side: ValueError: substring not found") -def test_e2e_multisig(rpc, rpc_test_wallet, client: Client, speculos_globals: SpeculosGlobals, is_speculos: bool, comm: SpeculosClient): - if not is_speculos: - pytest.skip("Requires speculos") - - wallet_name, core_xpub_orig = create_new_wallet() - wallet_rpc = get_wallet_rpc(wallet_name) - - bip32 = BIP32.from_seed(speculos_globals.seed, network="test") - internal_xpub = bip32.get_xpub_from_path("m/48'/1'/0'/2'") - wallet = MultisigWallet( - name="Cold storage", - address_type=AddressType.WIT, - threshold=2, - keys_info=[ - f"{core_xpub_orig}/**", - f"[{speculos_globals.master_key_fingerprint.hex()}/48'/1'/0'/2']{internal_xpub}/**", - ], - ) +def run_test(navigator: Navigator, client: RaggerClient, wallet_policy: WalletPolicy, + core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, + speculos_globals: SpeculosGlobals, instructions_register_wallet: Instructions, + instructions_sign_psbt: Instructions, test_name: str = ""): - with automation(comm, "automations/register_wallet_accept.json"): - wallet_id, wallet_hmac = client.register_wallet(wallet) + wallet_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions_register_wallet, testname=f"{test_name}_register") - assert wallet_id == wallet.id + assert wallet_id == wallet_policy.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, ) - address_hww = client.get_wallet_address(wallet, wallet_hmac, 0, 3, False) + address_hww = client.get_wallet_address( + wallet_policy, wallet_hmac, 0, 3, False) # ==> verify the address matches what bitcoin-core computes - receive_descriptor = wallet.get_descriptor(change=False) + receive_descriptor = wallet_policy.get_descriptor(change=False) receive_descriptor_info = rpc.getdescriptorinfo(receive_descriptor) # bitcoin-core adds the checksum, and requires it for other calls receive_descriptor_chk = receive_descriptor_info["descriptor"] @@ -119,7 +55,7 @@ def test_e2e_multisig(rpc, rpc_test_wallet, client: Client, speculos_globals: Sp assert T(address_hww) == address_core # also get the change descriptor for later - change_descriptor = wallet.get_descriptor(change=True) + change_descriptor = wallet_policy.get_descriptor(change=True) change_descriptor_info = rpc.getdescriptorinfo(change_descriptor) change_descriptor_chk = change_descriptor_info["descriptor"] @@ -155,9 +91,15 @@ def test_e2e_multisig(rpc, rpc_test_wallet, client: Client, speculos_globals: Sp out_address = rpc_test_wallet.getnewaddress() - result = multisig_rpc.walletcreatefundedpsbt(outputs={ - out_address: Decimal("0.01") - }) + result = multisig_rpc.walletcreatefundedpsbt( + outputs={ + out_address: Decimal("0.01") + }, + options={ + # We need a fixed position to be able to know how to navigate in the flows + "changePosition": 1 + } + ) psbt_b64 = result["psbt"] @@ -166,28 +108,122 @@ def test_e2e_multisig(rpc, rpc_test_wallet, client: Client, speculos_globals: Sp psbt = PSBT() psbt.deserialize(psbt_b64) - pubkeys = extract_our_pubkeys(psbt, speculos_globals.master_key_fingerprint) - - with automation(comm, "automations/sign_with_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, wallet_hmac) + hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac, navigator, + instructions=instructions_sign_psbt, + testname=f"{test_name}_sign") - assert len(hww_sigs) == len(pubkeys) # should be true as long as all inputs are internal + n_internal_keys = count_internal_keys( + speculos_globals.seed, "test", wallet_policy) + # should be true as long as all inputs are internal + assert len(hww_sigs) == n_internal_keys * len(psbt.inputs) - for i, sig in hww_sigs.items(): - pubkey = pubkeys[i] - psbt.inputs[i].partial_sigs[pubkey] = sig + for i, part_sig in hww_sigs: + psbt.inputs[i].partial_sigs[part_sig.pubkey] = part_sig.signature signed_psbt_hww_b64 = psbt.serialize() # ==> sign it with bitcoin-core + partial_psbts = [signed_psbt_hww_b64] - signed_psbt_core_b64 = wallet_rpc.walletprocesspsbt(psbt_b64)["psbt"] + for core_wallet_name in core_wallet_names: + partial_psbts.append(get_wallet_rpc( + core_wallet_name).walletprocesspsbt(psbt_b64)["psbt"]) # ==> finalize the psbt, extract tx and broadcast - combined_psbt = rpc.combinepsbt([signed_psbt_hww_b64, signed_psbt_core_b64]) + combined_psbt = rpc.combinepsbt(partial_psbts) result = rpc.finalizepsbt(combined_psbt) - rawtx = result["hex"] + assert result["complete"] == True + rawtx = result["hex"] # make sure the transaction is valid by broadcasting it (would fail if rejected) rpc.sendrawtransaction(rawtx) + + +def test_e2e_multisig_2_of_2(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, rpc: AuthServiceProxy, rpc_test_wallet, speculos_globals: + SpeculosGlobals): + path = "48'/1'/0'/2'" + core_wallet_name, core_xpub_orig = create_new_wallet() + + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + f"{core_xpub_orig}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + ], + ) + + run_test(navigator, client, wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, + speculos_globals, e2e_register_wallet_instruction( + firmware, wallet_policy.n_keys), + e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_multisig_multiple_internal_keys(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc: AuthServiceProxy, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # test an edge case of a multisig where the wallet controls more than one key + # 3-of-5 multisig where 2 keys are internal + + path_1 = "48'/1'/0'/2'" + internal_xpub_1 = get_internal_xpub(speculos_globals.seed, path_1) + path_2 = "48'/1'/1'/2'" + internal_xpub_2 = get_internal_xpub(speculos_globals.seed, path_2) + + _, core_xpub_orig_1 = create_new_wallet() + _, core_xpub_orig_2 = create_new_wallet() + core_wallet_name_3, core_xpub_orig_3 = create_new_wallet() + + wallet_policy = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=3, + keys_info=[ + f"[{speculos_globals.master_key_fingerprint.hex()}/{path_1}]{internal_xpub_1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path_2}]{internal_xpub_2}", + f"{core_xpub_orig_1}", + f"{core_xpub_orig_2}", + f"{core_xpub_orig_3}", + ], + ) + + run_test(navigator, client, wallet_policy, [core_wallet_name_3], + rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), + e2e_sign_psbt_instruction(firmware), test_name) + + +@pytest.mark.timeout(0) # disable timeout +def test_e2e_multisig_16_of_16(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, rpc: AuthServiceProxy, rpc_test_wallet, speculos_globals: SpeculosGlobals, enable_slow_tests: bool): + # Largest supported multisig with sortedmulti. + # The time for an end-to-end execution on a real Ledger Nano S (including user's input) is about 520 seconds. + + # slow test, disabled by default + if not enable_slow_tests: + pytest.skip() + + core_wallet_names: List[str] = [] + core_xpub_origs: List[str] = [] + for _ in range(15): + name, xpub_orig = create_new_wallet() + core_wallet_names.append(name) + core_xpub_origs.append(xpub_orig) + + path = "48'/1'/0'/2'" + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + + wallet_policy = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + sorted=True, + keys_info=core_xpub_origs + + [f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}"], + ) + + run_test(navigator, client, wallet_policy, core_wallet_names, rpc, rpc_test_wallet, + speculos_globals. e2e_register_wallet_instruction( + firmware, wallet_policy.n_keys), + e2e_sign_psbt_instruction(firmware), test_name) diff --git a/tests/test_e2e_tapscripts.py b/tests/test_e2e_tapscripts.py new file mode 100644 index 000000000..0d20f8cba --- /dev/null +++ b/tests/test_e2e_tapscripts.py @@ -0,0 +1,425 @@ +import pytest + +from typing import List, Union + +import hmac +from hashlib import sha256 +from decimal import Decimal + +from ledger_bitcoin import Client +from ledger_bitcoin.client_base import TransportClient +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import WalletPolicy + +from test_utils import SpeculosGlobals, get_internal_xpub, count_internal_keys + +from ragger_bitcoin import RaggerClient +from ragger_bitcoin.ragger_instructions import Instructions +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU + +from .instructions import e2e_register_wallet_instruction, e2e_sign_psbt_instruction +from .conftest import AuthServiceProxy +from .conftest import create_new_wallet, generate_blocks, get_unique_wallet_name, get_wallet_rpc, testnet_to_regtest_addr as T + + +def run_test_e2e(navigator: Navigator, client: RaggerClient, wallet_policy: WalletPolicy, + core_wallet_names: List[str], rpc: AuthServiceProxy, rpc_test_wallet: AuthServiceProxy, + speculos_globals: SpeculosGlobals, instructions_register_wallet: Instructions, + instructions_sign_psbt: Instructions, test_name: str = ""): + + wallet_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions_register_wallet, testname=f"{test_name}_register") + assert wallet_id == wallet_policy.id + + assert hmac.compare_digest( + hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), + wallet_hmac, + ) + + address_hww = client.get_wallet_address(wallet_policy, wallet_hmac, 0, 3, False) + + # ==> verify the address matches what bitcoin-core computes + receive_descriptor = wallet_policy.get_descriptor(change=False) + receive_descriptor_info = rpc.getdescriptorinfo(receive_descriptor) + # bitcoin-core adds the checksum, and requires it for other calls + receive_descriptor_chk = receive_descriptor_info["descriptor"] + address_core = rpc.deriveaddresses(receive_descriptor_chk, [3, 3])[0] + + assert T(address_hww) == address_core + + # also get the change descriptor for later + change_descriptor = wallet_policy.get_descriptor(change=True) + change_descriptor_info = rpc.getdescriptorinfo(change_descriptor) + change_descriptor_chk = change_descriptor_info["descriptor"] + + # ==> import wallet in bitcoin-core + + multisig_wallet_name = get_unique_wallet_name() + rpc.createwallet( + wallet_name=multisig_wallet_name, + disable_private_keys=True, + descriptors=True, + ) + multisig_rpc = get_wallet_rpc(multisig_wallet_name) + multisig_rpc.importdescriptors([{ + "desc": receive_descriptor_chk, + "active": True, + "internal": False, + "timestamp": "now" + }, { + "desc": change_descriptor_chk, + "active": True, + "internal": True, + "timestamp": "now" + }]) + + # ==> fund the multisig wallet and get prevout info + + rpc_test_wallet.sendtoaddress(T(address_hww), "0.1") + generate_blocks(1) + + assert multisig_rpc.getwalletinfo()["balance"] == Decimal("0.1") + + # ==> prepare a psbt spending from the wallet + + out_address = rpc_test_wallet.getnewaddress() + + result = multisig_rpc.walletcreatefundedpsbt( + outputs={ + out_address: Decimal("0.01") + }, + options={ + "changePosition": 1 # We need a fixed position to be able to know how to navigate in the flows + } + ) + + psbt_b64 = result["psbt"] + + # ==> sign it with the hww + + psbt = PSBT() + psbt.deserialize(psbt_b64) + + hww_sigs = client.sign_psbt(psbt, wallet_policy, wallet_hmac, navigator, + instructions=instructions_sign_psbt, + testname=f"{test_name}_sign") + + # only correct for taproot policies + for i, part_sig in hww_sigs: + if part_sig.tapleaf_hash is not None: + # signature for a script spend + psbt.inputs[i].tap_script_sigs[(part_sig.pubkey, part_sig.tapleaf_hash)] = part_sig.signature + else: + # key path spend + psbt.inputs[i].tap_key_sig = part_sig.signature + + signed_psbt_hww_b64 = psbt.serialize() + + n_internal_keys = count_internal_keys(speculos_globals.seed, "test", wallet_policy) + assert len(hww_sigs) == n_internal_keys * len(psbt.inputs) # should be true as long as all inputs are internal + + # ==> sign it with bitcoin-core + + partial_psbts = [signed_psbt_hww_b64] + for core_wallet_name in core_wallet_names: + partial_psbt_response = get_wallet_rpc(core_wallet_name).walletprocesspsbt(psbt_b64) + partial_psbts.append(partial_psbt_response["psbt"]) + + # ==> finalize the psbt, extract tx and broadcast + combined_psbt = rpc.combinepsbt(partial_psbts) + result = rpc.finalizepsbt(combined_psbt) + + assert result["complete"] == True + rawtx = result["hex"] + + # make sure the transaction is valid by broadcasting it (would fail if rejected) + rpc.sendrawtransaction(rawtx) + + +def run_test_invalid(client: RaggerClient, descriptor_template: str, keys_info: List[str]): + wallet_policy = WalletPolicy( + name="Invalid wallet", + descriptor_template=descriptor_template, + keys_info=keys_info) + + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet_policy) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError or DeviceException.exc.get( + e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + +def test_e2e_tapscript_one_of_two_keypath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, + speculos_globals: SpeculosGlobals): + # One of two keys, with the foreign key in the key path spend + # tr(my_key,pk(foreign_key_1)) + + path = "499'/1'/0'" + _, core_xpub_orig = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Tapscript 1-of-2", + descriptor_template="tr(@0/**,pk(@1/**))", + keys_info=[ + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapscript_one_of_two_scriptpath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # One of two keys, with the foreign key in the key path spend + # tr(foreign_key,pk(my_key)) + + path = "499'/1'/0'" + _, core_xpub_orig = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Tapscript 1-of-2", + descriptor_template="tr(@0/**,pk(@1/**))", + keys_info=[ + f"{core_xpub_orig}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapscript_one_of_three_keypath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # One of three keys, with the internal one in the key-path spend + # tr(my_key,{pk(foreign_key_1,foreign_key_2)}) + + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + _, core_xpub_orig_2 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Tapscript 1-of-3", + descriptor_template="tr(@0/**,{pk(@1/**),pk(@2/**)})", + keys_info=[ + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_1}", + f"{core_xpub_orig_2}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapscript_one_of_three_scriptpath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # One of three keys, with the internal one in on of the scripts + # tr(foreign_key_1,{pk(my_key,foreign_key_2)}) + + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + _, core_xpub_orig_2 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Tapscript 1-of-3", + descriptor_template="tr(@0/**,{pk(@1/**),pk(@2/**)})", + keys_info=[ + f"{core_xpub_orig_1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_2}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapscript_multi_a_2of2(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # tr(foreign_key_1,multi_a(2,my_key,foreign_key_2)) + + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + core_wallet_name2, core_xpub_orig_2 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Tapscript 1 or 2-of-2", + descriptor_template="tr(@0/**,multi_a(2,@1/**,@2/**))", + keys_info=[ + f"{core_xpub_orig_1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_2}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name2], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapscript_maxdepth(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # A taproot tree with maximum supported depth, where the internal key is in the deepest script + + MAX_TAPTREE_POLICY_DEPTH = 4 if firmware.name == "nanos" else 9 + + # Make the most unbalanced tree where each script is a simple pk() + parts = [f"pk(@{i}/**)" for i in range(1, MAX_TAPTREE_POLICY_DEPTH)] + descriptor_template = "tr(@0/**,{" + ',{'.join(parts) + \ + f",pk(@{MAX_TAPTREE_POLICY_DEPTH}/**)" + "}" * (MAX_TAPTREE_POLICY_DEPTH - 1) + ")" + + keys_info = [] + for _ in range(MAX_TAPTREE_POLICY_DEPTH): + _, core_xpub_orig = create_new_wallet() + keys_info.append(core_xpub_orig) + + # the last (deepest) script is the only one we sign with the ledger key + path = "499'/1'/0'" + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + keys_info.append(f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}") + + wallet_policy = WalletPolicy( + name="Tapscriptception", + descriptor_template=descriptor_template, + keys_info=keys_info) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapscript_large(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, rpc, rpc_test_wallet, speculos_globals: + SpeculosGlobals): + # A quite large tapscript with 8 tapleaves and 22 keys in total. + + # Takes more memory than Nano S can handle + if (firmware.name == "nanos"): + pytest.skip("Not supported on Nano S due to memory limitations") + + keys_info = [] + + core_wallet_name = None + for i in range(21): + core_wallet_name_i, core_xpub_orig = create_new_wallet() + if i == 9: + # sign with bitcoin-core using the ninth external key (it will be key @10 in the policy) + core_wallet_name = core_wallet_name_i + keys_info.append(core_xpub_orig) + + path = "499'/1'/0'" + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + + # the internal key is key @9, in a 2-of-4 multisig + keys_info.insert(9, f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}") + + wallet_policy = WalletPolicy( + name="Tapzilla", + descriptor_template="tr(@0/**,{{{sortedmulti_a(1,@1/**,@2/**,@3/**,@4/**,@5/**),multi_a(2,@6/**,@7/**,@8/**)},{multi_a(2,@9/**,@10/**,@11/**,@12/**),pk(@13/**)}},{{multi_a(2,@14/**,@15/**),multi_a(3,@16/**,@17/**,@18/**)},{multi_a(2,@19/**,@20/**),pk(@21/**)}}})", + keys_info=keys_info) + + run_test_e2e(navigator, client, wallet_policy, [core_wallet_name], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapminiscript_keypath_or_decaying_3of3(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # The key path is external + # The only script path is a decaying 3-of-3 that becomes a 2-of-3 after the timelock. + # Only one internal key in the script path. + + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + core_name_2, core_xpub_orig_2 = create_new_wallet() + core_name_3, core_xpub_orig_3 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Internal or decaying 3-of-3", + descriptor_template="tr(@0/**,thresh(3,pk(@1/**),s:pk(@2/**),s:pk(@3/**),sln:older(12960)))", + keys_info=[ + f"{core_xpub_orig_1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_2}", + f"{core_xpub_orig_3}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_name_2, core_name_3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapminiscript_with_hash256(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # a taptree containing a hash challenge in a script path (but we're signing for the other script path) + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + _, core_xpub_orig_2 = create_new_wallet() + core_name_3, core_xpub_orig_3 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Hash challenge", + descriptor_template="tr(@0/**,{and_v(v:pk(@1/**),hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588)),multi_a(2,@2/**,@3/**)})", + keys_info=[ + f"{core_xpub_orig_1}", + f"{core_xpub_orig_2}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_3}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [core_name_3], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_e2e_tapminiscript_mixed_leaves(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, rpc, rpc_test_wallet, speculos_globals: SpeculosGlobals): + # A leaf has miniscript, a leaf has sortedmulti_a (which is not miniscript) + + path = "499'/1'/0'" + _, core_xpub_orig_1 = create_new_wallet() + _, core_xpub_orig_2 = create_new_wallet() + _, core_xpub_orig_3 = create_new_wallet() + _, core_xpub_orig_4 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + wallet_policy = WalletPolicy( + name="Mixed tapminiscript and not", + descriptor_template="tr(@0/**,{sortedmulti_a(1,@1/**,@2/**),or_b(pk(@3/**),s:pk(@4/**))})", + keys_info=[ + f"{core_xpub_orig_1}", + f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}", + f"{core_xpub_orig_2}", + f"{core_xpub_orig_3}", + f"{core_xpub_orig_4}", + ]) + + run_test_e2e(navigator, client, wallet_policy, [], rpc, rpc_test_wallet, speculos_globals, + e2e_register_wallet_instruction(firmware, wallet_policy.n_keys), e2e_sign_psbt_instruction(firmware), test_name) + + +def test_invalid_tapminiscript(navigator: Navigator, firmware: Firmware, client: RaggerClient, + test_name: str, speculos_globals: SpeculosGlobals): + path = "48'/1'/0'/2'" + _, core_xpub_orig1 = create_new_wallet() + _, core_xpub_orig2 = create_new_wallet() + _, core_xpub_orig3 = create_new_wallet() + internal_xpub = get_internal_xpub(speculos_globals.seed, path) + internal_xpub_orig = f"[{speculos_globals.master_key_fingerprint.hex()}/{path}]{internal_xpub}" + + # can't have scripts in the key path + run_test_invalid(client, "tr(pk(@0/**))", [internal_xpub_orig]) + run_test_invalid(client, "tr(pk(@0/**),pk(@1/**))", [internal_xpub_orig, core_xpub_orig1]) + + # test scripts that are invalid inside taproot trees + run_test_invalid(client, "tr(@0,sh(pk(@1/**)))", [internal_xpub_orig, core_xpub_orig1]) + run_test_invalid(client, "tr(@0,wsh(pk(@1/**)))", [internal_xpub_orig, core_xpub_orig1]) + run_test_invalid(client, "tr(@0,multi(1,@1/**,@2/**))", [internal_xpub_orig, core_xpub_orig1, core_xpub_orig2]) + run_test_invalid(client, "tr(@0,sortedmulti(1,@1/**,@2/**))", + [internal_xpub_orig, core_xpub_orig1, core_xpub_orig2]) + + # sortedmulti_a is not valid tapminiscript (but it's valid as a tapscript) + run_test_invalid(client, "tr(@0,or_d(pk(@1/**),sortedmulti_a(2,@2/**,@3/**)))", + [ + internal_xpub_orig, + f"{core_xpub_orig1}", + f"{core_xpub_orig2}", + f"{core_xpub_orig3}", + ]) diff --git a/tests/test_get_extended_pubkey.py b/tests/test_get_extended_pubkey.py index acd6b025e..7bb8c00c8 100644 --- a/tests/test_get_extended_pubkey.py +++ b/tests/test_get_extended_pubkey.py @@ -1,13 +1,17 @@ -import threading - import pytest -from bitcoin_client.ledger_bitcoin import Client -from bitcoin_client.ledger_bitcoin.exception import DenyError, NotSupportedError -from speculos.client import SpeculosClient +from ragger_bitcoin import RaggerClient +from ragger.navigator import Navigator +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU + +from ledger_bitcoin.exception.errors import NotSupportedError, DenyError +from ledger_bitcoin.exception.device_exception import DeviceException +from .instructions import pubkey_instruction_approve, pubkey_instruction_reject_early, pubkey_reject -def test_get_extended_pubkey_standard_nodisplay(client: Client): +def test_get_extended_pubkey_standard_display(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): testcases = { "m/44'/1'/0'": "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", "m/44'/1'/10'": "tpubDCwYjpDhUdPGp21gSpVay2QPJVh6WNySWMXPhbcu1DsxH31dF7mY18oibbu5RxCLBc1Szerjscuc3D5HyvfYqfRvc9mesewnFqGmPjney4d", @@ -18,6 +22,32 @@ def test_get_extended_pubkey_standard_nodisplay(client: Client): "m/86'/1'/4'/1/12": "tpubDHTZ815MvTaRmo6Qg1rnU6TEU4ZkWyA56jA1UgpmMcBGomnSsyo34EZLoctzZY9MTJ6j7bhccceUeXZZLxZj5vgkVMYfcZ7DNPsyRdFpS3f", } + for path, pubkey in testcases.items(): + assert pubkey == client.get_extended_pubkey( + path=path, + display=True, + navigator=navigator, + instructions=pubkey_instruction_approve(firmware), + testname=f"{test_name}_{path}" + ) + + +def test_get_extended_pubkey_standard_nodisplay(client: RaggerClient): + testcases = { + "m/44'/1'/0'": "tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", + "m/44'/1'/10'": "tpubDCwYjpDhUdPGp21gSpVay2QPJVh6WNySWMXPhbcu1DsxH31dF7mY18oibbu5RxCLBc1Szerjscuc3D5HyvfYqfRvc9mesewnFqGmPjney4d", + "m/44'/1'/2'/1/42": "tpubDGF9YgHKv6qh777rcqVhpmDrbNzgophJM9ec7nHiSfrbss7fVBXoqhmZfohmJSvhNakDHAspPHjVVNL657tLbmTXvSeGev2vj5kzjMaeupT", + "m/48'/1'/4'/1'/0/7": "tpubDK8WPFx4WJo1R9mEL7Wq325wBiXvkAe8ipgb9Q1QBDTDUD2YeCfutWtzY88NPokZqJyRPKHLGwTNLT7jBG59aC6VH8q47LDGQitPB6tX2d7", + "m/49'/1'/1'/1/3": "tpubDGnetmJDCL18TyaaoyRAYbkSE9wbHktSdTS4mfsR6inC8c2r6TjdBt3wkqEQhHYPtXpa46xpxDaCXU2PRNUGVvDzAHPG6hHRavYbwAGfnFr", + "m/84'/1'/2'/0/10": "tpubDG9YpSUwScWJBBSrhnAT47NcT4NZGLcY18cpkaiWHnkUCi19EtCh8Heeox268NaFF6o56nVeSXuTyK6jpzTvV1h68Kr3edA8AZp27MiLUNt", + "m/86'/1'/4'/1/12": "tpubDHTZ815MvTaRmo6Qg1rnU6TEU4ZkWyA56jA1UgpmMcBGomnSsyo34EZLoctzZY9MTJ6j7bhccceUeXZZLxZj5vgkVMYfcZ7DNPsyRdFpS3f", + # support up to 8 steps + "m/86'/1'/4'/1/2/3/4/5": "tpubDNcjumrTe1BBYEc1FmMaJZQw47mbvb4LfX4YwqC6GQ18PfMfuH3BEYREfdHm2gWXkSJ3JiXHF11iKnbJxzxp5qkgo8BBy2L48FRvrLhpTuh", + # the following two paths test compatibility with Unchained Capital's multisig setup + "m/45'/1'/0'": "tpubDCy2BKyxJFzACNgThkunvdnkHNotREK9LQDw8L9J1gx26SyzfoeJynJgWekzkramggmahVAgeHPxfpnvFtJ7hcYADrsVUnsPSei2tY9fBLL", + "m/45'/1'/0'/1": "tpubDFGDxRGdGFKekUtPuta4p9Kw2a2PSeyyhSTa7KNENJfBuJ78EEsL1LxwAA8ddSxZFWBT9gYRuLDoa2rwdix56WRsq77vAJ2iqeyPw6UBeyt", + } + for path, pubkey in testcases.items(): assert pubkey == client.get_extended_pubkey( path=path, @@ -25,7 +55,7 @@ def test_get_extended_pubkey_standard_nodisplay(client: Client): ) -def test_get_extended_pubkey_nonstandard_nodisplay(client: Client): +def test_get_extended_pubkey_nonstandard_nodisplay(client: RaggerClient): # as these paths are not standard, the app should reject immediately if display=False testcases = [ "m", # unusual to export the root key @@ -38,108 +68,64 @@ def test_get_extended_pubkey_nonstandard_nodisplay(client: Client): "m/48'/1'/0'/0'", # script_type is 1' or 2' for BIP-0048 "m/48'/1'/0'/3'", # script_type is 1' or 2' for BIP-0048 "m/999'/1'/0'", # no standard with this purpose - "m/44'/1'/10'/0", # missing address_index - "m/44'/1'/10'/2/3", # change bigger than 1 - "m/44'/1'/10'/0/3/5", # no derivation steps expected after address_index ] for path in testcases: - with pytest.raises(NotSupportedError): + with pytest.raises(ExceptionRAPDU) as e: client.get_extended_pubkey( path=path, display=False ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + -@pytest.mark.skip(reason="Crashes Bitcoin Testnet app when run with other tests") -def test_get_extended_pubkey_non_standard(client: Client, comm: SpeculosClient, is_speculos: bool): +def test_get_extended_pubkey_non_standard(navigator: Navigator, firmware: Firmware, client: + RaggerClient, + test_name: str): # Test the successful UX flow for a non-standard path (here, root path) # (Slow test, not feasible to repeat it for many paths) - if not is_speculos: - pytest.skip("Requires speculos") - - def ux_thread(): - event = comm.wait_for_text_event("path is unusual") - - # press right until the last screen (will press the "right" button more times than needed) - while "Reject" != event["text"]: - comm.press_and_release("right") - - event = comm.get_next_event() - - # go back to the Accept screen, then accept - comm.press_and_release("left") - comm.press_and_release("both") - - x = threading.Thread(target=ux_thread) - x.start() - pub_key = client.get_extended_pubkey( path="m", # root pubkey - display=True + display=True, + navigator=navigator, + instructions=pubkey_instruction_approve(firmware), + testname=test_name ) - x.join() - assert pub_key == "tpubD6NzVbkrYhZ4YgUx2ZLNt2rLYAMTdYysCRzKoLu2BeSHKvzqPaBDvf17GeBPnExUVPkuBpx4kniP964e2MxyzzazcXLptxLXModSVCVEV1T" -def test_get_extended_pubkey_non_standard_reject_early(client: Client, comm: SpeculosClient, is_speculos: bool): +def test_get_extended_pubkey_non_standard_reject_early(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): # Test rejecting after the "Reject if you're not sure" warning # (Slow test, not feasible to repeat it for many paths) - if not is_speculos: - pytest.skip("Requires speculos") - - def ux_thread(): - comm.wait_for_text_event("path is unusual") - comm.press_and_release("right") - comm.wait_for_text_event("Confirm public key") - comm.press_and_release("right") - comm.wait_for_text_event("111'/222'/333'") - comm.press_and_release("right") - comm.wait_for_text_event("not sure") # second line of "Reject if you're not sure" - comm.press_and_release("both") - - x = threading.Thread(target=ux_thread) - x.start() - - with pytest.raises(DenyError): + with pytest.raises(ExceptionRAPDU) as e: client.get_extended_pubkey( path="m/111'/222'/333'", - display=True + display=True, + navigator=navigator, + instructions=pubkey_instruction_reject_early(firmware), + testname=test_name ) - - x.join() + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 -@pytest.mark.skip(reason="Fails (does not raise) when run with other tests") -def test_get_extended_pubkey_non_standard_reject(client: Client, comm: SpeculosClient, is_speculos: bool): +def test_get_extended_pubkey_non_standard_reject(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # Test rejecting at the end # (Slow test, not feasible to repeat it for many paths) - if not is_speculos: - pytest.skip("Requires speculos") - - def ux_thread(): - event = comm.wait_for_text_event("path is unusual") - - # press right until the last screen (will press the "right" button more times than needed) - while "Reject" != event["text"]: - comm.press_and_release("right") - - event = comm.get_next_event() - - # finally, reject - comm.press_and_release("both") - - x = threading.Thread(target=ux_thread) - x.start() - - with pytest.raises(DenyError): + with pytest.raises(ExceptionRAPDU) as e: client.get_extended_pubkey( path="m/111'/222'/333'", - display=True + display=True, + navigator=navigator, + instructions=pubkey_reject(firmware), + testname=test_name, ) - - x.join() + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 diff --git a/tests/test_get_master_fingerprint.py b/tests/test_get_master_fingerprint.py index 44c55bad3..d909bc1ab 100644 --- a/tests/test_get_master_fingerprint.py +++ b/tests/test_get_master_fingerprint.py @@ -1,6 +1,6 @@ -from bitcoin_client.ledger_bitcoin import Client +from ragger_bitcoin import RaggerClient from .conftest import SpeculosGlobals -def test_get_master_fingerprint(client: Client, speculos_globals: SpeculosGlobals): +def test_get_master_fingerprint(client: RaggerClient, speculos_globals: SpeculosGlobals): assert client.get_master_fingerprint() == speculos_globals.master_key_fingerprint diff --git a/tests/test_get_version.py b/tests/test_get_version.py index 9cfac6a7f..1876c4971 100644 --- a/tests/test_get_version.py +++ b/tests/test_get_version.py @@ -1,7 +1,7 @@ -from bitcoin_client.ledger_bitcoin import Client +from ragger_bitcoin import RaggerClient -def test_get_version(client: Client, app_version: str): +def test_get_version(client: RaggerClient, app_version: str): returned_app_name, returned_app_version, returned_app_flags = client.get_version() assert returned_app_version == app_version, "App version in Makefile did not match the one returned by the app" diff --git a/tests/test_get_wallet_address.py b/tests/test_get_wallet_address.py index a769b4673..199b2a82e 100644 --- a/tests/test_get_wallet_address.py +++ b/tests/test_get_wallet_address.py @@ -1,59 +1,69 @@ -from bitcoin_client.ledger_bitcoin import Client, AddressType, MultisigWallet, PolicyMapWallet -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError +from hashlib import sha256 +import hmac +import re +from ledger_bitcoin import Client, AddressType, MultisigWallet, WalletPolicy +from ledger_bitcoin.exception.errors import IncorrectDataError +from ledger_bitcoin.exception.device_exception import DeviceException +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient + +from .conftest import testnet_to_regtest_addr as T import pytest +from test_utils import SpeculosGlobals + # TODO: add tests with UI -def test_get_wallet_address_singlesig_legacy(client: Client): +def test_get_wallet_address_singlesig_legacy(client: RaggerClient): # legacy address (P2PKH) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[ - f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ) assert client.get_wallet_address(wallet, None, 0, 0, False) == "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm" assert client.get_wallet_address(wallet, None, 1, 15, False) == "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT" -def test_get_wallet_address_singlesig_wit(client: Client): +def test_get_wallet_address_singlesig_wit(client: RaggerClient): # bech32 address (P2WPKH) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="wpkh(@0)", + descriptor_template="wpkh(@0/**)", keys_info=[ - f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", + f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P", ], ) assert client.get_wallet_address(wallet, None, 0, 0, False) == "tb1qzdr7s2sr0dwmkwx033r4nujzk86u0cy6fmzfjk" assert client.get_wallet_address(wallet, None, 1, 15, False) == "tb1qlrvzyx8jcjfj2xuy69du9trtxnsvjuped7e289" -def test_get_wallet_address_singlesig_sh_wit(client: Client): +def test_get_wallet_address_singlesig_sh_wit(client: RaggerClient): # wrapped segwit addresses (P2SH-P2WPKH) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="sh(wpkh(@0))", + descriptor_template="sh(wpkh(@0/**))", keys_info=[ - f"[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**", + f"[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3", ], ) assert client.get_wallet_address(wallet, None, 0, 0, False) == "2MyHkbusvLomaarGYMqyq7q9pSBYJRwWcsw" assert client.get_wallet_address(wallet, None, 1, 15, False) == "2NAbM4FSeBQG4o85kbXw2YNfKypcnEZS9MR" -def test_get_wallet_address_singlesig_taproot(client: Client): +def test_get_wallet_address_singlesig_taproot(client: RaggerClient): # test for a native taproot wallet (bech32m addresses, per BIP-0086) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="tr(@0)", + descriptor_template="tr(@0/**)", keys_info=[ - f"[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**", + f"[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U", ], ) @@ -72,71 +82,131 @@ def test_get_wallet_address_singlesig_taproot(client: Client): # Failure cases for default wallets -def test_get_wallet_address_default_fail_wrongkeys(client: Client): +def test_get_wallet_address_fail_nonstandard(client: RaggerClient): + # Not empty name should be rejected + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="Not empty", + descriptor_template="pkh(@0/**)", + keys_info=[ + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", + ], + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + # 0 keys info should be rejected - with pytest.raises(IncorrectDataError): - client.get_wallet_address(PolicyMapWallet( + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # more than 1 key should be rejected - with pytest.raises(IncorrectDataError): - client.get_wallet_address(PolicyMapWallet( + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[ - f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", - f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**" + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT" ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # wrong BIP44 purpose should be rejected (here using 84' for a P2PKH address) - with pytest.raises(IncorrectDataError): - client.get_wallet_address(PolicyMapWallet( + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[ - f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", + f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 - # mismatching pubkey (claiming key origin "44'/1'/0'", but that's the extended dpubkey for "84'/1'/0'"") - with pytest.raises(IncorrectDataError): - client.get_wallet_address(PolicyMapWallet( + # mismatching pubkey (claiming key origin "44'/1'/0'", but that's the extended pubkey for "84'/1'/0'"") + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[ - f"[f5acc2fd/44'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", + f"[f5acc2fd/44'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # wrong master fingerprint - with pytest.raises(IncorrectDataError): - client.get_wallet_address(PolicyMapWallet( + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[ - f"[42424242/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + f"[42424242/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # too large address_index, cannot be done non-silently - with pytest.raises(IncorrectDataError): - client.get_wallet_address(PolicyMapWallet( + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( name="", - policy_map="pkh(@0)", + descriptor_template="pkh(@0/**)", keys_info=[ - f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", ], ), None, 0, 100000, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # missing key origin info + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0/**)", + keys_info=[ + f"tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", + ], + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # non-standard final derivation steps + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0/<0;2>/*)", + keys_info=[ + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", + ], + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # taproot single-sig with non-empty script + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="tr(@0,0)", + keys_info=[ + f"[f5acc2fd/86'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", + ], + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 # Multisig -def test_get_wallet_address_multisig_legacy(client: Client): +def test_get_wallet_address_multisig_legacy(client: RaggerClient): # test for a legacy p2sh multisig wallet wallet = MultisigWallet( @@ -144,19 +214,19 @@ def test_get_wallet_address_multisig_legacy(client: Client): address_type=AddressType.LEGACY, threshold=2, keys_info=[ - f"[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW/**", - f"[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm/**", + f"[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW", + f"[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm", ], ) wallet_hmac = bytes.fromhex( - "1980a07cde99fbdec0d487671d3bb296507e47b3ddfa778600a9d73d501983bc" + "fa73e36119324fbe4cc1ca94aa842c6261526d44112a22164bc57c3335102b04" ) res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) assert res == "2Mx69MjHC4ViZAH1koVXPvVgaazbBCdr89j" -def test_get_wallet_address_multisig_sh_wit(client: Client): +def test_get_wallet_address_multisig_sh_wit(client: RaggerClient): # test for a wrapped segwit multisig wallet wallet = MultisigWallet( @@ -164,19 +234,19 @@ def test_get_wallet_address_multisig_sh_wit(client: Client): address_type=AddressType.SH_WIT, threshold=2, keys_info=[ - f"[76223a6e/48'/1'/0'/1']tpubDE7NQymr4AFtcJXi9TaWZtrhAdy8QyKmT4U6b9qYByAxCzoyMJ8zw5d8xVLVpbTRAEqP8pVUxjLE2vDt1rSFjaiS8DSz1QcNZ8D1qxUMx1g/**", - f"[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY/**", + f"[76223a6e/48'/1'/0'/1']tpubDE7NQymr4AFtcJXi9TaWZtrhAdy8QyKmT4U6b9qYByAxCzoyMJ8zw5d8xVLVpbTRAEqP8pVUxjLE2vDt1rSFjaiS8DSz1QcNZ8D1qxUMx1g", + f"[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY", ], ) wallet_hmac = bytes.fromhex( - "ff96c09cfacf89f836ded409b7315b9d7f242db8033e4de4db1cb4c275153988" + "1f498e7444841b883c4a63e2b88a5cad297c289d235794f8e3e17cf559ed0654" ) res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) assert res == "2MxAUTJh27foYtyp9dcSxP7RgaSwkkVCHTU" -def test_get_wallet_address_multisig_wit(client: Client): +def test_get_wallet_address_multisig_wit(client: RaggerClient): # test for a native segwit multisig wallet (bech32 address) wallet = MultisigWallet( @@ -184,13 +254,180 @@ def test_get_wallet_address_multisig_wit(client: Client): address_type=AddressType.WIT, threshold=2, keys_info=[ - f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], ) wallet_hmac = bytes.fromhex( - "d6434852fb3caa7edbd1165084968f1691444b3cfc10cf1e431acbbc7f48451f" + "d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb371" ) res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) assert res == "tb1qmyauyzn08cduzdqweexgna2spwd0rndj55fsrkefry2cpuyt4cpsn2pg28" + + +def test_get_wallet_address_tr_script_pk(client: RaggerClient): + wallet = WalletPolicy( + name="Taproot foreign internal key, and our script key", + descriptor_template="tr(@0/**,pk(@1/**))", + keys_info=[ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ) + + wallet_hmac = bytes.fromhex( + "dae925660e20859ed8833025d46444483ce264fdb77e34569aabe9d590da8fb7" + ) + + res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) + assert res == "tb1pls9pp5cgcljpkjauxep03lv2c2yc2wcuua26p3ks6j2lq0vl9kjqf5rgm2" + + +def test_get_wallet_address_tr_script_sortedmulti(client: RaggerClient): + wallet = WalletPolicy( + name="Taproot single-key or multisig 2-of-2", + descriptor_template="tr(@0/**,sortedmulti_a(2,@1/**,@2/**))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ) + + wallet_hmac = bytes.fromhex( + "a3f31e9d7b70d1d967413488bae136a8b6c7afd1de0524deb6cf74f5c509b9ab" + ) + + res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) + assert res == "tb1pdzk72dnvz3246474p4m5a97u43h6ykt2qcjrrhk6y0fkg8hx2mvswwgvv7" + + +def test_get_wallet_address_large_addr_index(client: RaggerClient): + # 2**31 - 1 is the largest index allowed, per BIP-32 + + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ) + wallet_hmac = bytes.fromhex( + "d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb371" + ) + + client.get_wallet_address(wallet, wallet_hmac, 0, 2**31 - 1, False) + + # too large address_index, not allowed for an unhardened step + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(wallet, wallet_hmac, 0, 2**31, False) + + +def test_get_wallet_address_miniscript_all_fragments(client: Client, speculos_globals: SpeculosGlobals, rpc): + # Create some miniscripts to exercise all possible fragments at least once, + # by comparing with the addresses generated by bitcoin-core. + + # arbitrary 20-bytes and 32-bytes hex strings + H20 = bytes(list(range(20))).hex() + H32 = bytes(list(range(32))).hex() + fragments_common = [ + "or_d(pk(@0/**),0)", # 0, or_d and pk + "1", # 1 + "c:pk_k(@0/**)", # pk_k and c: + "c:pk_h(@0/**)", # pk_h + "pkh(@0/**)", # pkh + "older(42)", # older + "after(42)", # after + f"sha256({H32})", # sha256 + f"ripemd160({H20})", # ripemd160 + f"hash256({H32})", # hash256 + f"hash160({H20})", # hash160 + "andor(pk(@0/**),older(42),pk(@1/**))", # andor + "and_v(v:pk(@0/**),pk(@1/**))", # and_v and v: + "and_b(pk(@0/**),a:pk(@1/**))", # and_b and a: + "or_b(pk(@0/**),a:pk(@1/**))", # or_b + "t:or_c(pk(@0/**),v:pk(@1/**))", # or_c and t: + # or_d is covered + "or_i(pk(@0/**),pk(@1/**))", # or_i + "thresh(1,pk(@0/**),a:pk(@1/**))", # thresh + "thresh(1,pk(@0/**))", # thresh(1,X) + + # WRAPPERS not covered above + # a: is covered + "and_b(1,s:pk(@0/**))", # s: + # c: is covered + "dv:older(42)", # d: + # t: is covered + # v: is covered + "j:pk(@0/**)", # j: + "n:pk(@0/**)", # n: + "l:pk(@0/**)", # l: + "u:pk(@0/**)", # u: + ] + + fragments_wsh = [ + *fragments_common, + "multi(2,@0/**,@1/**,@2/**)", # multi + "multi(1,@0/**)", # multi(1,X) + ] + + fragments_tr = [ + *fragments_common, + "multi_a(2,@0/**,@1/**,@2/**)", # multi_a + "multi_a(1,@0/**)", # multi_a(1,X) + ] + + def prepend_a(frag): + # prepends the a: wrapper (taking into account that `frag` could already start with wrappers) + if re.match("^[a-z]+:", frag): + return "a" + frag + else: + return "a:" + frag + + test_keys = [ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + 'tpubDDcmHJ6bsQqSRDzXrF1cgyPfXpFTHmqBUcq5cevfszh83XJtjqXZXDYwP3N82bA51dBVhbe3uaaWwAxW2tEsjgZPXmupQpNwdmULXq1WXDU', + 'tpubDCXK744twow5CX8HdAvV4Vez413R4xrM3hgD85mA3EpbnwgvtBmhh18eLAGsL5R9E2mwThPTz9fs4x4ZYgCC6GuuKmzSitH9FgWyqaDEKta', + 'tpubDCLxCbopTq5qisZzRcf5ZJ8dHR3PXEexc1vDUR61eGDnSVcXjvEwC9CFXqRPzCi9vmrMd6xfJtFrZY8yrPo5886K1AjJACAviLuEXMNfvbS', + 'tpubDB7v3qHJSGF9r3c1VRnQxwVi7gaTWWF7rt1zohdU4RRahfcvcYXjQ63PWzHSGBzY3ZCu61A1t9SENM147kwdWimtmo5Lm5HgPNgzk83Q45x' + ] + + is_change = False + addr_index = 3 + + def generate_address_and_compare_with_core(desc_tmpl: str): + n_keys_total = desc_tmpl.count("@") + wallet_policy = WalletPolicy("A policy", desc_tmpl, test_keys[:n_keys_total]) + + assert n_keys_total <= len(test_keys), "add more tpubs to the test_keys" + + wallet_hmac = hmac.new(speculos_globals.wallet_registration_key, wallet_policy.id, sha256).digest() + addr_hww = client.get_wallet_address(wallet_policy, wallet_hmac, is_change, addr_index, False) + + desc = wallet_policy.get_descriptor(is_change) + # compute descriptor checksum and derive the address + desc_chk = rpc.getdescriptorinfo(desc)["descriptor"] + addr_core = rpc.deriveaddresses(desc_chk, [3, 3])[0] + + assert T(addr_hww) == addr_core + + for fr in fragments_wsh: + # We use "and_b(pk(@/**),a:})" as a generic gadget to compute + # a valid descriptor that can be registered, as long as the if valid and safe. + + n_keys = fr.count("@") + desc_tmpl = f"wsh(and_b(pk(@{n_keys}/**),{prepend_a(fr)}))" + + generate_address_and_compare_with_core(desc_tmpl) + + for fr in fragments_tr: + # For taproot miniscript, we use "tr(@, and_b(pk(@/**),a:})" + # as the generic gadget. + + n_keys = fr.count("@") + desc_tmpl = f"tr(@{n_keys}/**,and_b(pk(@{n_keys+1}/**),{prepend_a(fr)}))" + + generate_address_and_compare_with_core(desc_tmpl) diff --git a/tests/test_get_wallet_address_v1.py b/tests/test_get_wallet_address_v1.py new file mode 100644 index 000000000..8d7f52e3f --- /dev/null +++ b/tests/test_get_wallet_address_v1.py @@ -0,0 +1,270 @@ +# Tests using the V1 version of the wallet policy language, used before version 2.1.0 of the app +# Make sure we remain compatible for some time. + +from ledger_bitcoin import AddressType, MultisigWallet, WalletPolicy, WalletType +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.exception.errors import IncorrectDataError +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient + +from .instructions import wallet_instruction_approve + + +import pytest + +# TODO: add more tests with UI + + +def test_get_wallet_address_singlesig_legacy_v1(client: RaggerClient): + # legacy address (P2PKH) + wallet = WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[ + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + assert client.get_wallet_address(wallet, None, 0, 0, False) == "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm" + assert client.get_wallet_address(wallet, None, 1, 15, False) == "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT" + + +def test_get_wallet_address_singlesig_wit_v1(client: RaggerClient): + # bech32 address (P2WPKH) + wallet = WalletPolicy( + name="", + descriptor_template="wpkh(@0)", + keys_info=[ + f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + assert client.get_wallet_address(wallet, None, 0, 0, False) == "tb1qzdr7s2sr0dwmkwx033r4nujzk86u0cy6fmzfjk" + assert client.get_wallet_address(wallet, None, 1, 15, False) == "tb1qlrvzyx8jcjfj2xuy69du9trtxnsvjuped7e289" + + +def test_get_wallet_address_singlesig_sh_wit_v1(client: RaggerClient): + # wrapped segwit addresses (P2SH-P2WPKH) + wallet = WalletPolicy( + name="", + descriptor_template="sh(wpkh(@0))", + keys_info=[ + f"[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + assert client.get_wallet_address(wallet, None, 0, 0, False) == "2MyHkbusvLomaarGYMqyq7q9pSBYJRwWcsw" + assert client.get_wallet_address(wallet, None, 1, 15, False) == "2NAbM4FSeBQG4o85kbXw2YNfKypcnEZS9MR" + + +def test_get_wallet_address_singlesig_taproot_v1(client: RaggerClient): + # test for a native taproot wallet (bech32m addresses, per BIP-0086) + + wallet = WalletPolicy( + name="", + descriptor_template="tr(@0)", + keys_info=[ + f"[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + res = client.get_wallet_address(wallet, None, 0, 0, False) + assert res == "tb1pws8wvnj99ca6acf8kq7pjk7vyxknah0d9mexckh5s0vu2ccy68js9am6u7" + + res = client.get_wallet_address(wallet, None, 0, 9, False) + assert res == "tb1psl7eyk2jyjzq6evqvan854fts7a5j65rth25yqahkd2a765yvj0qggs5ne" + + res = client.get_wallet_address(wallet, None, 1, 0, False) + assert res == "tb1pmr60r5vfjmdkrwcu4a2z8h39mzs7a6wf2rfhuml6qgcp940x9cxs7t9pdy" + + res = client.get_wallet_address(wallet, None, 1, 9, False) + assert res == "tb1p98d6s9jkf0la8ras4nnm72zme5r03fexn29e3pgz4qksdy84ndpqgjak72" + + +# Failure cases for default wallets + +def test_get_wallet_address_default_fail_wrongkeys_v1(client: RaggerClient): + # 0 keys info should be rejected + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[], + version=WalletType.WALLET_POLICY_V1 + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # more than 1 key should be rejected + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[ + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**" + ], + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # wrong BIP44 purpose should be rejected (here using 84' for a P2PKH address) + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[ + f"[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", + ], + version=WalletType.WALLET_POLICY_V1 + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # mismatching pubkey (claiming key origin "44'/1'/0'", but that's the extended dpubkey for "84'/1'/0'"") + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[ + f"[f5acc2fd/44'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**", + ], + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # wrong master fingerprint + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[ + f"[42424242/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + ], + version=WalletType.WALLET_POLICY_V1 + ), None, 0, 0, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + # too large address_index, cannot be done non-silently + with pytest.raises(ExceptionRAPDU) as e: + client.get_wallet_address(WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[ + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + ], + version=WalletType.WALLET_POLICY_V1 + ), None, 0, 100000, False) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + +# Multisig + +def test_get_wallet_address_multisig_legacy_v1(client: RaggerClient): + # test for a legacy p2sh multisig wallet + + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.LEGACY, + threshold=2, + keys_info=[ + f"[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW/**", + f"[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + wallet_hmac = bytes.fromhex( + "1980a07cde99fbdec0d487671d3bb296507e47b3ddfa778600a9d73d501983bc" + ) + + res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) + assert res == "2Mx69MjHC4ViZAH1koVXPvVgaazbBCdr89j" + + +def test_get_wallet_address_multisig_sh_wit_v1(client: RaggerClient): + # test for a wrapped segwit multisig wallet + + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.SH_WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/1']tpubDE7NQymr4AFtcJXi9TaWZtrhAdy8QyKmT4U6b9qYByAxCzoyMJ8zw5d8xVLVpbTRAEqP8pVUxjLE2vDt1rSFjaiS8DSz1QcNZ8D1qxUMx1g/**", + f"[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + wallet_hmac = bytes.fromhex( + "ff96c09cfacf89f836ded409b7315b9d7f242db8033e4de4db1cb4c275153988" + ) + + res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) + assert res == "2MxAUTJh27foYtyp9dcSxP7RgaSwkkVCHTU" + + +def test_get_wallet_address_multisig_wit_v1(client: RaggerClient): + # test for a native segwit multisig wallet (bech32 address) + + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + wallet_hmac = bytes.fromhex( + "d6434852fb3caa7edbd1165084968f1691444b3cfc10cf1e431acbbc7f48451f" + ) + + res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, False) + assert res == "tb1qmyauyzn08cduzdqweexgna2spwd0rndj55fsrkefry2cpuyt4cpsn2pg28" + + +def test_get_wallet_address_singlesig_legacy_v1_ui(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # legacy address (P2PKH) + wallet = WalletPolicy( + name="", + descriptor_template="pkh(@0)", + keys_info=[ + f"[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + assert client.get_wallet_address(wallet, None, 0, 0, True, navigator=navigator, + instructions=wallet_instruction_approve(firmware), testname=f"{test_name}_0") == "mz5vLWdM1wHVGSmXUkhKVvZbJ2g4epMXSm" + + assert client.get_wallet_address(wallet, None, 1, 15, True, navigator=navigator, + instructions=wallet_instruction_approve(firmware), testname=f"{test_name}_1") == "myFCUBRCKFjV7292HnZtiHqMzzHrApobpT" + + +def test_get_wallet_address_multisig_legacy_v1_ui(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): + # test for a legacy p2sh multisig wallet + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.LEGACY, + threshold=2, + keys_info=[ + f"[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW/**", + f"[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + wallet_hmac = bytes.fromhex( + "1980a07cde99fbdec0d487671d3bb296507e47b3ddfa778600a9d73d501983bc" + ) + + res = client.get_wallet_address(wallet, wallet_hmac, 0, 0, True, navigator=navigator, + instructions=wallet_instruction_approve(firmware), + testname=test_name) + assert res == "2Mx69MjHC4ViZAH1koVXPvVgaazbBCdr89j" diff --git a/tests/test_protocol.py b/tests/test_protocol.py new file mode 100644 index 000000000..670ea6859 --- /dev/null +++ b/tests/test_protocol.py @@ -0,0 +1,38 @@ +import pytest + +from ledger_bitcoin.command_builder import BitcoinCommandBuilder, BitcoinInsType, CURRENT_PROTOCOL_VERSION +from ledger_bitcoin.exception.errors import WrongP1P2Error +from ledger_bitcoin.exception.device_exception import DeviceException + +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient + + +def test_high_p1_allowed(client: RaggerClient): + # We reserve p1 for feature flags, so non-zero bits shouldn't be rejected + # for forward-compatibility; this allows graceful degradation for optional features. + + # We can't use the client to send this apdu, so we use raw transport. + # We're only testing that no exception is raised. + client.transport_client.exchange( + cla=BitcoinCommandBuilder.CLA_BITCOIN, + ins=BitcoinInsType.GET_MASTER_FINGERPRINT, + p1=0xff, + p2=CURRENT_PROTOCOL_VERSION, + data=b'' + ) + + +def test_p2_too_high(client: RaggerClient): + # Tests that sending a p2 > CURRENT_PROTOCOL_VERSION fails with 0x6a86 (WRONG_P1P2) + with pytest.raises(ExceptionRAPDU) as e: + # We can't use the client to send this apdu, so we use raw transport + client.transport_client.exchange( + cla=BitcoinCommandBuilder.CLA_BITCOIN, + ins=BitcoinInsType.GET_MASTER_FINGERPRINT, + p1=0, + p2=CURRENT_PROTOCOL_VERSION + 1, + data=b'' + ) + assert DeviceException.exc.get(e.value.status) == WrongP1P2Error + assert len(e.value.data) == 0 diff --git a/tests/test_register_wallet.py b/tests/test_register_wallet.py index ffaf9d8ee..376ff67d0 100644 --- a/tests/test_register_wallet.py +++ b/tests/test_register_wallet.py @@ -1,102 +1,147 @@ -from bitcoin_client.ledger_bitcoin import Client, AddressType, MultisigWallet, PolicyMapWallet -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError -from bitcoin_client.ledger_bitcoin.exception import DenyError - -from test_utils import has_automation +from ledger_bitcoin import AddressType, MultisigWallet, WalletPolicy +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.exception import DenyError +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient +from .instructions import register_wallet_instruction_approve, register_wallet_instruction_approve_long, register_wallet_instruction_approve_unusual, register_wallet_instruction_reject, Instructions import hmac from hashlib import sha256 import pytest -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_legacy(client: Client, speculos_globals): - wallet = MultisigWallet( - name="Cold storage", - address_type=AddressType.LEGACY, - threshold=2, - keys_info=[ - f"[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW/**", - f"[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm/**", - ], - ) - - wallet_id, wallet_hmac = client.register_wallet(wallet) +def run_register_test(navigator: Navigator, client: RaggerClient, speculos_globals, wallet_policy: + WalletPolicy, instructions: Instructions, + test_name: str = "") -> None: + wallet_policy_id, wallet_hmac = client.register_wallet(wallet_policy, navigator, + instructions=instructions, + testname=test_name) - assert wallet_id == wallet.id + assert wallet_policy_id == wallet_policy.id assert hmac.compare_digest( - hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), + hmac.new(speculos_globals.wallet_registration_key, + wallet_policy_id, sha256).digest(), wallet_hmac, ) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_sh_wit(client: Client, speculos_globals): - wallet = MultisigWallet( +def test_register_wallet_accept_legacy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, MultisigWallet( name="Cold storage", - address_type=AddressType.SH_WIT, + address_type=AddressType.LEGACY, threshold=2, keys_info=[ - f"[76223a6e/48'/1'/0'/1']tpubDE7NQymr4AFtcJXi9TaWZtrhAdy8QyKmT4U6b9qYByAxCzoyMJ8zw5d8xVLVpbTRAEqP8pVUxjLE2vDt1rSFjaiS8DSz1QcNZ8D1qxUMx1g/**", - f"[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY/**", + "[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW", + "[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm", ], - ) + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) - wallet_id, wallet_hmac = client.register_wallet(wallet) - assert wallet_id == wallet.id - - assert hmac.compare_digest( - hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), - wallet_hmac, - ) +def test_register_wallet_accept_sh_wit(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, MultisigWallet( + name="Cold storage", + address_type=AddressType.SH_WIT, + threshold=2, + keys_info=[ + "[76223a6e/48'/1'/0'/1']tpubDE7NQymr4AFtcJXi9TaWZtrhAdy8QyKmT4U6b9qYByAxCzoyMJ8zw5d8xVLVpbTRAEqP8pVUxjLE2vDt1rSFjaiS8DSz1QcNZ8D1qxUMx1g", + "[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY", + ], + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_accept_wit(client: Client, speculos_globals): - wallet = MultisigWallet( +def test_register_wallet_accept_wit(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, MultisigWallet( name="Cold storage", address_type=AddressType.WIT, threshold=2, keys_info=[ - f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], - ) + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) - wallet_id, wallet_hmac = client.register_wallet(wallet) - assert wallet_id == wallet.id +def test_register_wallet_with_long_name(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + name = "Cold storage with a pretty long name that requires 64 characters" + assert len(name) == 64 - assert hmac.compare_digest( - hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), - wallet_hmac, - ) + run_register_test(navigator, client, speculos_globals, MultisigWallet( + name=name, + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) -@has_automation("automations/register_wallet_reject.json") -def test_register_wallet_reject_header(client: Client): +def test_register_wallet_reject_header(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, threshold=2, keys_info=[ - f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], ) - with pytest.raises(DenyError): - client.register_wallet(wallet) + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_reject( + firmware), + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 + + +def test_register_wallet_invalid_pubkey_version(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # This is the same wallet policy as the test_register_wallet_accept_wit test, + # but the external pubkey has the wrong BIP32 version (mainnet xpub instead of testnet tpub). + # An older version of the app ignored the version for external pubkeys, while now it rejects it + # if the version is wrong, as a sanity check. + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + "[76223a6e/48'/1'/0'/2']xpub6DjjtjxALtJSP9dKRKuhejeTpZc711gUGZyS9nCM5GAtrNTDuMBZD2FcndJoHst6LYNbJktm4NmJyKqspLi5uRmtnDMAdcPAf2jiSj9gFTX", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ), navigator, instructions=register_wallet_instruction_approve(firmware), testname=test_name) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_invalid_names(client: Client): +def test_register_wallet_invalid_names(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 + too_long_name, # 65 characters is too long " Test", "Test ", # can't start with spaces "Tæst", # characters out of allowed range ]: @@ -105,48 +150,269 @@ def test_register_wallet_invalid_names(client: Client): address_type=AddressType.WIT, threshold=2, keys_info=[ - f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], ) - with pytest.raises(IncorrectDataError): + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator, + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + +def test_register_wallet_missing_key(client: RaggerClient): + wallet = WalletPolicy( + name="Missing a key", + descriptor_template="wsh(multi(2,@0/**,@1/**))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + # the second key is missing + ], + ) + + with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(wallet) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 -@has_automation("automations/register_wallet_accept.json") -def test_register_wallet_unsupported_policy(client: Client): - # valid policise, but not supported (might change in the future) +def test_register_wallet_unsupported_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # valid policies, but not supported (might change in the future) - with pytest.raises(NotSupportedError): - client.register_wallet(PolicyMapWallet( + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( name="Unsupported", - policy_map="sh(pkh(@0))", # unusual script, not in the whitelist + descriptor_template="pk(@0/**)", # bare pubkey, not supported keys_info=[ - f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", ] - )) + ), + navigator, + testname=test_name) - with pytest.raises(NotSupportedError): - # Not supporting keys without wildcard - client.register_wallet(MultisigWallet( - name="Cold storage", - address_type=AddressType.WIT, - threshold=2, + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + +def test_register_miniscript_long_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + # This test makes sure that policies longer than 256 bytes work as expected on all devices, + # except on Nano S that has 196 bytes as a technical limitation. + wallet = WalletPolicy( + name="Long policy", + descriptor_template=f"wsh(and_v(and_v(v:pk(@0/**),or_c(pk(@1/**),or_c(pk(@2/**),v:older(1000)))),and_v(v:hash256(0563fb3e85cbc61b134941ad6610a2b0dfd77543dfb77a5433ff3cb538213807),and_v(v:hash256(ad3391a00bad00a6a03f907b3fcc2f369a88be038c63c7db7f43b01e097efbbe),hash256(137dfa9b54a538200c94e3c9dd1a59b431e3b89aef8093fc910df48a98cb06d9)))))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", + ]) + + if (firmware.name == "nanos"): + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet) + + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + else: + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_approve_long( + firmware), + testname=test_name) + + assert wallet_id == wallet.id + + assert hmac.compare_digest( + hmac.new(speculos_globals.wallet_registration_key, + wallet_id, sha256).digest(), + wallet_hmac, + ) + + +def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # pubkeys in the keys vector must be all different + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( + name="Unsupported policy", + descriptor_template=f"wsh(c:andor(pk(@0/<0;1>/*),pk_k(@1/**),and_v(v:pk(@2/<2;3>/*),pk_k(@3/**))))", keys_info=[ - f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", - f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", - ], - )) + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + # the next key is again the internal pubkey, but without key origin information + "tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", + ]), + navigator, + testname=test_name + ) - with pytest.raises(NotSupportedError): - # Not supporting keys without origin information (even if external) - client.register_wallet(MultisigWallet( - name="Cold storage", - address_type=AddressType.WIT, - threshold=2, + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + # Key placeholders referring to the same key must have distinct derivations + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( + name="Unsupported policy", + descriptor_template="wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@0/**),sln:older(12960)))", keys_info=[ - f"tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", - ], - )) + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( + name="Unsupported policy", + # even a partial overlap (derivation @0/1 being used twice) is not acceptable + descriptor_template="wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@0/<1;2>/*),sln:older(12960)))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + # Miniscript policy with timelock mixing + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( + name="Timelock mixing is bad", + descriptor_template="wsh(thresh(2,c:pk_k(@0/**),ac:pk_k(@1/**),altv:after(1000000000),altv:after(100)))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + # Miniscript policy that does not always require a signature + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( + name="No need for sig", + descriptor_template="wsh(or_d(multi(1,@0/**),or_b(multi(3,@1/**,@2/**,@3/**),su:after(500000))))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", + "tpubDF6JT5K4izwALMpFv7fQrpWr5bGUMEoWphkzTVJH8jTfgirNEgGZnxsWJDCCxhg2UnW5RcD9Tx8aVAdoM734X5bnRGmJUujz26uQ5gAC1nE", + "tpubDF4kujkh5dAhC1pFgBToZybXdvJFXXGX4BWdDxWqP7EUpG8gxkfMQeDjGPDnTr9e4NrkFmDM1ocav3Jz6x79CRZbxGr9dzFokJLuvDDnyRh", + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + # Malleable policy, even if it requires a signature + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( + name="Malleable", + descriptor_template="wsh(c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(@0/**),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(@1/**))))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDDV6FDLcCieWUeN7R3vZK2Qs3KuQed3ScTY9EiwMXvyCkLjDbCb8RXaAgWDbkG4tW1BMKVF1zERHnyt78QKd4ZaAYGMJMpvHPwgSSU1AxZ3", + ]), + navigator, + testname=test_name + ) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + # 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. + + +def test_register_unusual_singlesig_accounts(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + # Tests that it is possible to register policies for single-signature using unusual paths + + run_register_test(navigator, client, speculos_globals, WalletPolicy( + name="Unusual Legacy", + descriptor_template="pkh(@0/**)", + keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Legacy") + + run_register_test(navigator, client, speculos_globals, WalletPolicy( + name="Unusual Nested SegWit", + descriptor_template="sh(wpkh(@0/**))", + keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Nested_Segwit") + + run_register_test(navigator, client, speculos_globals, WalletPolicy( + name="Unusual Native SegWit", + descriptor_template="wpkh(@0/**)", + keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Native_Segwit") + + run_register_test(navigator, client, speculos_globals, WalletPolicy( + name="Unusual Taproot", + descriptor_template="tr(@0/**)", + keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"] + ), + instructions=register_wallet_instruction_approve_unusual(firmware), + test_name=f"{test_name}_Unusual_Taproot") + + +def test_register_wallet_tr_script_pk(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, WalletPolicy( + name="Taproot foreign internal key, and our script key", + descriptor_template="tr(@0/**,pk(@1/**))", + keys_info=[ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) + + +def test_register_wallet_tr_with_nums_keypath(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + # The taproot keypath is unspendable; the UX must explicitly mark it as a 'dummy' key. + # The tpub for @0 is obtained by using the NUMS (Nothing-Up-My-Sleeve) key defined in BIP-0341, + # and using 32 zero bytes as the chaincode. + # It is important that the app can detect and clearly communicate to the user that the key is + # a dummy one, therefore unusable for spending. + run_register_test(navigator, client, speculos_globals, WalletPolicy( + name="Taproot unspendable keypath", + descriptor_template="tr(@0/**,pk(@1/**))", + keys_info=[ + "tpubD6NzVbkrYhZ4WLczPJWReQycCJdd6YVWXubbVUFnJ5KgU5MDQrD998ZJLSmaB7GVcCnJSDWprxmrGkJ6SvgQC6QAffVpqSvonXmeizXcrkN", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ), + instructions=register_wallet_instruction_approve(firmware), + test_name=test_name) + + +def test_register_wallet_tr_script_sortedmulti(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + run_register_test(navigator, client, speculos_globals, WalletPolicy( + name="Taproot single-key or multisig 2-of-2", + descriptor_template="tr(@0/**,sortedmulti_a(2,@1/**,@2/**))", + keys_info=[ + "[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY", + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ), + instructions=register_wallet_instruction_approve_long(firmware), + test_name=test_name) diff --git a/tests/test_register_wallet_v1.py b/tests/test_register_wallet_v1.py new file mode 100644 index 000000000..bcea05160 --- /dev/null +++ b/tests/test_register_wallet_v1.py @@ -0,0 +1,181 @@ +# Tests using the V1 version of the wallet policy language, used before version 2.1.0 of the app +# Make sure we remain compatible for some time. + +from ledger_bitcoin import AddressType, MultisigWallet, WalletPolicy, WalletType +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError, DenyError +from ledger_bitcoin.exception.device_exception import DeviceException + +from ragger.error import ExceptionRAPDU +from ragger.navigator import Navigator, NavInsID +from ragger.firmware import Firmware +from ragger_bitcoin import RaggerClient + +from .instructions import register_wallet_instruction_approve, register_wallet_instruction_approve_long, register_wallet_instruction_approve_unusual, register_wallet_instruction_reject + +import hmac +from hashlib import sha256 + +import pytest + + +def test_register_wallet_accept_legacy_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.LEGACY, + threshold=2, + keys_info=[ + f"[5c9e228d/48'/1'/0'/0']tpubDEGquuorgFNb8bjh5kNZQMPtABJzoWwNm78FUmeoPkfRtoPF7JLrtoZeT3J3ybq1HmC3Rn1Q8wFQ8J5usanzups5rj7PJoQLNyvq8QbJruW/**", + f"[f5acc2fd/48'/1'/0'/0']tpubDFAqEGNyad35WQAZMmPD4vgBXnjH16RGciLdWekPe4f4d5JzoHVu1PS86Sy4Tm63vDf8rfV3UjifhrRuSUDfiZj5KPffTPyZ4ZXBKvjD8jm/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + 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(), + wallet_hmac, + ) + + +def test_register_wallet_accept_sh_wit_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.SH_WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/1']tpubDE7NQymr4AFtcJXi9TaWZtrhAdy8QyKmT4U6b9qYByAxCzoyMJ8zw5d8xVLVpbTRAEqP8pVUxjLE2vDt1rSFjaiS8DSz1QcNZ8D1qxUMx1g/**", + f"[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + 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(), + wallet_hmac, + ) + + +def test_register_wallet_accept_wit_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, speculos_globals): + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, + 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(), + wallet_hmac, + ) + + +def test_register_wallet_reject_header_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator, + instructions=register_wallet_instruction_reject(firmware), + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 + + +def test_register_wallet_invalid_names_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + 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 + ]: + wallet = MultisigWallet( + name=invalid_name, + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, navigator) + + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + + +def test_register_wallet_unsupported_policy_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # valid policies, but not supported (might change in the future) + + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(WalletPolicy( + name="Unsupported", + descriptor_template="pk(@0)", # bare pubkey, not supported + keys_info=[ + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + ], + version=WalletType.WALLET_POLICY_V1 + ), + navigator, + testname=test_name) + + # NotSupportedError + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + with pytest.raises(ExceptionRAPDU) as e: + # Not supporting keys without wildcard + client.register_wallet(MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + version=WalletType.WALLET_POLICY_V1 + ), + navigator, + testname=test_name) + + # NotSupportedError + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 diff --git a/tests/test_sign_message.py b/tests/test_sign_message.py index 53dccbeac..34f422469 100644 --- a/tests/test_sign_message.py +++ b/tests/test_sign_message.py @@ -1,47 +1,103 @@ import pytest -from bitcoin_client.ledger_bitcoin import Client -from bitcoin_client.ledger_bitcoin.exception.errors import DenyError +from ledger_bitcoin.exception.errors import DenyError +from ledger_bitcoin.exception.device_exception import DeviceException +from ragger.navigator import Navigator +from ragger.firmware import Firmware +from ragger.error import ExceptionRAPDU +from ragger_bitcoin import RaggerClient +from .instructions import message_instruction_approve, message_instruction_approve_long, message_instruction_reject -from test_utils import has_automation - -@has_automation("automations/sign_message_accept.json") -def test_sign_message(client: Client): +def test_sign_message(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): msg = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks." path = "m/44'/1'/0'/0/0" - result = client.sign_message(msg, path) + result = client.sign_message(msg, path, navigator, + instructions=message_instruction_approve(firmware), + testname=test_name) assert result == "IOR4YRVlmJGMx+H7PgQvHzWAF0HAgrUggQeRdnoWKpypfaAberpvF+XbOCM5Cd/ljogNyU3w2OIL8eYCyZ6Ru2k=" -@has_automation("automations/sign_message_accept.json") -def test_sign_message_accept(client: Client): +def test_sign_message_accept(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): message = "Hello world!" res = client.sign_message( message, - "m/84'/1'/0'/0/0" + "m/84'/1'/0'/0/0", + navigator, + instructions=message_instruction_approve(firmware), + testname=test_name ) assert res == 'IEOK4+JMK7FToR7XMzFCoAYh1nud1IKm9Wq3vXLSVk/lBay8rHCRp9bP6riyR5NDqXYyYf7cXgMQTHNz3SemwZI=' -@has_automation("automations/sign_message_accept.json") -def test_sign_message_accept_long(client: Client): +def test_sign_message_accept_long(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # Test with a long message that is split in multiple leaves in the Merkle tree - message = "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible." res = client.sign_message( message, - "m/84'/1'/0'/0/8" + "m/84'/1'/0'/0/8", + navigator, + instructions=message_instruction_approve_long(firmware), + testname=test_name ) assert res == 'H4frM6TYm5ty1MAf9o/Zz9Qiy3VEldAYFY91SJ/5nYMAZY1UUB97fiRjKW8mJit2+V4OCa1YCqjDqyFnD9Fw75k=' -@has_automation("automations/sign_message_reject.json") -def test_sign_message_reject(client: Client): - with pytest.raises(DenyError): - client.sign_message("Anything", "m/44'/1'/0'/0/0") +def test_sign_message_reject(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + with pytest.raises(ExceptionRAPDU) as e: + client.sign_message("Anything", "m/44'/1'/0'/0/0", + navigator, + instructions=message_instruction_reject(firmware), + testname=test_name + ) + + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 + + +def test_sign_message_accept_non_ascii(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + # Test with a message that contains non ascii char + message = "Hello\nworld!" + + res = client.sign_message( + message, + "m/84'/1'/0'/0/8", + navigator, + instructions=message_instruction_approve(firmware), + testname=test_name + ) + + assert res == 'IGGk2UM12aQGtigJ7XCLJEXQl3bdKgx0G3CIt0ADSWknfAHqs+9+9OPZSjGrjyp46GjztGzUAnCa/DDMrSIAfbg=' + + +def test_sign_message_accept_too_long(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + # Test with a message that is too long to be displayed + message = "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible. The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible. The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust. Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible." + + res = client.sign_message( + message, + "m/84'/1'/0'/0/8", + navigator, + instructions=message_instruction_approve(firmware), + testname=test_name + ) + + assert res == 'IDAl9RThAyunmYuol9DaDs/CScUpiol3FDSjIjyK9y0tc/x1HWrbT/ufdkPFY1Bmi+L9hc3ip1me2RmufprVuNk=' + + +def test_sign_message_hash_reject(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + with pytest.raises(ExceptionRAPDU) as e: + client.sign_message("Hello\nworld!", + "m/44'/1'/0'/0/0", + navigator, + instructions=message_instruction_reject(firmware), + testname=test_name + ) + + assert DeviceException.exc.get(e.value.status) == DenyError + assert len(e.value.data) == 0 diff --git a/tests/test_sign_psbt.py b/tests/test_sign_psbt.py index c622a2ed7..089634949 100644 --- a/tests/test_sign_psbt.py +++ b/tests/test_sign_psbt.py @@ -1,3 +1,4 @@ +import base64 import pytest import threading @@ -8,107 +9,29 @@ from pathlib import Path -from bitcoin_client.ledger_bitcoin import Client, PolicyMapWallet, MultisigWallet, AddressType -from bitcoin_client.ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin import WalletPolicy, MultisigWallet, AddressType, PartialSignature +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException -from bitcoin_client.ledger_bitcoin.psbt import PSBT -from bitcoin_client.ledger_bitcoin.wallet import AddressType -from speculos.client import SpeculosClient +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import AddressType +from ragger.navigator import Navigator, NavInsID +from ragger.error import ExceptionRAPDU +from ragger.firmware import Firmware -from test_utils import has_automation, bip0340, txmaker +from test_utils import bip0340, txmaker from embit.script import Script from embit.networks import NETWORKS +import requests +import json -from test_utils.speculos import automation +from ragger_bitcoin import RaggerClient +from .instructions import * tests_root: Path = Path(__file__).parent -CURRENCY_TICKER = "TEST" - - -def format_amount(ticker: str, amount: int) -> str: - """Formats an amounts in sats as shown in the app: divided by 10_000_000, with no trailing zeroes.""" - assert amount >= 0 - - return f"{ticker} {str(Decimal(amount) / 100_000_000)}" - - -def should_go_right(event: dict): - """Returns true if the current text event implies a "right" button press to proceed.""" - - if event["text"].startswith("Review"): - return True - elif event["text"].startswith("Amount"): - return True - elif event["text"].startswith("Address"): - return True - elif event["text"].startswith("Confirm"): - return True - elif event["text"].startswith("Fees"): - return True - return False - - -def ux_thread_sign_psbt(speculos_client: SpeculosClient, all_events: List[dict]): - """Completes the signing flow always going right and accepting at the appropriate time, while collecting all the events in all_events.""" - - # press right until the last screen (will press the "right" button more times than needed) - - while True: - event = speculos_client.get_next_event() - all_events.append(event) - - if should_go_right(event): - speculos_client.press_and_release("right") - elif event["text"] == "Approve": - speculos_client.press_and_release("both") - elif event["text"] == "Accept": - speculos_client.press_and_release("both") - break - - -def parse_signing_events(events: List[dict]) -> dict: - ret = dict() - - # each of these is True if the _previous_ event was matching (so the next text needs to be recorded) - was_amount = False - was_address = False - was_fees = False - - cur_output_index = -1 - - ret["addresses"] = [] - ret["amounts"] = [] - ret["fees"] = "" - - for ev in events: - if ev["text"].startswith("output #"): - idx_str = ev["text"][8:] - - assert int(idx_str) - 1 == cur_output_index + 1 # should not skip outputs - - cur_output_index = int(idx_str) - 1 - - ret["addresses"].append("") - ret["amounts"].append("") - - if was_address: - ret["addresses"][-1] += ev["text"] - if was_amount: - ret["amounts"][-1] += ev["text"] - - if was_fees: - ret["fees"] += ev["text"] - - was_amount = ev["text"].startswith("Amount") - was_address = ev["text"].startswith("Address") - was_fees = ev["text"].startswith("Fees") - - return ret - - def open_psbt_from_file(filename: str) -> PSBT: raw_psbt_base64 = open(filename, "r").read() @@ -117,17 +40,15 @@ def open_psbt_from_file(filename: str) -> PSBT: return psbt -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_pkh_1to1(client: Client): - +def test_sign_psbt_singlesig_pkh_1to1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # PSBT for a legacy 1-input 1-output spend (no change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/pkh-1to1.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "pkh(@0)", + "pkh(@0/**)", [ - "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**" + "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT" ], ) @@ -135,26 +56,41 @@ def test_sign_psbt_singlesig_pkh_1to1(client: Client): # #0: # "pubkey" : "02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718", # "signature" : "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" - result = client.sign_psbt(psbt, wallet, None) - - assert result == { - 0: bytes.fromhex( - "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + print(result) + print([( + 0, + PartialSignature( + pubkey=bytes.fromhex("02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718"), + signature=bytes.fromhex( + "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + ) ) - } + )]) + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718"), + signature=bytes.fromhex( + "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + ) + ) + )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_sh_wpkh_1to2(client: Client): +def test_sign_psbt_singlesig_sh_wpkh_1to2(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # PSBT for a wrapped segwit 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/sh-wpkh-1to2.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "sh(wpkh(@0))", + "sh(wpkh(@0/**))", [ - "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**" + "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3" ], ) @@ -162,58 +98,100 @@ def test_sign_psbt_singlesig_sh_wpkh_1to2(client: Client): # #0: # "pubkey" : "024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", # "signature" : "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" - result = client.sign_psbt(psbt, wallet, None) - - assert result == { - 0: bytes.fromhex( - "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67"), + signature=bytes.fromhex( + "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" + ) ) - } + )] + + +def test_sign_psbt_highfee(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + # Transactions with fees higher than 10% of total amount + # An additional warning is shown. + + # PSBT for a wrapped segwit 1-input 2-output spend (1 change address) + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/sh-wpkh-1to2.psbt") + # Make sure that the fees are at least 10% of the total amount + for out in psbt.tx.vout: + out.nValue = int(out.nValue * 0.9) -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_1to2(client: Client): + # the test is only interesting if the total amount is at least 10000 sats + assert sum(input.witness_utxo.nValue for input in psbt.inputs) >= 10000 - # PSBT for a legacy 1-input 2-output spend (1 change address) + wallet = WalletPolicy( + "", + "sh(wpkh(@0/**))", + [ + "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3" + ], + ) + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_3(firmware), + testname=test_name) + + assert len(result) == 1 + + +def test_sign_psbt_singlesig_wpkh_1to2(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a segwit 1-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) # expected sigs # #0: # "pubkey" : "03ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d3068", # "signature" : "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" - assert result == { - 0: bytes.fromhex( - "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d3068"), + signature=bytes.fromhex( + "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" + ) ) - } + )] -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_2to2(client: Client): - # PSBT for a legacy 2-input 2-output spend (1 change address) +def test_sign_psbt_singlesig_wpkh_2to2(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a segwit 2-input 2-output spend (1 change address) psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-2to2.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) # expected sigs # #0: @@ -223,17 +201,98 @@ def test_sign_psbt_singlesig_wpkh_2to2(client: Client): # "pubkey" : "0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0", # "signature" : "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" - assert result == { - 0: bytes.fromhex( - "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" - ), - 1: bytes.fromhex( - "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" - ), - } + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3"), + signature=bytes.fromhex( + "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" + ) + ) + ), ( + 1, + PartialSignature( + pubkey=bytes.fromhex("0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0"), + signature=bytes.fromhex( + "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" + ), + ) + )] + + +def test_sign_psbt_singlesig_wpkh_2to2_missing_nonwitnessutxo(navigator: Navigator, firmware: + Firmware, client: RaggerClient, test_name: str): + # Same as the previous test, but the non-witness-utxo is missing. + # The app should sign after a warning. + + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-2to2.psbt") + + # remove the non-witness-utxo field + for input in psbt.inputs: + input.non_witness_utxo = None + + wallet = WalletPolicy( + "", + "wpkh(@0/**)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" + ], + ) + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + # expected sigs + # #0: + # "pubkey" : "03455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3", + # "signature" : "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" + # #1: + # "pubkey" : "0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0", + # "signature" : "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3"), + signature=bytes.fromhex( + "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" + ) + ) + ), ( + 1, + PartialSignature( + pubkey=bytes.fromhex("0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0"), + signature=bytes.fromhex( + "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" + ) + ) + )] + + +def test_sign_psbt_singlesig_wpkh_selftransfer(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + + # The only output is a change output. + # A "self-transfer" screen should be shown before the fees. + + wallet = WalletPolicy( + "", + "wpkh(@0/**)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" + ], + ) + psbt = "cHNidP8BAHECAAAAAfcDVJxLN1tzz5vaIy2onFL/ht/OqwKm2jEWGwMNDE/cAQAAAAD9////As0qAAAAAAAAFgAUJfcXOL7SoYGoDC1n6egGa0OTD9/mtgEAAAAAABYAFDXG4N1tPISxa6iF3Kc6yGPQtZPsTTQlAAABAPYCAAAAAAEBCOcYS1aMP1uQcUKTMJbvlsZXsV4yNnVxynyMfxSX//UAAAAAFxYAFGEWho6AN6qeux0gU3BSWnK+Dw4D/f///wKfJwEAAAAAABepFG1IUtrzpUCfdyFtu46j1ZIxLX7ph0DiAQAAAAAAFgAU4e5IJz0XxNe96ANYDugMQ34E0/cCRzBEAiB1b84pX0QaOUrvCdDxKeB+idM6wYKTLGmqnUU/tL8/lQIgbSinpq4jBlo+SIGyh8XNVrWAeMlKBNmoLenKOBugKzcBIQKXsd8NwO+9naIfeI3nkgYjg6g3QZarGTRDs7SNVZfGPJBJJAABAR9A4gEAAAAAABYAFOHuSCc9F8TXvegDWA7oDEN+BNP3IgYCgffBheEUZI8iAFFfv7b+HNM7j4jolv6lj5/n3j68h3kY9azC/VQAAIABAACAAAAAgAAAAAAHAAAAACICAzQZjNnkwXFEhm1F6oC2nk1ADqH6t/RHBAOblLA4tV5BGPWswv1UAACAAQAAgAAAAIABAAAAEgAAAAAiAgJxtbd5rYcIOFh3l7z28MeuxavnanCdck9I0uJs+HTwoBj1rML9VAAAgAEAAIAAAACAAQAAAAAAAAAA" + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_5(firmware), + testname=test_name) + + assert len(result) == 1 -# def test_sign_psbt_legacy(client: Client): + +# def test_sign_psbt_legacy(client: RaggerClient, test_name: str): # # legacy address # # PSBT for a legacy 1-input 1-output spend # unsigned_raw_psbt_base64 = "cHNidP8BAFQCAAAAAbUlIwxFfIt0fsuFCNtL3dHKcOvUPQu2CNcqc8FrNtTyAAAAAAD+////AaDwGQAAAAAAGKkU2FZEFTTPb1ZpCw2Oa2sc/FxM59GIrAAAAAAAAQD5AgAAAAABATfphYFskBaL7jbWIkU3K7RS5zKr5BvfNHjec1rNieTrAQAAABcWABTkjiMSrvGNi5KFtSy72CSJolzNDv7///8C/y8bAAAAAAAZdqkU2FZEFTTPb1ZpCw2Oa2sc/FxM59GIrDS2GJ0BAAAAF6kUnEFiBqwsbP0pWpazURx45PGdXkWHAkcwRAIgCxWs2+R6UcpQuD6QKydU0irJ7yNe++5eoOly5VgqrEsCIHUD6t4LNW0292vnP+heXZ6Walx8DRW2TB+IOazzDNcaASEDnQS6zdUebuNm7FuOdKonnlNmPPpUyN66w2CIsX5N+pUhIh4AAAA=" @@ -246,7 +305,7 @@ def test_sign_psbt_singlesig_wpkh_2to2(client: Client): # print(result) -# def test_sign_psbt_legacy_p2pkh(client: Client): +# def test_sign_psbt_legacy_p2pkh(client: RaggerClient, test_name: str): # # test from app-bitcoin # # legacy address @@ -263,63 +322,128 @@ def test_sign_psbt_singlesig_wpkh_2to2(client: Client): # print(result) -@has_automation("automations/sign_with_wallet_accept.json") -def test_sign_psbt_multisig_wsh(client: Client): +def test_sign_psbt_multisig_wsh(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): wallet = MultisigWallet( name="Cold storage", address_type=AddressType.WIT, threshold=2, keys_info=[ - f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", - f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", ], ) wallet_hmac = bytes.fromhex( - "d6434852fb3caa7edbd1165084968f1691444b3cfc10cf1e431acbbc7f48451f" + "d7c7a60b4ab4a14c1bf8901ba627d72140b2fb907f2b4e35d2e693bce9fbb371" ) psbt = open_psbt_from_file(f"{tests_root}/psbt/multisig/wsh-2of2.psbt") - result = client.sign_psbt(psbt, wallet, wallet_hmac) + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_6(firmware), + testname=test_name) + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("036b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac"), + signature=bytes.fromhex( + "304402206ab297c83ab66e573723892061d827c5ac0150e2044fed7ed34742fedbcfb26e0220319cdf4eaddff63fc308cdf53e225ea034024ef96de03fd0939b6deeea1e8bd301" + ) + ) + )] + - assert result == { - 0: bytes.fromhex( - "304402206ab297c83ab66e573723892061d827c5ac0150e2044fed7ed34742fedbcfb26e0220319cdf4eaddff63fc308cdf53e225ea034024ef96de03fd0939b6deeea1e8bd301" +def test_sign_psbt_multisig_sh_wsh(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + # wrapped segwit multisig ("sh(wsh(sortedmulti(...)))") + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.SH_WIT, + threshold=2, + keys_info=[ + "[e24243b4/48'/1'/0'/1']tpubDFY2NoEHyYsp4J98UCMAaRT5LzRYeXjWqh2txK2RsxPAR5YWKWyTeZBBncRJ7z5nL5RUQPEgycbgbbmywbeLaH9yWK6rnFAYQn28HyiYc1Y", + "[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY", + ], + sorted=True + ) + + wallet_hmac = bytes.fromhex( + "677ec94c2e1a7446c6cac9db2adde8667b9a746dd63fa1e1863553cdb814a54a" + ) + + psbt = "cHNidP8BAFUCAAAAAS60cHn6kIlm2wk314ZKiOok2xj++cPoa/K5TXzNk4s6AQAAAAD9////AescAAAAAAAAGXapFFnK2lAxTIKeGfWneG+O4NSYf0KdiKwhlRUAAAEAigIAAAABAaNw+E0toKUlohxkK0YmapPS7uToo7RG7DA2YLrmoD8BAAAAFxYAFAppBymwQTPq8lpFfFWMuPRNdbTX/v///wI7rUIBAAAAABepFJMyNbbbdF4o3zxQhWSJ5ZXY5naHh60dAAAAAAAAF6kU9wt/XvakFsqnsR6xlBxP5N9MyyqHbvokAAEBIK0dAAAAAAAAF6kU9wt/XvakFsqnsR6xlBxP5N9MyyqHAQQiACAyIOGl/sIPCRep2F4Bude0ME17U2m2dPAiK96XdDCf7wEFR1IhA0fxhNV0BDkMTLzQjBSpKxSeh39pMEcQ+reqlD2a/D20IQPlOZCX7JMMMjUxBLMNtzR+gcVKZaL4J4sf/VRbo03NfFKuIgYDR/GE1XQEOQxMvNCMFKkrFJ6Hf2kwRxD6t6qUPZr8PbQc4kJDtDAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA+U5kJfskwwyNTEEsw23NH6BxUplovgnix/9VFujTc18HPWswv0wAACAAQAAgAAAAIABAACAAAAAAAAAAAAAAA==" + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03e5399097ec930c32353104b30db7347e81c54a65a2f8278b1ffd545ba34dcd7c"), + signature=bytes.fromhex( + "30440220689c3ee23b8f52c21abe47ea6f37cf8bc72653cab9cd32658199b1a16db193d802200db5d2157044913d5a60f69e9ce10ab9a9d883d421d3fb0400d948b31c3b7ee201" + ) ) - } + )] -# def test_sign_psbt_legacy_wrong_non_witness_utxo(client: Client): -# # legacy address -# # PSBT for a legacy 1-input 1-output spend -# # The spend is valid, but the non-witness utxo is wrong; therefore, it should fail the hash test -# # TODO: this fails PSBT decoding; need to make a version we can control for this test. +def test_sign_psbt_multisig_sh_wsh_missing_nonwitnessutxo(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): + # A transaction spending a wrapped segwit address has a script that appears like a legacy UTXO, but uses + # the segwit sighash algorithm. + # Therefore, if the non-witness-utxo is missing, we should still sign it while giving the warning for unverified inputs, + # for consistency with other segwit input types. -# unsigned_raw_psbt_base64 = "cHNidP8BAFQCAAAAAbUlIwxFfIt0fsuFCNtL3dHKcOvUPQu2CNcqc8FrNtTyAAAAAAD+////AaDwGQAAAAAAGKkU2FZEFTTPb1ZpCw2Oa2sc/FxM59GIrAAAAAAAAQD5AgAAAAABATfphYFskBaL7jbWIkU3K7RS5zKr5BvfNHjec1rNieTrAQAAABcWABTkjiMSrvGNi5KFtSy72CSJolzNDv7///8C/y8bAAAAAAAZdqkU2FZEFTTPb1ZpCw2Oa2sc/FxM59GIrDS2GJ0BAAAAF6kUnEFiBqwsbP0pWpazURx45PGdXkWHAkcwRAIgCxWs2+R6UcpQuD6QKydU0irJ7yNe++5eoOly5VgqrEsCIHUD6t4LNW0292vnP+heXZ6Walx8DRW2TB+IOazzDNcaASEDnQS6zdUebuNm7FuOdKonnlNmPPpUyN66w2CIsX5N+pUySC0BAAA=" -# psbt = PSBT() -# psbt.deserialize(unsigned_raw_psbt_base64) + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.SH_WIT, + threshold=2, + keys_info=[ + "[e24243b4/48'/1'/0'/1']tpubDFY2NoEHyYsp4J98UCMAaRT5LzRYeXjWqh2txK2RsxPAR5YWKWyTeZBBncRJ7z5nL5RUQPEgycbgbbmywbeLaH9yWK6rnFAYQn28HyiYc1Y", + "[f5acc2fd/48'/1'/0'/1']tpubDFAqEGNyad35YgH8zxvxFZqNUoPtr5mDojs7wzbXQBHTZ4xHeVXG6w2HvsKvjBpaRpTmjYDjdPg5w2c6Wvu8QBkyMDrmBWdCyqkDM7reSsY", + ], + sorted=True + ) -# with pytest.raises(IncorrectDataError): -# client.sign_psbt(psbt) + wallet_hmac = bytes.fromhex( + "677ec94c2e1a7446c6cac9db2adde8667b9a746dd63fa1e1863553cdb814a54a" + ) + + psbt = "cHNidP8BAFUCAAAAAS60cHn6kIlm2wk314ZKiOok2xj++cPoa/K5TXzNk4s6AQAAAAD9////AescAAAAAAAAGXapFFnK2lAxTIKeGfWneG+O4NSYf0KdiKwhlRUAAAEBIK0dAAAAAAAAF6kU9wt/XvakFsqnsR6xlBxP5N9MyyqHAQQiACAyIOGl/sIPCRep2F4Bude0ME17U2m2dPAiK96XdDCf7wEFR1IhA0fxhNV0BDkMTLzQjBSpKxSeh39pMEcQ+reqlD2a/D20IQPlOZCX7JMMMjUxBLMNtzR+gcVKZaL4J4sf/VRbo03NfFKuIgYDR/GE1XQEOQxMvNCMFKkrFJ6Hf2kwRxD6t6qUPZr8PbQc4kJDtDAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA+U5kJfskwwyNTEEsw23NH6BxUplovgnix/9VFujTc18HPWswv0wAACAAQAAgAAAAIABAACAAAAAAAAAAAAAAA==" + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_8(firmware), + testname=test_name) + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03e5399097ec930c32353104b30db7347e81c54a65a2f8278b1ffd545ba34dcd7c"), + signature=bytes.fromhex( + "30440220689c3ee23b8f52c21abe47ea6f37cf8bc72653cab9cd32658199b1a16db193d802200db5d2157044913d5a60f69e9ce10ab9a9d883d421d3fb0400d948b31c3b7ee201" + ) + ) + )] -@pytest.mark.skip(reason="Fails with bitcoin_testnet_lite build configuration: BadStateError") -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_taproot_1to2(client: Client): +def test_sign_psbt_taproot_1to2_sighash_all(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a p2tr 1-input 2-output spend (1 change address) - psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/tr-1to2.psbt") + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/tr-1to2-sighash-all.psbt") - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "tr(@0)", + "tr(@0/**)", [ - "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**" + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U" ], ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert len(result) == 1 # Unlike other transactions, Schnorr signatures are not deterministic (unless the randomness is removed) # Therefore, for this testcase we hard-code the sighash (which was validated with Bitcoin Core 22.0 when the @@ -329,31 +453,79 @@ def test_sign_psbt_taproot_1to2(client: Client): sighash0 = bytes.fromhex("7A999E5AD6F53EA6448E7026061D3B4523F957999C430A5A492DFACE74AE31B6") # get the (tweaked) pubkey from the scriptPubKey - pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey0_psbt = psbt.inputs[0].witness_utxo.scriptPubKey[2:] - assert len(result) == 1 + idx0, partial_sig0 = result[0] + assert idx0 == 0 + assert partial_sig0.pubkey == pubkey0_psbt + assert partial_sig0.tapleaf_hash is None # the sighash 0x01 is appended to the signature - assert len(result[0]) == 64+1 - assert result[0][-1] == 0x01 + assert len(partial_sig0.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x01 - sig0 = result[0][:-1] + assert bip0340.schnorr_verify(sighash0, pubkey0_psbt, partial_sig0.signature[:-1]) - assert bip0340.schnorr_verify(sighash0, pubkey0, sig0) +def test_sign_psbt_taproot_1to2_sighash_default(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): -def test_sign_psbt_singlesig_wpkh_4to3(client: Client, comm: SpeculosClient, is_speculos: bool): + # PSBT for a p2tr 1-input 2-output spend (1 change address) + + # Test two times: + # - the first PSBT has SIGHASH_DEFAULT; + # - the second PSBT does not specify the sighash type. + # The behavior for taproot transactions should be the same, producing 64-byte signatures + + index = 0 + for psbt_file_name in ["tr-1to2-sighash-default", "tr-1to2-sighash-omitted"]: + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/{psbt_file_name}.psbt") + + wallet = WalletPolicy( + "", + "tr(@0/**)", + [ + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U" + ], + ) + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=f"{test_name}_{index}") + index += 1 + + # Unlike other transactions, Schnorr signatures are not deterministic (unless the randomness is removed) + # Therefore, for this testcase we hard-code the sighash (which was validated with Bitcoin Core 22.0 when the + # transaction was sent), and we verify the produced Schnorr signature with the reference bip340 implementation. + + # sighash verified with bitcoin-core + sighash0 = bytes.fromhex("75C96FB06A12DB4CD011D8C95A5995DB758A4F2837A22F30F0F579619A4466F3") + + # get the (tweaked) pubkey from the scriptPubKey + expected_pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + + assert len(result) == 1 + + idx0, partial_sig0 = result[0] + + assert idx0 == 0 + assert partial_sig0.pubkey == expected_pubkey0 + assert len(partial_sig0.signature) == 64 + assert partial_sig0.tapleaf_hash is None + + assert bip0340.schnorr_verify(sighash0, partial_sig0.pubkey, partial_sig0.signature) + + +def test_sign_psbt_singlesig_wpkh_4to3(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for a segwit 4-input 3-output spend (1 change address) # this test also checks that addresses, amounts and fees shown on screen are correct - if not is_speculos: - pytest.skip("Requires speculos") - - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) @@ -361,7 +533,8 @@ def test_sign_psbt_singlesig_wpkh_4to3(client: Client, comm: SpeculosClient, is_ n_outs = 3 in_amounts = [10000 + 10000 * i for i in range(n_ins)] - out_amounts = [9999 + 9999 * i for i in range(n_outs)] + total_in = sum(in_amounts) + out_amounts = [total_in // n_outs - i for i in range(n_outs)] change_index = 1 @@ -377,44 +550,22 @@ def test_sign_psbt_singlesig_wpkh_4to3(client: Client, comm: SpeculosClient, is_ assert sum_out < sum_in - fees_amount = sum_in - sum_out - - all_events: List[dict] = [] - - x = threading.Thread(target=ux_thread_sign_psbt, args=[comm, all_events]) - x.start() - result = client.sign_psbt(psbt, wallet, None) - x.join() + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_9(firmware), + testname=test_name) assert len(result) == n_ins - parsed_events = parse_signing_events(all_events) - - assert(parsed_events["fees"] == format_amount(CURRENCY_TICKER, fees_amount)) - - shown_out_idx = 0 - for out_idx in range(n_outs): - if out_idx != change_index: - out_amt = psbt.tx.vout[out_idx].nValue - assert parsed_events["amounts"][shown_out_idx] == format_amount(CURRENCY_TICKER, out_amt) - - out_addr = Script(psbt.tx.vout[out_idx].scriptPubKey).address(network=NETWORKS["test"]) - assert parsed_events["addresses"][shown_out_idx] == out_addr - - shown_out_idx += 1 - -def test_sign_psbt_singlesig_large_amount(client: Client, comm: SpeculosClient, is_speculos: bool): +def test_sign_psbt_singlesig_large_amount(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # Test with a transaction with an extremely large amount - if not is_speculos: - pytest.skip("Requires speculos") - - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) @@ -428,65 +579,59 @@ def test_sign_psbt_singlesig_large_amount(client: Client, comm: SpeculosClient, assert sum_out < sum_in - fees_amount = sum_in - sum_out - - all_events: List[dict] = [] - - x = threading.Thread(target=ux_thread_sign_psbt, args=[comm, all_events]) - x.start() - result = client.sign_psbt(psbt, wallet, None) - x.join() + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(result) == 1 - parsed_events = parse_signing_events(all_events) - - assert(parsed_events["fees"] == format_amount(CURRENCY_TICKER, fees_amount)) - out_amt = psbt.tx.vout[0].nValue - assert parsed_events["amounts"][0] == format_amount(CURRENCY_TICKER, out_amt) - - -@has_automation("automations/sign_with_default_wallet_accept.json") -def test_sign_psbt_singlesig_wpkh_512to256(client: Client, enable_slow_tests: bool): +def test_sign_psbt_singlesig_wpkh_512to256(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, enable_slow_tests: bool): # PSBT for a transaction with 512 inputs and 256 outputs (maximum currently supported in the app) # Very slow test (esp. with DEBUG enabled), so disabled unless the --enableslowtests option is used if not enable_slow_tests: pytest.skip() - n_inputs = 512 - n_outputs = 256 - - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "tr(@0)", + "tr(@0/**)", [ - "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**" + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U" ], ) + n_inputs = 512 + n_outputs = 256 + + input_amounts = [10000 + 10000 * i for i in range(n_inputs)] + total_amount = sum(input_amounts) + output_amounts = [(total_amount // n_outputs) - 10 for _ in range(n_outputs)] + psbt = txmaker.createPsbt( wallet, - [10000 + 10000 * i for i in range(n_inputs)], - [999 + 99 * i for i in range(n_outputs)], + input_amounts, + output_amounts, [i == 42 for i in range(n_outputs)] ) - result = client.sign_psbt(psbt, wallet, None) + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(result) == n_inputs -def test_sign_psbt_fail_11_changes(client: Client): +def test_sign_psbt_fail_11_changes(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # PSBT for transaction with 11 change addresses; the limit is 10, so it must fail with NotSupportedError - # before any user interaction + # before any user interaction on nanos. - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) @@ -497,22 +642,25 @@ def test_sign_psbt_fail_11_changes(client: Client): [True] * 11, ) - with pytest.raises(NotSupportedError): - client.sign_psbt(psbt, wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_tap(firmware), + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 -def test_sign_psbt_fail_wrong_non_witness_utxo(client: Client, is_speculos: bool): +def test_sign_psbt_fail_wrong_non_witness_utxo(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): # PSBT for transaction with the wrong non-witness utxo for an input. # It must fail with IncorrectDataError before any user interaction. - if not is_speculos: - pytest.skip("Requires speculos") - - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) @@ -530,17 +678,21 @@ def test_sign_psbt_fail_wrong_non_witness_utxo(client: Client, is_speculos: bool psbt.inputs[0].non_witness_utxo = wit client._no_clone_psbt = True - with pytest.raises(IncorrectDataError): - client.sign_psbt(psbt, wallet, None) + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 client._no_clone_psbt = False -def test_sign_psbt_with_opreturn(client: Client, comm: SpeculosClient): - wallet = PolicyMapWallet( +def test_sign_psbt_with_opreturn(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) @@ -548,13 +700,37 @@ def test_sign_psbt_with_opreturn(client: Client, comm: SpeculosClient): psbt = PSBT() psbt.deserialize(psbt_b64) - with automation(comm, "automations/sign_with_default_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) assert len(hww_sigs) == 1 -def test_sign_psbt_with_segwit_v16(client: Client, comm: SpeculosClient): +def test_sign_psbt_with_naked_opreturn(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + wallet = WalletPolicy( + "", + "wpkh(@0/**)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" + ], + ) + + # Same psbt as in test_sign_psbt_with_opreturn, but the first output is a naked OP_RETURN script (no data). + # Signing such outputs is needed in BIP-0322. + psbt_b64 = "cHNidP8BAFwCAAAAAZ0gZDu3l28lrZWbtsuoIfI07zpsaXXMe6sMHHJn03LPAAAAAAD+////AgAAAAAAAAAAAWrBlZgAAAAAABYAFCuTP2nl6yRKHwS+1J6OyeTsk7yfAAAAAAABAHECAAAAAZ6afPCN0VxFOW9vKyNxhgF2lpJPsNbBKlg1xV3WnCoPAAAAAAD+////AoCWmAAAAAAAFgAUE0foKgN7Xbs4z4xHWfJCsfXH4JrzWm0pAQAAABYAFAgOnmT0kCvYJ6vJ4DkmkNGXT3iFQQAAAAEBH4CWmAAAAAAAFgAUE0foKgN7Xbs4z4xHWfJCsfXH4JoiBgJ8t100sAXE659iu/LEV9djjoE+dX787I+mhnfZULY2Yhj1rML9VAAAgAEAAIAAAACAAAAAAAAAAAAAACICAxmbidg1b1fhzjgKEgXPKGBtvqiYVbEcPf7PuKGlM1aJGPWswv1UAACAAQAAgAAAAIABAAAAAQAAAAA=" + psbt = PSBT() + psbt.deserialize(psbt_b64) + + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) + + assert len(hww_sigs) == 1 + + +def test_sign_psbt_with_segwit_v16(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): # This psbt contains an output with future psbt version 16 (corresponding to address # tb1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq4hu3px). # The app should accept it nonetheless. @@ -563,15 +739,218 @@ def test_sign_psbt_with_segwit_v16(client: Client, comm: SpeculosClient): psbt = PSBT() psbt.deserialize(psbt_b64) - wallet = PolicyMapWallet( + wallet = WalletPolicy( "", - "wpkh(@0)", + "wpkh(@0/**)", [ - "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" ], ) - with automation(comm, "automations/sign_with_default_wallet_accept.json"): - hww_sigs = client.sign_psbt(psbt, wallet, None) + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) assert len(hww_sigs) == 1 + + +def test_sign_psbt_with_external_inputs(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + + instructions = [sign_psbt_instruction_approve_external_inputs(firmware), + sign_psbt_instruction_approve_external_inputs_2(firmware), + sign_psbt_instruction_approve_external_inputs_2(firmware)] + # PSBT obtained by joining pkh-1to1.psbt, tr-1to2.psbt, wpkh-1to2.psbt. + # We sign it with each of the respective wallets; therefore it must show the "external inputs" warning each time. + psbt_b64 = "cHNidP8BAP0yAQIAAAADobgj0jNtaUtJNO+bblt94XoFUT2oop2wKi7Lx6mm/m0BAAAAAP3///9RIsLN5oI+VXVBdbksnFegqOGsg8OOF4f9Oh/zNI6VEwEAAAAA/f///3oqmXlWwJ+Op/0oGcGph7sU4iv5rc2vIKiXY3Is7uJkAQAAAAD9////BaCGAQAAAAAAFgAUE5m4oJhHoDmwNS9Y0hLBgLqxf3dV/6cAAAAAACJRIAuOdIa8MGoK77enwArwQFVC2xrNc+7MqCdxzPX+XrYPeEEPAAAAAAAZdqkUE9fVgWaUbD7AIpNAZtjA0RHRu0GIrHQ4IwAAAAAAFgAU6zj6m4Eo+B8m6V7bDF/66oNpD+Sguw0AAAAAABl2qRQ0Sg9IyhUOwrkDgXZgubaLE6ZwJoisAAAAAAABASunhqkAAAAAACJRINj08dGJltthuxyvVCPeJdih7unJUNN+b/oCMBLV5i4NIRYhLqKFalzxEOZqK+nXNTFHk/28s4iyuPE/K2remC569RkA9azC/VYAAIABAACAAAAAgAEAAAAAAAAAARcgIS6ihWpc8RDmaivp1zUxR5P9vLOIsrjxPytq3pguevUAAQCMAgAAAAHsIw5TCVJWBSokKCcO7ASYlEsQ9vHFePQxwj0AmLSuWgEAAAAXFgAUKBU5gg4t6XOuQbpgBLQxySHE2G3+////AnJydQAAAAAAF6kUyLkGrymMcOYDoow+/C+uGearKA+HQEIPAAAAAAAZdqkUy65bUM+Tnm9TG4prer14j+FLApeIrITyHAAiBgLuhgggfiEChCb2nnZEfX49XgdwSfXmg8MTbCMUdipHGBj1rML9LAAAgAEAAIAAAACAAAAAAAAAAAAAAQB9AgAAAAGvv64GWQ90H/GvWbasRhEmM2pMSoLbVT32/vq3N6wz8wEAAAAA/f///wJwEQEAAAAAACIAIP3uRBxW5bBtDfgsEkxwcBSlyhlli+C5hWvKFvHtMln3pfQwAAAAAAAWABQ6+EKa1ZVKpe6KM8mD/YoehnmSSwAAAAABAR+l9DAAAAAAABYAFDr4QprVlUql7oozyYP9ih6GeZJLIgYD7iw9mOsfk8Chqo5aQAm3Dre0Tq0V8WZvE2sBKtWNMGgY9azC/VQAAIABAACAAAAAgAEAAAAIAAAAAAABBSACkIHs5WFqocuZMZ/Eh07+5H8IzrpfYARjbIxDQJpfCiEHApCB7OVhaqHLmTGfxIdO/uR/CM66X2AEY2yMQ0CaXwoZAPWswv1WAACAAQAAgAAAAIABAAAAAgAAAAAAIgICKexHcnEx7SWIogxG7amrt9qm9J/VC6/nC5xappYcTswY9azC/VQAAIABAACAAAAAgAEAAAAKAAAAAAA=" + psbt = PSBT() + psbt.deserialize(psbt_b64) + + wallets = [ + WalletPolicy( + "", + "pkh(@0/**)", + [ + "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT" + ], + ), + WalletPolicy( + "", + "tr(@0/**)", + [ + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U" + ], + ), + WalletPolicy( + "", + "wpkh(@0/**)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" + ], + ) + ] + + index = 0 + for wallet, text in zip(wallets, instructions): + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=text, + testname=f"{test_name}_{index}") + index += 1 + + assert len(hww_sigs) == 1 + + +def test_sign_psbt_miniscript_multikey(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # An earlier (unreleased) version of the app had issues in recognizing the internal key in + # wallets with multiple internal keys. This caused the app not to recognize some inputs as + # internal and refuse signing. + # This test avoid regressions. + + psbt_b64 = "cHNidP8BAH0CAAAAAc73gS9SovmC2TiOljy8430GM1piHwQ2qexyVay941KiAAAAAAD9////AoRMiQAAAAAAIgAgCNTAdTDWmzUxjznipRc1U4uQGLRoZrO78XwYXYUabZpAQg8AAAAAABYAFERtNGMisyaTdIH6qfcs0sgquVa9AAAAAAABAIkCAAAAARZzljeqA1KqIrHu0Dlk1eHOMjicZvTJNJyJr/EKGRpbAAAAAAD+////AoCWmAAAAAAAIgAg7J/0qnaRhypwlt/UpEpsEFWGdEW1xaoG8zEdfnMy+LjbWm0pAQAAACJRIDnrpLTPwAtLr+lUmuKj0BponWuHmsIjhT94mb7F0QNeaQAAAAEBK4CWmAAAAAAAIgAg7J/0qnaRhypwlt/UpEpsEFWGdEW1xaoG8zEdfnMy+LgBBY4hA1h5yhc6nBs/MA7Fh/tMxtVNYY4wWE5CXBtTuYgocI8drGQhArcwd+z8NOR+OK4rn4KGkQCCFM8COcy6P8m/T41alSKMrSED2O7HEKG8D9F+VNLOnTTQL43jAvbEPioS3GbGDIQceJVnIQMcumfE5xIqwJgWWRP5G5iLnJRWKeGzi7yANv+se/rxEmisIgYCtzB37Pw05H44riufgoaRAIIUzwI5zLo/yb9PjVqVIowY9azC/SwAAIABAACAAAAAgAIAAAADAAAAIgYDHLpnxOcSKsCYFlkT+RuYi5yUVinhs4u8gDb/rHv68RIMoPWInwAAAAADAAAAIgYDWHnKFzqcGz8wDsWH+0zG1U1hjjBYTkJcG1O5iChwjx0Y9azC/SwAAIABAACAAAAAgAAAAAADAAAAIgYD2O7HEKG8D9F+VNLOnTTQL43jAvbEPioS3GbGDIQceJUMt6EhtAAAAAADAAAAAAEBjiECUzcaIHTkAJE5L9bKuknM0NEVGMEJZh9J+AglLIn3FWSsZCECmjJ4GqB1Hs3Dr/H+FWE3rSzge5+iVTuf+FA3DBpG3SWtIQL+762PyBkOL51EV+NoTOccB+ABFJtDgCJ3I79tiEq7cGchAuWvSvOsaVtJcvo5AFkgH7RZXYD4+VU+4x4MqG2IiTPcaKwiAgJTNxogdOQAkTkv1sq6SczQ0RUYwQlmH0n4CCUsifcVZBj1rML9LAAAgAEAAIAAAACAAQAAAAEAAAAiAgKaMngaoHUezcOv8f4VYTetLOB7n6JVO5/4UDcMGkbdJRj1rML9LAAAgAEAAIAAAACAAwAAAAEAAAAiAgLlr0rzrGlbSXL6OQBZIB+0WV2A+PlVPuMeDKhtiIkz3Ayg9YifAQAAAAEAAAAiAgL+762PyBkOL51EV+NoTOccB+ABFJtDgCJ3I79tiEq7cAy3oSG0AQAAAAEAAAAAAA==" + psbt = PSBT() + psbt.deserialize(psbt_b64) + + wallet = WalletPolicy( + "Me and Bob or me and Carl", + "wsh(c:andor(pk(@0/<0;1>/*),pk_k(@1/**),and_v(v:pk(@0/<2;3>/*),pk_k(@2/**))))", + [ + "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT", + "tpubDDcwGjxKph1xkiAnzvpgdnTeaEhVnH9d766yqvd16JN7EmPW9qSwnbco7kZwPd7UbyEwRojYGUaHT1UULbdqAjGQzeCy3qdZEwZLRmpzwZV", + "tpubDCDraP1C24GGX6BHCewLBWbKQRNGACfz8JjyKXYoZjEJWeGV5Ng43FL31MryaiqeBjdC5dPUZD2zqnmMe6gqrYEstnu8pmJZYp3AQmhzQ6G", + ] + ) + + wallet_hmac = bytes.fromhex( + "e139a96195e18bc61e8cda72d11b3f75d3084a5c893990ca74a152206064792d" + ) + + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) + + assert len(result) == 2 + + +def test_sign_psbt_singlesig_pkh_1to1_other_encodings(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): + # same as test_sign_psbt_singlesig_pkh_1to1, but the psbt is passed as bytes or base64 string + + psbt_obj = open_psbt_from_file(f"{tests_root}/psbt/singlesig/pkh-1to1.psbt") + + wallet = WalletPolicy( + "", + "pkh(@0/**)", + [ + "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT" + ], + ) + + psbt_b64 = psbt_obj.serialize() + psbt_bytes = base64.b64decode(psbt_b64) + + index = 0 + for psbt in [psbt_b64, psbt_bytes]: + # expected sigs: + # #0: + # "pubkey" : "02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718", + # "signature" : "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=f"{test_name}_{index}") + index += 1 + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718"), + signature=bytes.fromhex( + "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + ) + ) + )] + + +def test_sign_psbt_tr_script_pk_sighash_all(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # Transaction signed with SIGHASH_ALL, therefore producing a 65-byte signature + + wallet = WalletPolicy( + name="Taproot foreign internal key, and our script key", + descriptor_template="tr(@0/**,pk(@1/**))", + keys_info=[ + "[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + ], + ) + + wallet_hmac = bytes.fromhex( + "dae925660e20859ed8833025d46444483ce264fdb77e34569aabe9d590da8fb7" + ) + + psbt = PSBT() + psbt.deserialize("cHNidP8BAFICAAAAAR/BzFdxy4OGDMVtlLz+2ThgjBf2NmJDW0HpxE/8/TFCAQAAAAD9////ATkFAAAAAAAAFgAUqo7zdMr638p2kC3bXPYcYLv9nYUAAAAAAAEBK0wGAAAAAAAAIlEg/AoQ0wjH5BtLvDZC+P2KwomFOxznVaDG0NSV8D2fLaQBAwQBAAAAIhXBUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUjIGsW6MH5efpMwPBbajAK//+UFFm28g3nfeVbAWDvjkysrMAhFlAXEIvs6o3txTALsiOGs6swNnrCYvnOXlgybrg+OiL1HQB2IjpuMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIRZrFujB+Xn6TMDwW2owCv//lBRZtvIN533lWwFg745MrD0BCS7aAzYX4hDuf30ON4pASuocSLVqoQMCK+z3dG5HAKT1rML9MAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAARcgUBcQi+zqje3FMAuyI4azqzA2esJi+c5eWDJuuD46IvUBGCAJLtoDNhfiEO5/fQ43ikBK6hxItWqhAwIr7Pd0bkcApAAA") + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) + + assert len(result) == 1 + + # sighash verified with bitcoin-core (real transaction) + sighash0 = bytes.fromhex("39CEACF28A980B46749DD416EABE6E380C0C3742D19AA3E2ABB64F0840251E5B") + + assert len(result) == 1 + + idx0, partial_sig0 = result[0] + + assert idx0 == 0 + assert partial_sig0.pubkey == bytes.fromhex("6b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac") + assert partial_sig0.tapleaf_hash == bytes.fromhex( + "092eda033617e210ee7f7d0e378a404aea1c48b56aa103022becf7746e4700a4") + + assert len(partial_sig0.signature) == 65 + assert partial_sig0.signature[-1] == 1 # SIGHASH_ALL + + assert bip0340.schnorr_verify(sighash0, partial_sig0.pubkey, partial_sig0.signature[:64]) + + +def test_sign_psbt_against_wrong_tapleaf_hash(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # Versions 2.1.2, 2.1.3 and 2.2.0 incorrectly derived keys for policies with keys whose + # derivation doesn't end in /** or /<0;1>/*. + wallet = WalletPolicy( + name="Used to return a wrong tapleaf_hash", + descriptor_template="tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + keys_info=[ + "tpubDD7LLJNCVTKQiB41FH3NyJPzMUNroRtzzY3WFAzKZDikrMpw9PJTi6A2Yes5Tamin4wsgJ4JLsj2AVUSvQqP2T6q3bztu7obRuU3Lrh4eTw", + "[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK", + "tpubDCczwGSwQAF9Z5gTqL3tjznCFC9De5kFBLGdJJuj3UogVyYXVG7HuFdNsvJ9oDtvn4waeawS8XvRpBfbAZaDv1pGRiZdc9qnQhLKTS8eWXH" + ] + ) + + wallet_hmac = bytes.fromhex( + "649d8ef6721d63046144f4f05d156655bc42fb0fe4a85020ac524cd79973c9d1") + + psbt_b64 = "cHNidP8BAH0CAAAAAYBaTWS0c6cz/bqhz0gkvw2CoOJ9/y4sKh5CovAYdw38AAAAAAD9////ArFTiQAAAAAAIlEgUM92rzrvv69scu7om669/XHG88cGJbYVeMikCkWmlxRAQg8AAAAAABYAFJDl+lvev62lopbLzjGdWRDjAYvgAAAAAAABASuAlpgAAAAAACJRINN8fQAgAcXxI9eoGZhPGUUGNjw4g9EeoiMqhcVBO5VLQhXBw4BHaz5Rb16iJhge9exK1RkvpgSBkmRu83QIUOE6J65bgplv5s8b9DhoURGBxkyWW3v18W8Aes7FLe3lKI+SJUkgIRdstYjTZ0gDOmYhQWnhPLeSgxFVT7+P2Da5rOQ5ofSsIO+9DR1rAsJPsa5gnGaxlTcLz+FasRFEtS1GPP9S4AEHulGdUrLAQhXBw4BHaz5Rb16iJhge9exK1RkvpgSBkmRu83QIUOE6J66x3SqLzSBzMBF+yv8nlwb7y8wznx3ph3mkNbEShEEVdUcgnmRvueBFJGCUTkn4hp+audqQgg2l1ThBr54ScaO8+c6sIEOg+6Z7BaL8AdExL0y1lU+WzQLqlFNMBvCuB5kbfXn6ulKcwCEWIRdstYjTZ0gDOmYhQWnhPLeSgxFVT7+P2Da5rOQ5ofQ9AbHdKovNIHMwEX7K/yeXBvvLzDOfHemHeaQ1sRKEQRV19azC/TAAAIABAACAAAAAgAIAAIACAAAAAwAAACEWQ6D7pnsFovwB0TEvTLWVT5bNAuqUU0wG8K4HmRt9efotAVuCmW/mzxv0OGhREYHGTJZbe/XxbwB6zsUt7eUoj5IlB4DpBQAAAAADAAAAIRaeZG+54EUkYJROSfiGn5q52pCCDaXVOEGvnhJxo7z5zj0BW4KZb+bPG/Q4aFERgcZMllt79fFvAHrOxS3t5SiPkiX1rML9MAAAgAEAAIAAAACAAgAAgAAAAAADAAAAIRbDgEdrPlFvXqImGB717ErVGS+mBIGSZG7zdAhQ4Tonrg0As/NWDAAAAAADAAAAIRbvvQ0dawLCT7GuYJxmsZU3C8/hWrERRLUtRjz/UuABBy0Bsd0qi80gczARfsr/J5cG+8vMM58d6Yd5pDWxEoRBFXUHgOkFAgAAAAMAAAABFyDDgEdrPlFvXqImGB717ErVGS+mBIGSZG7zdAhQ4TonrgEYIALiXeErTe+AoRAtQnHQX7jXI4YbZBhruweZSvu1pjAnAAEFIDUB03lc0pILNyKsR6rhmUOmt4haBLLEqg+PUngRkh1tAQaUAcBGIN2D5P/RpWDLWr8u0Sot1Nvr5XYq9Q/AMKqMEXmB3147rCCnLb87WO/OHvM80hvKtQd/5eDRTyap/Nn6wGXiShz23rpSnAHASCB9x/N9yMHBTLoCp176y3zxfQ4uhFjr2IrFWzh6EZDhV6wgPMPmbiXzWmycjxYW5CemUduJTNaIRBRpeKGxZocLVzu6UZ1SsiEHNQHTeVzSkgs3IqxHquGZQ6a3iFoEssSqD49SeBGSHW0NALPzVgwBAAAAAAAAACEHPMPmbiXzWmycjxYW5CemUduJTNaIRBRpeKGxZocLVzstAQImDD+peKARccErGHSxVp2Aq1+VWjA681kfcLPjYIfHB4DpBQMAAAAAAAAAIQd9x/N9yMHBTLoCp176y3zxfQ4uhFjr2IrFWzh6EZDhVz0BAiYMP6l4oBFxwSsYdLFWnYCrX5VaMDrzWR9ws+Ngh8f1rML9MAAAgAEAAIAAAACAAgAAgAMAAAAAAAAAIQenLb87WO/OHvM80hvKtQd/5eDRTyap/Nn6wGXiShz23i0BWuE6OIQBkBYr0ks+isRVRxvEs10ErP2gC9qtZAt0KE8HgOkFAQAAAAAAAAAhB92D5P/RpWDLWr8u0Sot1Nvr5XYq9Q/AMKqMEXmB3147PQFa4To4hAGQFivSSz6KxFVHG8SzXQSs/aAL2q1kC3QoT/Wswv0wAACAAQAAgAAAAIACAACAAQAAAAAAAAAAAA==" + + result = client.sign_psbt(psbt_b64, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_7(firmware), + testname=test_name) + + assert len(result) == 2 + + # This test assumes that keys are yielded in the same order as the internal placeholders + + part_sig_1 = result[0][1] + assert part_sig_1.pubkey == bytes.fromhex( + "21176cb588d36748033a66214169e13cb7928311554fbf8fd836b9ace439a1f4") + # version 2.2.0 returned b2ee0699c6063e37ee778bd87774660b3f4c62b47473f28a0d32e6ff2bccd5db for part_sig_1.tapleaf_hash + assert part_sig_1.tapleaf_hash == bytes.fromhex( + "b1dd2a8bcd207330117ecaff279706fbcbcc339f1de98779a435b11284411575") + + part_sig_2 = result[1][1] + assert part_sig_2.pubkey == bytes.fromhex( + "9e646fb9e0452460944e49f8869f9ab9da90820da5d53841af9e1271a3bcf9ce") + assert part_sig_2.tapleaf_hash == bytes.fromhex( + "5b82996fe6cf1bf43868511181c64c965b7bf5f16f007acec52dede5288f9225") diff --git a/tests/test_sign_psbt_v1.py b/tests/test_sign_psbt_v1.py new file mode 100644 index 000000000..f8af8c5c5 --- /dev/null +++ b/tests/test_sign_psbt_v1.py @@ -0,0 +1,480 @@ +# Tests using the V1 version of the wallet policy language, used before version 2.1.0 of the app +# Make sure we remain compatible for some time. + +import pytest + +import threading + +from decimal import Decimal + +from typing import List + +from pathlib import Path + +from ledger_bitcoin import WalletPolicy, MultisigWallet, AddressType, WalletType, PartialSignature +from ledger_bitcoin.exception.errors import IncorrectDataError, NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException + +from ledger_bitcoin.psbt import PSBT +from ledger_bitcoin.wallet import AddressType + +from test_utils import bip0340, txmaker + +from embit.script import Script +from embit.networks import NETWORKS +from ragger.navigator import Navigator, NavInsID +from ragger.error import ExceptionRAPDU +from ragger.firmware import Firmware + +import requests +import json + +from ragger_bitcoin import RaggerClient + +from .instructions import * + +tests_root: Path = Path(__file__).parent + + +def open_psbt_from_file(filename: str) -> PSBT: + raw_psbt_base64 = open(filename, "r").read() + + psbt = PSBT() + psbt.deserialize(raw_psbt_base64) + return psbt + + +def test_sign_psbt_singlesig_pkh_1to1_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a legacy 1-input 1-output spend (no change address) + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/pkh-1to1.psbt") + + wallet = WalletPolicy( + "", + "pkh(@0)", + [ + "[f5acc2fd/44'/1'/0']tpubDCwYjpDhUdPGP5rS3wgNg13mTrrjBuG8V9VpWbyptX6TRPbNoZVXsoVUSkCjmQ8jJycjuDKBb9eataSymXakTTaGifxR6kmVsfFehH1ZgJT/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + # expected sigs: + # #0: + # "pubkey" : "02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718", + # "signature" : "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("02ee8608207e21028426f69e76447d7e3d5e077049f5e683c3136c2314762a4718"), + signature=bytes.fromhex( + "3045022100e55b3ca788721aae8def2eadff710e524ffe8c9dec1764fdaa89584f9726e196022012a30fbcf9e1a24df31a1010356b794ab8de438b4250684757ed5772402540f401" + ) + ) + )] + + +def test_sign_psbt_singlesig_sh_wpkh_1to2_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a wrapped segwit 1-input 2-output spend (1 change address) + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/sh-wpkh-1to2.psbt") + + wallet = WalletPolicy( + "", + "sh(wpkh(@0))", + [ + "[f5acc2fd/49'/1'/0']tpubDC871vGLAiKPcwAw22EjhKVLk5L98UGXBEcGR8gpcigLQVDDfgcYW24QBEyTHTSFEjgJgbaHU8CdRi9vmG4cPm1kPLmZhJEP17FMBdNheh3/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + # expected sigs: + # #0: + # "pubkey" : "024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67", + # "signature" : "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("024ba3b77d933de9fa3f9583348c40f3caaf2effad5b6e244ece8abbfcc7244f67"), + signature=bytes.fromhex( + "30440220720722b08489c2a50d10edea8e21880086c8e8f22889a16815e306daeea4665b02203fcf453fa490b76cf4f929714065fc90a519b7b97ab18914f9451b5a4b45241201" + ) + ) + )] + + +def test_sign_psbt_singlesig_wpkh_1to2_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a legacy 1-input 2-output spend (1 change address) + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") + + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) + + # expected sigs + # #0: + # "pubkey" : "03ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d3068", + # "signature" : "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03ee2c3d98eb1f93c0a1aa8e5a4009b70eb7b44ead15f1666f136b012ad58d3068"), + signature=bytes.fromhex( + "3045022100ab44f34dd7e87c9054591297a101e8500a0641d1d591878d0d23cf8096fa79e802205d12d1062d925e27b57bdcf994ecf332ad0a8e67b8fe407bab2101255da632aa01" + ) + ) + )] + + +def test_sign_psbt_singlesig_wpkh_2to2_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a legacy 2-input 2-output spend (1 change address) + + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-2to2.psbt") + + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + # expected sigs + # #0: + # "pubkey" : "03455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3", + # "signature" : "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" + # #1: + # "pubkey" : "0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0", + # "signature" : "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("03455ee7cedc97b0ba435b80066fc92c963a34c600317981d135330c4ee43ac7a3"), + signature=bytes.fromhex( + "304402206b3e877655f08c6e7b1b74d6d893a82cdf799f68a5ae7cecae63a71b0339e5ce022019b94aa3fb6635956e109f3d89c996b1bfbbaf3c619134b5a302badfaf52180e01" + ) + ) + ), ( + 1, + PartialSignature( + pubkey=bytes.fromhex("0271b5b779ad870838587797bcf6f0c7aec5abe76a709d724f48d2e26cf874f0a0"), + signature=bytes.fromhex( + "3045022100e2e98e4f8c70274f10145c89a5d86e216d0376bdf9f42f829e4315ea67d79d210220743589fd4f55e540540a976a5af58acd610fa5e188a5096dfe7d36baf3afb94001" + ) + ) + )] + + +def test_sign_psbt_multisig_wsh_v1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + wallet = MultisigWallet( + name="Cold storage", + address_type=AddressType.WIT, + threshold=2, + keys_info=[ + f"[76223a6e/48'/1'/0'/2']tpubDE7NQymr4AFtewpAsWtnreyq9ghkzQBXpCZjWLFVRAvnbf7vya2eMTvT2fPapNqL8SuVvLQdbUbMfWLVDCZKnsEBqp6UK93QEzL8Ck23AwF/**", + f"[f5acc2fd/48'/1'/0'/2']tpubDFAqEGNyad35aBCKUAXbQGDjdVhNueno5ZZVEn3sQbW5ci457gLR7HyTmHBg93oourBssgUxuWz1jX5uhc1qaqFo9VsybY1J5FuedLfm4dK/**", + ], + version=WalletType.WALLET_POLICY_V1 + ) + + wallet_hmac = bytes.fromhex( + "d6434852fb3caa7edbd1165084968f1691444b3cfc10cf1e431acbbc7f48451f" + ) + + psbt = open_psbt_from_file(f"{tests_root}/psbt/multisig/wsh-2of2.psbt") + + result = client.sign_psbt(psbt, wallet, wallet_hmac, navigator, + instructions=sign_psbt_instruction_approve_6(firmware), + testname=test_name) + + assert result == [( + 0, + PartialSignature( + pubkey=bytes.fromhex("036b16e8c1f979fa4cc0f05b6a300affff941459b6f20de77de55b0160ef8e4cac"), + signature=bytes.fromhex( + "304402206ab297c83ab66e573723892061d827c5ac0150e2044fed7ed34742fedbcfb26e0220319cdf4eaddff63fc308cdf53e225ea034024ef96de03fd0939b6deeea1e8bd301" + ) + ) + )] + + +def test_sign_psbt_taproot_1to2_v1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + # PSBT for a p2tr 1-input 2-output spend (1 change address) + + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/tr-1to2-sighash-all.psbt") + + wallet = WalletPolicy( + "", + "tr(@0)", + [ + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert len(result) == 1 + + # Unlike other transactions, Schnorr signatures are not deterministic (unless the randomness is removed) + # Therefore, for this testcase we hard-code the sighash (which was validated with Bitcoin Core 22.0 when the + # transaction was sent), and we verify the produced Schnorr signature with the reference bip340 implementation. + + # sighash verified with bitcoin-core + sighash0 = bytes.fromhex("7A999E5AD6F53EA6448E7026061D3B4523F957999C430A5A492DFACE74AE31B6") + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0_psbt = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + + idx0, partial_sig0 = result[0] + assert idx0 == 0 + assert partial_sig0.pubkey == pubkey0_psbt + + # the sighash 0x01 is appended to the signature + assert len(partial_sig0.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x01 + + assert bip0340.schnorr_verify(sighash0, pubkey0_psbt, partial_sig0.signature[:-1]) + + +def test_sign_psbt_singlesig_wpkh_4to3_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for a segwit 4-input 3-output spend (1 change address) + # this test also checks that addresses, amounts and fees shown on screen are correct + + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + n_ins = 4 + n_outs = 3 + + in_amounts = [10000 + 10000 * i for i in range(n_ins)] + sum_in = sum(in_amounts) + out_amounts = [sum_in // n_outs - i for i in range(n_outs)] + + change_index = 1 + + psbt = txmaker.createPsbt( + wallet, + in_amounts, + out_amounts, + [i == change_index for i in range(n_outs)] + ) + + sum_out = sum(out_amounts) + + assert sum_out < sum_in + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_9(firmware), + testname=test_name) + + assert len(result) == n_ins + + +def test_sign_psbt_singlesig_large_amount_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # Test with a transaction with an extremely large amount + + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + in_amounts = [21_000_000*100_000_000] + out_amounts = [21_000_000*100_000_000 - 100_000] + + psbt = txmaker.createPsbt(wallet, in_amounts, out_amounts, [False]) + + sum_in = sum(in_amounts) + sum_out = sum(out_amounts) + + assert sum_out < sum_in + + result = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + assert len(result) == 1 + + +def test_sign_psbt_singlesig_wpkh_512to256_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str, enable_slow_tests: bool): + # PSBT for a transaction with 512 inputs and 256 outputs (maximum currently supported in the app) + # Very slow test (esp. with DEBUG enabled), so disabled unless the --enableslowtests option is used + + if not enable_slow_tests: + pytest.skip() + + n_inputs = 512 + n_outputs = 256 + + wallet = WalletPolicy( + "", + "tr(@0)", + [ + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + psbt = txmaker.createPsbt( + wallet, + [10000 + 10000 * i for i in range(n_inputs)], + [999 + 99 * i for i in range(n_outputs)], + [i == 42 for i in range(n_outputs)] + ) + + result = client.sign_psbt(psbt, wallet, None, None) + + assert len(result) == n_inputs + + +def test_sign_psbt_fail_11_changes_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for transaction with 11 change addresses; the limit is 10, so it must fail with NotSupportedError + # before any user interaction + + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + psbt = txmaker.createPsbt( + wallet, + [11 * 100_000_000 + 1234], + [100_000_000] * 11, + [True] * 11, + ) + + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_tap(firmware), + testname=test_name) + + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + +def test_sign_psbt_fail_wrong_non_witness_utxo_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # PSBT for transaction with the wrong non-witness utxo for an input. + # It must fail with IncorrectDataError before any user interaction. + + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + psbt = txmaker.createPsbt( + wallet, + [3 * 100_000_000], + [1 * 100_000_000, 2 * 100_000_000], + [False, True] + ) + + # Modify the non_witness_utxo so that the txid does not matches + wit = psbt.inputs[0].non_witness_utxo + wit.nLockTime = wit.nLockTime ^ 1 # change one bit of nLockTime arbitrarily to change the txid + wit.rehash() + psbt.inputs[0].non_witness_utxo = wit + + client._no_clone_psbt = True + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + assert len(e.value.data) == 0 + client._no_clone_psbt = False + + +def test_sign_psbt_with_opreturn_v1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + psbt_b64 = "cHNidP8BAKMCAAAAAZ0gZDu3l28lrZWbtsuoIfI07zpsaXXMe6sMHHJn03LPAAAAAAD+////AgAAAAAAAAAASGpGVGhlIFRpbWVzIDAzL0phbi8yMDA5IENoYW5jZWxsb3Igb24gYnJpbmsgb2Ygc2Vjb25kIGJhaWxvdXQgZm9yIGJhbmtzLsGVmAAAAAAAFgAUK5M/aeXrJEofBL7Uno7J5OyTvJ8AAAAAAAEAcQIAAAABnpp88I3RXEU5b28rI3GGAXaWkk+w1sEqWDXFXdacKg8AAAAAAP7///8CgJaYAAAAAAAWABQTR+gqA3tduzjPjEdZ8kKx9cfgmvNabSkBAAAAFgAUCA6eZPSQK9gnq8ngOSaQ0ZdPeIVBAAAAAQEfgJaYAAAAAAAWABQTR+gqA3tduzjPjEdZ8kKx9cfgmiIGAny3XTSwBcTrn2K78sRX12OOgT51fvzsj6aGd9lQtjZiGPWswv1UAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDGZuJ2DVvV+HOOAoSBc8oYG2+qJhVsRw9/s+4oaUzVokY9azC/VQAAIABAACAAAAAgAEAAAABAAAAAA==" + psbt = PSBT() + psbt.deserialize(psbt_b64) + + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) + + assert len(hww_sigs) == 1 + + +def test_sign_psbt_with_segwit_v16_v1(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + # This psbt contains an output with future psbt version 16 (corresponding to address + # tb1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq4hu3px). + # The app should accept it nonetheless. + + psbt_b64 = "cHNidP8BAH0CAAAAAZvg4s1Yxz9DddwBeI+qqU7hcldqGSgWPXuZZReEFYvKAAAAAAD+////AqdTiQAAAAAAFgAUK5M/aeXrJEofBL7Uno7J5OyTvJ9AQg8AAAAAACJgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAHECAAAAAYWOSCXXmA0ztidPI5A6FskW99o7nWNVeFP7rXND5B9aAAAAAAD+////AoCWmAAAAAAAFgAUE0foKgN7Xbs4z4xHWfJCsfXH4JrzWm0pAQAAABYAFF7XSHCIZoptcIrXIWce1tKqp11EaQAAAAEBH4CWmAAAAAAAFgAUE0foKgN7Xbs4z4xHWfJCsfXH4JoiBgJ8t100sAXE659iu/LEV9djjoE+dX787I+mhnfZULY2Yhj1rML9VAAAgAEAAIAAAACAAAAAAAAAAAAAIgIDGZuJ2DVvV+HOOAoSBc8oYG2+qJhVsRw9/s+4oaUzVokY9azC/VQAAIABAACAAAAAgAEAAAABAAAAAAA=" + psbt = PSBT() + psbt.deserialize(psbt_b64) + + wallet = WalletPolicy( + "", + "wpkh(@0)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**" + ], + version=WalletType.WALLET_POLICY_V1 + ) + + hww_sigs = client.sign_psbt(psbt, wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + assert len(hww_sigs) == 1 diff --git a/tests/test_sign_psbt_with_sighash_types.py b/tests/test_sign_psbt_with_sighash_types.py new file mode 100644 index 000000000..abbcd2beb --- /dev/null +++ b/tests/test_sign_psbt_with_sighash_types.py @@ -0,0 +1,614 @@ +import pytest +from pathlib import Path +from ledger_bitcoin import WalletPolicy +from ledger_bitcoin.exception.errors import NotSupportedError +from ledger_bitcoin.exception.device_exception import DeviceException +from ledger_bitcoin.psbt import PSBT +from test_utils import bip0340 +from ragger.navigator import Navigator, NavInsID +from ragger.error import ExceptionRAPDU +from ragger.firmware import Firmware +from ragger_bitcoin import RaggerClient + +from .instructions import sign_psbt_instruction_approve, sign_psbt_instruction_approve_2, sign_psbt_instruction_approve_4, sign_psbt_instruction_approve_10 +tests_root: Path = Path(__file__).parent + +tr_wallet = WalletPolicy( + "", + "tr(@0/**)", + [ + "[f5acc2fd/86'/1'/0']tpubDDKYE6BREvDsSWMazgHoyQWiJwYaDDYPbCFjYxN3HFXJP5fokeiK4hwK5tTLBNEDBwrDXn8cQ4v9b2xdW62Xr5yxoQdMu1v6c7UDXYVH27U" + ], +) + +wpkh_wallet = WalletPolicy( + "", + "wpkh(@0/**)", + [ + "[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P" + ], +) + + +# Unlike other transactions, Schnorr signatures are not deterministic (unless the randomness is removed) +# Therefore, for this testcase we hard-code the sighash, and we verify the produced Schnorr signature with the reference bip340 implementation. +sighash_bitcoin_core_all_0 = bytes.fromhex("2221AA462110C77A8E2DD34C3681BAA9BFFF6553B4C609EC7E3D8FF9B1D18D69") +sighash_bitcoin_core_all_1 = bytes.fromhex("D47D3FA22B4F6C50521C49E1A42E8CB10689540A227491A8FC5AD0A6E413063E") +sighash_bitcoin_core_none_0 = bytes.fromhex("965976D58A387369D970F0B6560B144E1B721D41E04675592C41AC35D30D2A56") +sighash_bitcoin_core_none_1 = bytes.fromhex("67E85534A12E4054F4AFAA434D7A7C38123DA6909DF7E45DDB9945F7B8D832D0") +sighash_bitcoin_core_single_0 = bytes.fromhex("F9B834D7FE272F9EACE2FC5F7A97468B024438EF5D55338FC243D5273534A6B5") +sighash_bitcoin_core_single_1 = bytes.fromhex("9A4DDC13C6D0EE10A41D33C6595C63F51AF4C9314387685304F515F790260F78") +sighash_bitcoin_core_all_anyone_0 = bytes.fromhex("09A6559AF84C48C8D5A7984C5A72E53ED88D160AABAE99C18F00E78A55E7EDC7") +sighash_bitcoin_core_all_anyone_1 = bytes.fromhex("9B25C319E12F4755D8A43F3295B8C61B861FB23D7EBF7F9A25E6E8CE3242F939") +sighash_bitcoin_core_none_anyone_0 = bytes.fromhex("8FCEFFAE04D320E05DE04034069FE6AF8C7CBCC93CDE3F187AB0DEC202692735") +sighash_bitcoin_core_none_anyone_1 = bytes.fromhex("A06D37C1C8EEE7EA145F9D8A98CBE79F6BB1691B37F8F26F49F8318F9443B766") +sighash_bitcoin_core_single_anyone_0 = bytes.fromhex("971886B247797E0A616489B449B5E78AE8EC63E54B55727AF626B964DD8F329D") +sighash_bitcoin_core_single_anyone_1 = bytes.fromhex("6B130F2BE5467A8BC36227B8C2A082B46CA24F91A6A6A54AA5EFA4901BE5ADBB") + + +def open_psbt_from_file(filename: str) -> PSBT: + raw_psbt_base64 = open(filename, "r").read() + + psbt = PSBT() + psbt.deserialize(raw_psbt_base64) + return psbt + + +def test_sighash_all_sign_psbt(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + assert len(result) == 2 + + _, partial_sig0 = result[0] + assert len(partial_sig0.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x01 + + _, partial_sig1 = result[1] + assert len(partial_sig1.signature) == 64+1 + assert partial_sig1.signature[-1] == 0x01 + + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_all_input_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") + + psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_1, pubkey1, partial_sig1.signature[:-1]) == 0 + + +def test_sighash_all_output_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") + + psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_1, pubkey1, partial_sig1.signature[:-1]) == 0 + + +def test_sighash_none_sign_psbt(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + assert len(result) == 2 + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert len(partial_sig0.signature) == 64+1 + assert len(partial_sig1.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x02 + assert partial_sig1.signature[-1] == 0x02 + + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_none_input_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-sign.psbt") + psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_1, pubkey1, partial_sig1.signature[:-1]) == 0 + + +def test_sighash_none_output_modified(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-sign.psbt") + psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_single_sign_psbt(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert len(partial_sig0.signature) == 64+1 + assert len(partial_sig1.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x03 + assert partial_sig1.signature[-1] == 0x03 + + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_single_input_modified(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") + psbt.tx.vin[1].nSequence = psbt.tx.vin[1].nSequence - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + assert len(result) == 2 + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) == 0 + + +def test_sighash_single_output_same_index_modified(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") + psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + assert len(result) == 2 + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_single_output_different_index_modified(navigator: Navigator, firmware: Firmware, + client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-sign.psbt") + psbt.tx.vout[1].nValue = psbt.tx.vout[1].nValue - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + assert len(result) == 2 + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_1, pubkey1, partial_sig1.signature[:-1]) == 0 + + +def test_sighash_single_3_ins_2_out(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-3-ins-2-outs.psbt") + + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + +def test_sighash_all_anyone_sign(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert len(partial_sig0.signature) == 64+1 + assert len(partial_sig1.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x81 + assert partial_sig1.signature[-1] == 0x81 + + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_all_anyone_input_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt") + psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_all_anyone_output_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-anyone-can-pay-sign.psbt") + psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_all_anyone_1, pubkey1, partial_sig1.signature[:-1]) == 0 + + +def test_sighash_none_anyone_sign(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert len(partial_sig0.signature) == 64+1 + assert len(partial_sig1.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x82 + assert partial_sig1.signature[-1] == 0x82 + + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_none_anyone_input_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt") + psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_none_anyone_output_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-none-anyone-can-pay-sign.psbt") + psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_none_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_single_anyone_sign(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert len(partial_sig0.signature) == 64+1 + assert len(partial_sig1.signature) == 64+1 + assert partial_sig0.signature[-1] == 0x83 + assert partial_sig1.signature[-1] == 0x83 + + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_0, pubkey0, partial_sig0.signature[:-1]) + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_single_anyone_input_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt") + psbt.tx.vin[0].nSequence = psbt.tx.vin[0].nSequence - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_single_anyone_output_changed(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-single-anyone-can-pay-sign.psbt") + psbt.tx.vout[0].nValue = psbt.tx.vout[0].nValue - 1 + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_4(firmware), + testname=test_name) + + assert len(result) == 2 + + # get the (tweaked) pubkey from the scriptPubKey + pubkey0 = psbt.inputs[0].witness_utxo.scriptPubKey[2:] + pubkey1 = psbt.inputs[1].witness_utxo.scriptPubKey[2:] + + _, partial_sig0 = result[0] + _, partial_sig1 = result[1] + + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_0, pubkey0, partial_sig0.signature[:-1]) == 0 + assert bip0340.schnorr_verify(sighash_bitcoin_core_single_anyone_1, pubkey1, partial_sig1.signature[:-1]) + + +def test_sighash_unsupported(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-unsupported.psbt") + + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + +def test_sighash_unsupported_for_segwitv0(navigator: Navigator, firmware: Firmware, client: + RaggerClient, test_name: str): + psbt = open_psbt_from_file(f"{tests_root}/psbt/sighash/sighash-all-sign.psbt") + + result = client.sign_psbt(psbt, tr_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") + + psbt.inputs[0].sighash = 0 + + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + psbt.inputs[0].sighash = 0x80 + + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + psbt.inputs[0].sighash = 0x84 + + with pytest.raises(ExceptionRAPDU) as e: + client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve(firmware), + testname=test_name) + assert DeviceException.exc.get(e.value.status) == NotSupportedError + assert len(e.value.data) == 0 + + +def test_sighash_segwitv0_sighash1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + expected_sig = b"0E\x02!\x00\xabD\xf3M\xd7\xe8|\x90TY\x12\x97\xa1\x01\xe8P\n\x06A\xd1\xd5\x91\x87\x8d\r#\xcf\x80\x96\xfay\xe8\x02 ]\x12\xd1\x06-\x92^'\xb5{\xdc\xf9\x94\xec\xf32\xad\n\x8eg\xb8\xfe@{\xab!\x01%]\xa62\xaa\x01" + + psbt = open_psbt_from_file(f"{tests_root}/psbt/singlesig/wpkh-1to2.psbt") + psbt.inputs[0].sighash = 1 + result = client.sign_psbt(psbt, wpkh_wallet, None, navigator, + instructions=sign_psbt_instruction_approve_2(firmware), + testname=test_name) + assert result[0][1].signature == expected_sig + + +def test_sighash_segwitv0_sighash2(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + expected_sig = b'0D\x02 o\x86>\xd5\x8b\xb5\xa5\xa2KZ\xcez\xb2\x92\xd0\xce\x04!L_\x8f9\xeb#m3\x9e\xb4\x8d\xc6sK\x02 p\x8d\x95\x0b4B\x02^\xf1nB\xd2\xea\x84b\x14\xc7\x00\x88"\xed\x19o=6.1.1,<7.0.0 +pytest-timeout>=2.1.0,<3.0.0 ledgercomm>=1.1.0,<1.2.0 ecdsa>=0.16.1,<0.17.0 typing-extensions>=3.7,<4.0 -embit>=0.4.10,<0.5.0 +embit>=0.7.0,<0.8.0 mnemonic==0.20 -bip32>=2.1,<3.0 \ No newline at end of file +bip32>=3.4,<4.0 \ No newline at end of file diff --git a/tests_mainnet/test_bip86.py b/tests_mainnet/test_bip86.py index ee19e5781..8bedf1a66 100644 --- a/tests_mainnet/test_bip86.py +++ b/tests_mainnet/test_bip86.py @@ -1,4 +1,4 @@ -from bitcoin_client.ledger_bitcoin import Client, PolicyMapWallet +from bitcoin_client.ledger_bitcoin import Client, WalletPolicy from test_utils import SpeculosGlobals, mnemonic @@ -13,11 +13,11 @@ def test_bip86(client: Client, speculos_globals: SpeculosGlobals): # test for a native taproot wallet (bech32m addresses, per BIP-0086) - wallet = PolicyMapWallet( + wallet = WalletPolicy( name="", - policy_map="tr(@0)", + descriptor_template="tr(@0/**)", keys_info=[ - f"[{fpr}/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/**", + f"[{fpr}/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ", ], ) diff --git a/tests_mainnet/test_dashboard.py b/tests_mainnet/test_dashboard.py index a6f056a41..39e5afb7a 100644 --- a/tests_mainnet/test_dashboard.py +++ b/tests_mainnet/test_dashboard.py @@ -3,12 +3,15 @@ from speculos.client import SpeculosClient -def test_dashboard(comm: SpeculosClient, is_speculos: bool, app_version: str): +def test_dashboard(comm: SpeculosClient, is_speculos: bool, app_version: str, model: str): # Tests that the text shown in the dashboard screens are the expected ones if not is_speculos: pytest.skip("Requires speculos") + if model == "stax": + pytest.skip("No dashboard test for stax") + comm.press_and_release("right") comm.wait_for_text_event("Version") comm.wait_for_text_event(app_version) diff --git a/unit-tests/CMakeLists.txt b/unit-tests/CMakeLists.txt index 0f6967191..5dc70db86 100644 --- a/unit-tests/CMakeLists.txt +++ b/unit-tests/CMakeLists.txt @@ -21,18 +21,17 @@ if(NOT WIN32) endif() if(${CMAKE_VERSION} VERSION_LESS 3.10) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) endif() # project information project(unit_tests - VERSION 0.1 - DESCRIPTION "Unit tests for Ledger Nano application" - LANGUAGES C) - + VERSION 0.1 + DESCRIPTION "Unit tests for Ledger Nano application" + LANGUAGES C) # guard against bad build-type strings -if (NOT CMAKE_BUILD_TYPE) +if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug") endif() @@ -63,7 +62,7 @@ if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ") endif() -add_compile_definitions(TEST DEBUG=0 SKIP_FOR_CMOCKA) +add_compile_definitions(TEST DEBUG=0 SKIP_FOR_CMOCKA PRINTF=printf) if (NOT DEFINED HAVE_LIQUID OR HAVE_LIQUID GREATER_EQUAL 1) message("${BoldCyan}** LIQUID SUPPORT ENABLED **${ColourReset}") @@ -79,6 +78,7 @@ if (CMAKE_C_COMPILER_ID STREQUAL "AppleClang") include_directories(/usr/local/include) link_directories(/usr/local/lib) endif() +include_directories(libs) add_executable(test_apdu_parser test_apdu_parser.c) add_executable(test_base58 test_base58.c) @@ -91,8 +91,13 @@ add_executable(test_parser test_parser.c) add_executable(test_script test_script.c) add_executable(test_wallet test_wallet.c) add_executable(test_write test_write.c) -add_executable(test_crypto test_crypto.c) +# add_executable(test_crypto test_crypto.c) add_executable(test_wif test_wif.c) + +# Mock libraries +add_library(crypto_mocks SHARED libs/crypto_mocks.c) +add_library(sha256 SHARED libs/sha-256.c) + if (HAVE_LIQUID) add_executable(test_liquid_addr test_liquid_addr.c) add_executable(test_liquid test_liquid.c) @@ -101,38 +106,43 @@ if (HAVE_LIQUID) add_executable(test_asset_metadata test_asset_metadata.c) endif() -add_library(apdu_parser ${LIB_TYPE} ../src/boilerplate/apdu_parser.c) -add_library(base58 ${LIB_TYPE} ../src/common/base58.c) -add_library(bip32 ${LIB_TYPE} ../src/common/bip32.c) -add_library(buffer ${LIB_TYPE} ../src/common/buffer.c) -add_library(display_utils ${LIB_TYPE} ../src/ui/display_utils.c) -add_library(format ${LIB_TYPE} ../src/common/format.c) -add_library(parser ${LIB_TYPE} ../src/common/parser.c) -add_library(read ${LIB_TYPE} ../src/common/read.c) -add_library(script ${LIB_TYPE} ../src/common/script.c) -add_library(varint ${LIB_TYPE} ../src/common/varint.c) -add_library(wallet ${LIB_TYPE} ../src/common/wallet.c) -add_library(write ${LIB_TYPE} ../src/common/write.c) -add_library(crypto ${LIB_TYPE} ../src/crypto.c) +# App's libraries +add_library(apdu_parser SHARED ../src/boilerplate/apdu_parser.c) +add_library(base58 SHARED ../src/common/base58.c) +add_library(bip32 SHARED ../src/common/bip32.c) +add_library(buffer SHARED ../src/common/buffer.c) +add_library(display_utils SHARED ../src/ui/display_utils.c) +add_library(format SHARED ../src/common/format.c) +add_library(parser SHARED ../src/common/parser.c) +add_library(read SHARED ../src/common/read.c) +add_library(script SHARED ../src/common/script.c) +add_library(varint SHARED ../src/common/varint.c) +add_library(wallet SHARED ../src/common/wallet.c) +add_library(write SHARED ../src/common/write.c) + +# add_library(crypto SHARED ../src/crypto.c) add_library(wif ${LIB_TYPE} ../src/common/wif.c) add_library(mock_crypto ${LIB_TYPE} mock_crypto.c lib/sha-2/sha-256.c) if (HAVE_LIQUID) add_library(liquid ${LIB_TYPE} ../src/liquid/liquid.c ../src/liquid/liquid_addr.c ../src/liquid/blech32.c ../src/liquid/liquid_assets.c ../src/liquid/contract_parser.c ../src/liquid/liquid_asset_metadata.c) endif() -target_link_libraries(test_apdu_parser PUBLIC cmocka ${GCOV_LIB} apdu_parser) -target_link_libraries(test_base58 PUBLIC cmocka ${GCOV_LIB} base58 mock_crypto) -target_link_libraries(test_bip32 PUBLIC cmocka ${GCOV_LIB} bip32 read) -target_link_libraries(test_bitvector PUBLIC cmocka ${GCOV_LIB}) -target_link_libraries(test_buffer PUBLIC cmocka ${GCOV_LIB} buffer varint read write bip32) -target_link_libraries(test_display_utils PUBLIC cmocka ${GCOV_LIB} display_utils) -target_link_libraries(test_format PUBLIC cmocka ${GCOV_LIB} format) -target_link_libraries(test_parser PUBLIC cmocka ${GCOV_LIB} parser buffer varint read write bip32) -target_link_libraries(test_script PUBLIC cmocka ${GCOV_LIB} script buffer varint read write bip32) -target_link_libraries(test_wallet PUBLIC cmocka ${GCOV_LIB} wallet buffer varint read write bip32 wif base58 crypto mock_crypto) -target_link_libraries(test_write PUBLIC cmocka ${GCOV_LIB} write) -target_link_libraries(test_crypto PUBLIC cmocka ${GCOV_LIB} crypto base58 read mock_crypto) -target_link_libraries(test_wif PUBLIC cmocka ${GCOV_LIB} wif base58 read mock_crypto) +# Mock libraries +target_link_libraries(crypto_mocks PUBLIC sha256) + +# App's libraries +target_link_libraries(test_apdu_parser PUBLIC cmocka gcov apdu_parser) +target_link_libraries(test_base58 PUBLIC cmocka gcov base58) +target_link_libraries(test_bip32 PUBLIC cmocka gcov bip32 read) +target_link_libraries(test_bitvector PUBLIC cmocka gcov) +target_link_libraries(test_buffer PUBLIC cmocka gcov buffer varint read write bip32) +target_link_libraries(test_display_utils PUBLIC cmocka gcov display_utils) +target_link_libraries(test_format PUBLIC cmocka gcov format) +target_link_libraries(test_parser PUBLIC cmocka gcov parser buffer varint read write bip32) +target_link_libraries(test_script PUBLIC cmocka gcov script buffer varint read write bip32) +target_link_libraries(test_wallet PUBLIC cmocka gcov wallet script buffer varint read write bip32 base58 crypto_mocks) +target_link_libraries(test_write PUBLIC cmocka gcov write) + if (HAVE_LIQUID) target_link_libraries(test_liquid_addr PUBLIC cmocka ${GCOV_LIB} liquid buffer varint bip32 base58 read write mock_crypto) target_link_libraries(test_liquid PUBLIC cmocka ${GCOV_LIB} liquid wif wallet script buffer varint bip32 base58 read write crypto mock_crypto) @@ -141,6 +151,7 @@ if (HAVE_LIQUID) target_link_libraries(test_asset_metadata PUBLIC cmocka ${GCOV_LIB} liquid buffer varint read write bip32 mock_crypto) endif() +# target_link_libraries(test_crypto PUBLIC cmocka gcov crypto) add_test(test_apdu_parser test_apdu_parser) add_test(test_base58 test_base58) add_test(test_bip32 test_bip32) @@ -152,7 +163,8 @@ add_test(test_parser test_parser) add_test(test_script test_script) add_test(test_wallet test_wallet) add_test(test_write test_write) -add_test(test_crypto test_crypto) +# TODO: restore tests for crypto.c +# add_test(test_crypto test_crypto) add_test(test_wif test_wif) if (HAVE_LIQUID) add_test(test_liquid_addr test_liquid_addr) diff --git a/unit-tests/libs/crypto_mocks.c b/unit-tests/libs/crypto_mocks.c new file mode 100644 index 000000000..79d09d066 --- /dev/null +++ b/unit-tests/libs/crypto_mocks.c @@ -0,0 +1,10 @@ +#include +#include "crypto_mocks.h" +#include "sha-256.h" + +void crypto_get_checksum(const uint8_t *in, uint16_t in_len, uint8_t out[static 4]) { + uint8_t buffer[32]; + calc_sha_256(buffer, in, in_len); + calc_sha_256(buffer, buffer, 32); + memmove(out, buffer, 4); +} diff --git a/unit-tests/libs/crypto_mocks.h b/unit-tests/libs/crypto_mocks.h new file mode 100644 index 000000000..436076ea5 --- /dev/null +++ b/unit-tests/libs/crypto_mocks.h @@ -0,0 +1,7 @@ +// We're currently unable to compile the app's crypto.c in unit tests. +// This library mocks the functions currently used in other modules that are part of +// the unit tests. + +#include + +void crypto_get_checksum(const uint8_t *in, uint16_t in_len, uint8_t out[static 4]); \ No newline at end of file diff --git a/unit-tests/libs/sha-256.c b/unit-tests/libs/sha-256.c new file mode 100644 index 000000000..3ae891b7a --- /dev/null +++ b/unit-tests/libs/sha-256.c @@ -0,0 +1,224 @@ +// from https://github.com/amosnier/sha-2/blob/0be5e1601b487b2aa6869e2fe12bd30ac2ca543c/sha-256.c + +#include "sha-256.h" + +#define TOTAL_LEN_LEN 8 + +/* + * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here. + * When useful for clarification, portions of the pseudo-code are reproduced here too. + */ + +/* + * @brief Rotate a 32-bit value by a number of bits to the right. + * @param value The value to be rotated. + * @param count The number of bits to rotate by. + * @return The rotated value. + */ +static inline uint32_t right_rot(uint32_t value, unsigned int count) +{ + /* + * Defined behaviour in standard C for all count where 0 < count < 32, which is what we need here. + */ + return value >> count | value << (32 - count); +} + +/* + * @brief Update a hash value under calculation with a new chunk of data. + * @param h Pointer to the first hash item, of a total of eight. + * @param p Pointer to the chunk data, which has a standard length. + * + * @note This is the SHA-256 work horse. + */ +static inline void consume_chunk(uint32_t *h, const uint8_t *p) +{ + unsigned i, j; + uint32_t ah[8]; + + /* Initialize working variables to current hash value: */ + for (i = 0; i < 8; i++) + ah[i] = h[i]; + + /* + * The w-array is really w[64], but since we only need 16 of them at a time, we save stack by + * calculating 16 at a time. + * + * This optimization was not there initially and the rest of the comments about w[64] are kept in their + * initial state. + */ + + /* + * create a 64-entry message schedule array w[0..63] of 32-bit words (The initial values in w[0..63] + * don't matter, so many implementations zero them here) copy chunk into first 16 words w[0..15] of the + * message schedule array + */ + uint32_t w[16]; + + /* Compression function main loop: */ + for (i = 0; i < 4; i++) { + for (j = 0; j < 16; j++) { + if (i == 0) { + w[j] = + (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; + p += 4; + } else { + /* Extend the first 16 words into the remaining 48 words w[16..63] of the + * message schedule array: */ + const uint32_t s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ + (w[(j + 1) & 0xf] >> 3); + const uint32_t s1 = right_rot(w[(j + 14) & 0xf], 17) ^ + right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10); + w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1; + } + const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25); + const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]); + + /* + * Initialize array of round constants: + * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): + */ + static const uint32_t k[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, + 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, + 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, + 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, + 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, + 0xc67178f2}; + + const uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j]; + const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22); + const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]); + const uint32_t temp2 = s0 + maj; + + ah[7] = ah[6]; + ah[6] = ah[5]; + ah[5] = ah[4]; + ah[4] = ah[3] + temp1; + ah[3] = ah[2]; + ah[2] = ah[1]; + ah[1] = ah[0]; + ah[0] = temp1 + temp2; + } + } + + /* Add the compressed chunk to the current hash value: */ + for (i = 0; i < 8; i++) + h[i] += ah[i]; +} + +/* + * Public functions. See header file for documentation. + */ + +void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]) +{ + sha_256->hash = hash; + sha_256->chunk_pos = sha_256->chunk; + sha_256->space_left = SIZE_OF_SHA_256_CHUNK; + sha_256->total_len = 0; + /* + * Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes + * 2..19): + */ + sha_256->h[0] = 0x6a09e667; + sha_256->h[1] = 0xbb67ae85; + sha_256->h[2] = 0x3c6ef372; + sha_256->h[3] = 0xa54ff53a; + sha_256->h[4] = 0x510e527f; + sha_256->h[5] = 0x9b05688c; + sha_256->h[6] = 0x1f83d9ab; + sha_256->h[7] = 0x5be0cd19; +} + +void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len) +{ + sha_256->total_len += len; + + const uint8_t *p = data; + + while (len > 0) { + /* + * If the input chunks have sizes that are multiples of the calculation chunk size, no copies are + * necessary. We operate directly on the input data instead. + */ + if (sha_256->space_left == SIZE_OF_SHA_256_CHUNK && len >= SIZE_OF_SHA_256_CHUNK) { + consume_chunk(sha_256->h, p); + len -= SIZE_OF_SHA_256_CHUNK; + p += SIZE_OF_SHA_256_CHUNK; + continue; + } + /* General case, no particular optimization. */ + const size_t consumed_len = len < sha_256->space_left ? len : sha_256->space_left; + memcpy(sha_256->chunk_pos, p, consumed_len); + sha_256->space_left -= consumed_len; + len -= consumed_len; + p += consumed_len; + if (sha_256->space_left == 0) { + consume_chunk(sha_256->h, sha_256->chunk); + sha_256->chunk_pos = sha_256->chunk; + sha_256->space_left = SIZE_OF_SHA_256_CHUNK; + } else { + sha_256->chunk_pos += consumed_len; + } + } +} + +uint8_t *sha_256_close(struct Sha_256 *sha_256) +{ + uint8_t *pos = sha_256->chunk_pos; + size_t space_left = sha_256->space_left; + uint32_t *const h = sha_256->h; + + /* + * The current chunk cannot be full. Otherwise, it would already have been consumed. I.e. there is space left for + * at least one byte. The next step in the calculation is to add a single one-bit to the data. + */ + *pos++ = 0x80; + --space_left; + + /* + * Now, the last step is to add the total data length at the end of the last chunk, and zero padding before + * that. But we do not necessarily have enough space left. If not, we pad the current chunk with zeroes, and add + * an extra chunk at the end. + */ + if (space_left < TOTAL_LEN_LEN) { + memset(pos, 0x00, space_left); + consume_chunk(h, sha_256->chunk); + pos = sha_256->chunk; + space_left = SIZE_OF_SHA_256_CHUNK; + } + const size_t left = space_left - TOTAL_LEN_LEN; + memset(pos, 0x00, left); + pos += left; + size_t len = sha_256->total_len; + pos[7] = (uint8_t)(len << 3); + len >>= 5; + int i; + for (i = 6; i >= 0; --i) { + pos[i] = (uint8_t)len; + len >>= 8; + } + consume_chunk(h, sha_256->chunk); + /* Produce the final hash value (big-endian): */ + int j; + uint8_t *const hash = sha_256->hash; + for (i = 0, j = 0; i < 8; i++) { + hash[j++] = (uint8_t)(h[i] >> 24); + hash[j++] = (uint8_t)(h[i] >> 16); + hash[j++] = (uint8_t)(h[i] >> 8); + hash[j++] = (uint8_t)h[i]; + } + return sha_256->hash; +} + +void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len) +{ + struct Sha_256 sha_256; + sha_256_init(&sha_256, hash); + sha_256_write(&sha_256, input, len); + (void)sha_256_close(&sha_256); +} \ No newline at end of file diff --git a/unit-tests/libs/sha-256.h b/unit-tests/libs/sha-256.h new file mode 100644 index 000000000..2c1c8047f --- /dev/null +++ b/unit-tests/libs/sha-256.h @@ -0,0 +1,105 @@ +// from https://github.com/amosnier/sha-2/blob/0be5e1601b487b2aa6869e2fe12bd30ac2ca543c/sha-256.h + +#ifndef SHA_256_H +#define SHA_256_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * @brief Size of the SHA-256 sum. This times eight is 256 bits. + */ +#define SIZE_OF_SHA_256_HASH 32 + +/* + * @brief Size of the chunks used for the calculations. + * + * @note This should mostly be ignored by the user, although when using the streaming API, it has an impact for + * performance. Add chunks whose size is a multiple of this, and you will avoid a lot of superfluous copying in RAM! + */ +#define SIZE_OF_SHA_256_CHUNK 64 + +/* + * @brief The opaque SHA-256 type, that should be instantiated when using the streaming API. + * + * @note Although the details are exposed here, in order to make instantiation easy, you should refrain from directly + * accessing the fields, as they may change in the future. + */ +struct Sha_256 { + uint8_t *hash; + uint8_t chunk[SIZE_OF_SHA_256_CHUNK]; + uint8_t *chunk_pos; + size_t space_left; + size_t total_len; + uint32_t h[8]; +}; + +/* + * @brief The simple SHA-256 calculation function. + * @param hash Hash array, where the result is delivered. + * @param input Pointer to the data the hash shall be calculated on. + * @param len Length of the input data, in byte. + * + * @note If all of the data you are calculating the hash value on is available in a contiguous buffer in memory, this is + * the function you should use. + * + * @note If either of the passed pointers is NULL, the results are unpredictable. + */ +void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len); + +/* + * @brief Initialize a SHA-256 streaming calculation. + * @param sha_256 A pointer to a SHA-256 structure. + * @param hash Hash array, where the result will be delivered. + * + * @note If all of the data you are calculating the hash value on is not available in a contiguous buffer in memory, this is + * where you should start. Instantiate a SHA-256 structure, for instance by simply declaring it locally, make your hash + * buffer available, and invoke this function. Once a SHA-256 hash has been calculated (see further below) a SHA-256 + * structure can be initialized again for the next calculation. + * + * @note If either of the passed pointers is NULL, the results are unpredictable. + */ +void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]); + +/* + * @brief Stream more input data for an on-going SHA-256 calculation. + * @param sha_256 A pointer to a previously initialized SHA-256 structure. + * @param data Pointer to the data to be added to the calculation. + * @param len Length of the data to add, in byte. + * + * @note This function may be invoked an arbitrary number of times between initialization and closing, but the maximum + * data length is limited by the SHA-256 algorithm: the total number of bits (i.e. the total number of bytes times + * eight) must be representable by a 64-bit unsigned integer. While that is not a practical limitation, the results are + * unpredictable if that limit is exceeded. + * + * @note This function may be invoked on empty data (zero length), although that obviously will not add any data. + * + * @note If either of the passed pointers is NULL, the results are unpredictable. + */ +void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len); + +/* + * @brief Conclude a SHA-256 streaming calculation, making the hash value available. + * @param sha_256 A pointer to a previously initialized SHA-256 structure. + * @return Pointer to the hash array, where the result is delivered. + * + * @note After this function has been invoked, the result is available in the hash buffer that initially was provided. A + * pointer to the hash value is returned for convenience, but you should feel free to ignore it: it is simply a pointer + * to the first byte of your initially provided hash array. + * + * @note If the passed pointer is NULL, the results are unpredictable. + * + * @note Invoking this function for a calculation with no data (the writing function has never been invoked, or it only + * has been invoked with empty data) is legal. It will calculate the SHA-256 value of the empty string. + */ +uint8_t *sha_256_close(struct Sha_256 *sha_256); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/unit-tests/test_bip32.c b/unit-tests/test_bip32.c index ad310e003..382c06a69 100644 --- a/unit-tests/test_bip32.c +++ b/unit-tests/test_bip32.c @@ -229,9 +229,7 @@ int main(void) { cmocka_unit_test(test_bip32_read), cmocka_unit_test(test_bad_bip32_read), cmocka_unit_test(test_is_pubkey_path_standard_true), - cmocka_unit_test(test_is_pubkey_path_standard_false), - cmocka_unit_test(test_is_address_path_standard_true), - cmocka_unit_test(test_is_address_path_standard_false) + cmocka_unit_test(test_is_pubkey_path_standard_false) }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/unit-tests/test_buffer.c b/unit-tests/test_buffer.c index 3cc459290..ae05e2de8 100644 --- a/unit-tests/test_buffer.c +++ b/unit-tests/test_buffer.c @@ -191,6 +191,43 @@ static void test_buffer_peek(void **state) { assert_int_equal(c, 0x55); // unchanged because of failure } +static void test_buffer_peek_n(void **state) { + (void) state; + + uint8_t temp[6] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 + }; + buffer_t buf = {.ptr = temp, .size = sizeof(temp), .offset = 0}; + + bool result; + uint8_t c; + + for (int i = 0; i < 6; i++) { + result = buffer_peek_n(&buf, i, &c); + assert_true(result); + assert_int_equal(c, temp[i]); + } + + c = 42; + result = buffer_peek_n(&buf, 6, &c); // past the end + assert_false(result); + assert_int_equal(c, 42); // c should not change on failure + + buf.offset += 3; + + for (int i = 0; i < 3; i++) { + result = buffer_peek_n(&buf, i, &c); + assert_true(result); + assert_int_equal(c, temp[3+i]); + } + + c = 42; + result = buffer_peek_n(&buf, 4, &c); // past the end + assert_false(result); + assert_int_equal(c, 42); // c should not change on failure +} + + static void test_buffer_write(void **state) { (void) state; @@ -391,6 +428,37 @@ static void test_buffer_alloc(void **state) { assert_int_equal(buf.offset, (2 * sizeof(void*)) - 2); } +static void test_buffer_is_cur_aligned(void **state) { + (void) state; + + uint8_t data[32] __attribute__ ((aligned (4))); + + buffer_t buf = buffer_create(data + 2, sizeof(data) - 2); + + assert_false(buffer_is_cur_aligned(&buf)); //2 + + buffer_seek_cur(&buf, 1); + assert_false(buffer_is_cur_aligned(&buf)); //3 + + buffer_seek_cur(&buf, 1); + assert_true(buffer_is_cur_aligned(&buf)); //4 + + buffer_seek_cur(&buf, 1); + assert_false(buffer_is_cur_aligned(&buf)); //5 + + buffer_seek_cur(&buf, 1); + assert_false(buffer_is_cur_aligned(&buf)); //6 + + buffer_seek_cur(&buf, 1); + assert_false(buffer_is_cur_aligned(&buf)); //7 + + buffer_seek_cur(&buf, 1); + assert_true(buffer_is_cur_aligned(&buf)); //8 + + buffer_seek_cur(&buf, 1); + assert_false(buffer_is_cur_aligned(&buf)); //9 +} + // tests the buffer_snapshot/buffer_restore functions static void test_buffer_snapshot_restore(void **state) { (void) state; @@ -420,9 +488,11 @@ int main(void) { cmocka_unit_test(test_buffer_get_cur), cmocka_unit_test(test_buffer_read), cmocka_unit_test(test_buffer_peek), + cmocka_unit_test(test_buffer_peek_n), cmocka_unit_test(test_buffer_write), cmocka_unit_test(test_buffer_create), cmocka_unit_test(test_buffer_alloc), + cmocka_unit_test(test_buffer_is_cur_aligned), cmocka_unit_test(test_buffer_snapshot_restore)}; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/unit-tests/test_display_utils.c b/unit-tests/test_display_utils.c index 7a9cccbec..7d397ebf9 100644 --- a/unit-tests/test_display_utils.c +++ b/unit-tests/test_display_utils.c @@ -126,9 +126,9 @@ static void test_format_amount(void **state) { memset(out, 0xEE, sizeof(out)); format_amount(sats_testcases[i].coin, - sats_testcases[i].amount, - sats_testcases[i].decimals, - out); + sats_testcases[i].amount, + sats_testcases[i].decimals, + out); out[sizeof(out) - 1] = '\0'; assert_string_equal((char *) out, sats_testcases[i].expected); diff --git a/unit-tests/test_script.c b/unit-tests/test_script.c index d75376261..d8b49d598 100644 --- a/unit-tests/test_script.c +++ b/unit-tests/test_script.c @@ -18,6 +18,26 @@ unsigned int pic(unsigned int linked_address) { #include "common/script.h" +static void test_get_push_script_size(void **state) { + (void) state; + + assert_int_equal(get_push_script_size((uint32_t) 0), 1); + assert_int_equal(get_push_script_size((uint32_t) 1), 1); + assert_int_equal(get_push_script_size((uint32_t) 15), 1); + assert_int_equal(get_push_script_size((uint32_t) 16), 1); + assert_int_equal(get_push_script_size((uint32_t) 17), 2); + assert_int_equal(get_push_script_size((uint32_t) 0x7f), 2); + assert_int_equal(get_push_script_size((uint32_t) 0x80), 3); + assert_int_equal(get_push_script_size((uint32_t) 0xff), 3); + assert_int_equal(get_push_script_size((uint32_t) 0x7fff), 3); + assert_int_equal(get_push_script_size((uint32_t) 0x8000), 4); + assert_int_equal(get_push_script_size((uint32_t) 0x7fffff), 4); + assert_int_equal(get_push_script_size((uint32_t) 0x800000), 5); + assert_int_equal(get_push_script_size((uint32_t) 0x7fffffff), 5); + assert_int_equal(get_push_script_size((uint32_t) 0x80000000), 6); + assert_int_equal(get_push_script_size((uint32_t) 0xffffffff), 6); +} + static void test_get_script_type_valid(void **state) { (void) state; @@ -198,9 +218,6 @@ static void test_format_opscript_script_valid(void **state) { "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425" "262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a"); - uint8_t input20[] = {OP_RETURN, OP_PUSHDATA1, 7, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; - CHECK_VALID_TESTCASE(input20, "OP_RETURN 0x01020304050607"); - uint8_t input21[] = {OP_RETURN, OP_PUSHDATA1, 80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, @@ -215,6 +232,18 @@ static void test_format_opscript_script_valid(void **state) { uint8_t input22[] = {OP_RETURN, OP_1NEGATE}; CHECK_VALID_TESTCASE(input22, "OP_RETURN -1"); + + uint8_t input23[] = {OP_RETURN, OP_0, OP_1, OP_5, OP_7, OP_16}; + CHECK_VALID_TESTCASE(input23, "OP_RETURN 0 1 5 7 16"); + + uint8_t input24[] = {OP_RETURN, OP_8, OP_1NEGATE, 15, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, OP_0, + 7, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + CHECK_VALID_TESTCASE(input24, + "OP_RETURN 8 -1 0x0102030405060708090a0b0c0d0e0f 0 0x11223344556677"); + + uint8_t input_25[] = {OP_RETURN}; + CHECK_VALID_TESTCASE(input_25, "OP_RETURN"); } static void test_format_opscript_script_invalid(void **state) { @@ -224,9 +253,6 @@ static void test_format_opscript_script_invalid(void **state) { char out[MAX_OPRETURN_OUTPUT_DESC_SIZE]; assert_int_equal(format_opscript_script(input_empty, 0, out), -1); - uint8_t input_no_push[] = {OP_RETURN}; - CHECK_INVALID_TESTCASE(input_no_push); - uint8_t input_not_opreturn[] = {OP_DUP}; CHECK_INVALID_TESTCASE(input_not_opreturn); @@ -243,15 +269,50 @@ static void test_format_opscript_script_invalid(void **state) { {OP_RETURN, OP_PUSHDATA4, 0x06, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; CHECK_INVALID_TESTCASE(input_pushdata4); - uint8_t input_extra_push[] = {OP_RETURN, OP_0, OP_0}; + uint8_t input_extra_push[] = {OP_RETURN, 4, 1, 2, 3, 4, 42}; CHECK_INVALID_TESTCASE(input_extra_push); - uint8_t input_extra_push2[] = {OP_RETURN, 4, 1, 2, 3, 4, 42}; - CHECK_INVALID_TESTCASE(input_extra_push2); + uint8_t input_6pushes[] = {OP_RETURN, OP_1, OP_2, OP_3, OP_4, OP_5, OP_6}; + CHECK_INVALID_TESTCASE(input_6pushes); + + // clang-format off + uint8_t input_too_long[] = { + OP_RETURN, + OP_PUSHDATA1, 81, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 81 + }; + // clang-format on + CHECK_INVALID_TESTCASE(input_too_long); + + // not minimal push encodings + uint8_t input_pushdata_nonstandard[] = + {OP_RETURN, OP_PUSHDATA1, 7, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; + CHECK_INVALID_TESTCASE(input_pushdata_nonstandard); + + uint8_t input_negative1_notminimal_1[] = {OP_RETURN, 1, 0x81}; + CHECK_INVALID_TESTCASE(input_negative1_notminimal_1); + uint8_t input_negative1_notminimal_2[] = {OP_RETURN, OP_PUSHDATA1, 1, 0x81}; + CHECK_INVALID_TESTCASE(input_negative1_notminimal_2); + for (uint8_t i = 0; i <= 16; i++) { + uint8_t input_negative1_notminimal_push_1[] = {OP_RETURN, 1, i}; + CHECK_INVALID_TESTCASE(input_negative1_notminimal_push_1); + uint8_t input_negative1_notminimal_push_2[] = {OP_RETURN, OP_PUSHDATA1, 1, i}; + CHECK_INVALID_TESTCASE(input_negative1_notminimal_push_2); + } + + // transaction containing non-push opcodes are not standard + uint8_t input_non_push_opcode[] = {OP_RETURN, OP_3, OP_2, OP_ADD, OP_0}; + CHECK_INVALID_TESTCASE(input_non_push_opcode); } int main(void) { const struct CMUnitTest tests[] = { + cmocka_unit_test(test_get_push_script_size), cmocka_unit_test(test_get_script_type_valid), cmocka_unit_test(test_get_script_type_invalid), cmocka_unit_test(test_format_opscript_script_valid), diff --git a/unit-tests/test_wallet.c b/unit-tests/test_wallet.c index fefdc87a9..975545487 100644 --- a/unit-tests/test_wallet.c +++ b/unit-tests/test_wallet.c @@ -18,283 +18,609 @@ unsigned int pic(unsigned int linked_address) { #include "common/wallet.h" +static int parse_policy(const char *descriptor_template, uint8_t *out, size_t out_size) { + buffer_t descriptor_template_buf = + buffer_create((void *) descriptor_template, strlen(descriptor_template)); + + return parse_descriptor_template(&descriptor_template_buf, + out, + out_size, + WALLET_POLICY_VERSION_V2); +} + // in unit tests, size_t integers are currently 8 compiled as 8 bytes; therefore, in the app // about half of the memory would be needed -#define MAX_POLICY_MAP_MEMORY_SIZE 512 +#define MAX_WALLET_POLICY_MEMORY_SIZE 512 + +// convenience function to compactly check common assertions on a key placeholder pointer +static void check_key_placeholder(const policy_node_key_placeholder_t *ptr, + int key_index, + uint32_t num_first, + uint32_t num_second) { + assert_int_equal(ptr->key_index, key_index); + assert_int_equal(ptr->num_first, num_first); + assert_int_equal(ptr->num_second, num_second); +} static void test_parse_policy_map_singlesig_1(void **state) { (void) state; - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; - - int res; + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; - char *policy = "pkh(@0)"; - buffer_t policy_buf = buffer_create((void *) policy, strlen(policy)); + int res = parse_policy("pkh(@0/**)", out, sizeof(out)); - res = parse_policy_map(&policy_buf, out, sizeof(out), 0, 0); - assert_int_equal(res, 0); + assert_true(res >= 0); policy_node_with_key_t *node_1 = (policy_node_with_key_t *) out; - assert_int_equal(node_1->type, TOKEN_PKH); - assert_int_equal(node_1->key_index, 0); + assert_int_equal(node_1->base.type, TOKEN_PKH); + check_key_placeholder(r_policy_node_key_placeholder(&node_1->key_placeholder), 0, 0, 1); } static void test_parse_policy_map_singlesig_2(void **state) { (void) state; - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; - - int res; + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; - char *policy = "sh(wpkh(@0))"; - buffer_t policy_buf = buffer_create((void *) policy, strlen(policy)); + int res = parse_policy("sh(wpkh(@0/**))", out, sizeof(out)); - res = parse_policy_map(&policy_buf, out, sizeof(out), 0, 0); - assert_int_equal(res, 0); + assert_true(res >= 0); policy_node_with_script_t *root = (policy_node_with_script_t *) out; - assert_int_equal(root->type, TOKEN_SH); + assert_int_equal(root->base.type, TOKEN_SH); - policy_node_with_key_t *inner = (policy_node_with_key_t *) root->script; + policy_node_with_key_t *inner = (policy_node_with_key_t *) r_policy_node(&root->script); - assert_int_equal(inner->type, TOKEN_WPKH); - assert_int_equal(inner->key_index, 0); + assert_int_equal(inner->base.type, TOKEN_WPKH); + check_key_placeholder(r_policy_node_key_placeholder(&inner->key_placeholder), 0, 0, 1); } static void test_parse_policy_map_singlesig_3(void **state) { (void) state; - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; - int res; + int res = parse_policy("sh(wsh(pkh(@0/**)))", out, sizeof(out)); - char *policy = "sh(wsh(pkh(@0)))"; - buffer_t policy_buf = buffer_create((void *) policy, strlen(policy)); - - res = parse_policy_map(&policy_buf, out, sizeof(out), 0, 0); - assert_int_equal(res, 0); + assert_true(res >= 0); policy_node_with_script_t *root = (policy_node_with_script_t *) out; - assert_int_equal(root->type, TOKEN_SH); + assert_int_equal(root->base.type, TOKEN_SH); - policy_node_with_script_t *mid = (policy_node_with_script_t *) root->script; + policy_node_with_script_t *mid = (policy_node_with_script_t *) r_policy_node(&root->script); - assert_int_equal(mid->type, TOKEN_WSH); + assert_int_equal(mid->base.type, TOKEN_WSH); - policy_node_with_key_t *inner = (policy_node_with_key_t *) mid->script; + policy_node_with_key_t *inner = (policy_node_with_key_t *) r_policy_node(&mid->script); - assert_int_equal(inner->type, TOKEN_PKH); - assert_int_equal(inner->key_index, 0); + assert_int_equal(inner->base.type, TOKEN_PKH); + check_key_placeholder(r_policy_node_key_placeholder(&inner->key_placeholder), 0, 0, 1); } static void test_parse_policy_map_multisig_1(void **state) { (void) state; - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; - - int res; + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; - char *policy = "sortedmulti(2,@0,@1,@2)"; - buffer_t policy_buf = buffer_create((void *) policy, strlen(policy)); + int res = parse_policy("sortedmulti(2,@0/**,@1/**,@2/**)", out, sizeof(out)); - res = parse_policy_map(&policy_buf, out, sizeof(out), 0, 0); - assert_int_equal(res, 0); + assert_true(res >= 0); policy_node_multisig_t *node_1 = (policy_node_multisig_t *) out; - assert_int_equal(node_1->type, TOKEN_SORTEDMULTI); + assert_int_equal(node_1->base.type, TOKEN_SORTEDMULTI); assert_int_equal(node_1->k, 2); assert_int_equal(node_1->n, 3); - assert_int_equal(node_1->key_indexes[0], 0); - assert_int_equal(node_1->key_indexes[1], 1); - assert_int_equal(node_1->key_indexes[2], 2); + check_key_placeholder(&r_policy_node_key_placeholder(&node_1->key_placeholders)[0], 0, 0, 1); + check_key_placeholder(&r_policy_node_key_placeholder(&node_1->key_placeholders)[1], 1, 0, 1); + check_key_placeholder(&r_policy_node_key_placeholder(&node_1->key_placeholders)[2], 2, 0, 1); } static void test_parse_policy_map_multisig_2(void **state) { (void) state; - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; - int res; + int res = parse_policy("wsh(multi(3,@0/**,@1/**,@2/**,@3/**,@4/**))", out, sizeof(out)); - char *policy = "wsh(multi(3,@0,@1,@2,@3,@4))"; - buffer_t policy_buf = buffer_create((void *) policy, strlen(policy)); - - res = parse_policy_map(&policy_buf, out, sizeof(out), 0, 0); - assert_int_equal(res, 0); + assert_true(res >= 0); policy_node_with_script_t *root = (policy_node_with_script_t *) out; - assert_int_equal(root->type, TOKEN_WSH); + assert_int_equal(root->base.type, TOKEN_WSH); - policy_node_multisig_t *inner = (policy_node_multisig_t *) root->script; - assert_int_equal(inner->type, TOKEN_MULTI); + policy_node_multisig_t *inner = (policy_node_multisig_t *) r_policy_node(&root->script); + assert_int_equal(inner->base.type, TOKEN_MULTI); assert_int_equal(inner->k, 3); assert_int_equal(inner->n, 5); - for (int i = 0; i < 5; i++) assert_int_equal(inner->key_indexes[i], i); + for (int i = 0; i < 5; i++) { + check_key_placeholder(&r_policy_node_key_placeholder(&inner->key_placeholders)[i], i, 0, 1); + } } static void test_parse_policy_map_multisig_3(void **state) { (void) state; - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; - - int res; + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; - char *policy = "sh(wsh(sortedmulti(3,@0,@1,@2,@3,@4)))"; - buffer_t policy_buf = buffer_create((void *) policy, strlen(policy)); + int res = + parse_policy("sh(wsh(sortedmulti(3,@0/**,@1/**,@2/**,@3/**,@4/**)))", out, sizeof(out)); - res = parse_policy_map(&policy_buf, out, sizeof(out), 0, 0); - assert_int_equal(res, 0); + assert_true(res >= 0); policy_node_with_script_t *root = (policy_node_with_script_t *) out; - assert_int_equal(root->type, TOKEN_SH); + assert_int_equal(root->base.type, TOKEN_SH); - policy_node_with_script_t *mid = (policy_node_with_script_t *) root->script; - assert_int_equal(mid->type, TOKEN_WSH); + policy_node_with_script_t *mid = (policy_node_with_script_t *) r_policy_node(&root->script); + assert_int_equal(mid->base.type, TOKEN_WSH); - policy_node_multisig_t *inner = (policy_node_multisig_t *) mid->script; - assert_int_equal(inner->type, TOKEN_SORTEDMULTI); + policy_node_multisig_t *inner = (policy_node_multisig_t *) r_policy_node(&mid->script); + assert_int_equal(inner->base.type, TOKEN_SORTEDMULTI); assert_int_equal(inner->k, 3); assert_int_equal(inner->n, 5); - for (int i = 0; i < 5; i++) assert_int_equal(inner->key_indexes[i], i); + for (int i = 0; i < 5; i++) { + check_key_placeholder(&r_policy_node_key_placeholder(&inner->key_placeholders)[i], i, 0, 1); + } } -// convenience function to parse as one liners +static void test_parse_policy_tr(void **state) { + (void) state; + + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; + int res; + + // Simple tr without a tree + res = parse_policy("tr(@0/**)", out, sizeof(out)); + + assert_true(res >= 0); + policy_node_tr_t *root = (policy_node_tr_t *) out; + + assert_true(isnull_policy_node_tree(&root->tree)); + check_key_placeholder(r_policy_node_key_placeholder(&root->key_placeholder), 0, 0, 1); + + // Simple tr with a TREE that is a simple script + res = parse_policy("tr(@0/**,pk(@1/**))", out, sizeof(out)); + + assert_true(res >= 0); + root = (policy_node_tr_t *) out; + + check_key_placeholder(r_policy_node_key_placeholder(&root->key_placeholder), 0, 0, 1); + + assert_int_equal(r_policy_node_tree(&root->tree)->is_leaf, true); + + policy_node_with_key_t *tapscript = + (policy_node_with_key_t *) r_policy_node(&r_policy_node_tree(&root->tree)->script); + + assert_int_equal(tapscript->base.type, TOKEN_PK); + check_key_placeholder(r_policy_node_key_placeholder(&tapscript->key_placeholder), 1, 0, 1); + + // Simple tr with a TREE with two tapleaves + res = parse_policy("tr(@0/**,{pk(@1/**),pk(@2/<5;7>/*)})", out, sizeof(out)); + + assert_true(res >= 0); + root = (policy_node_tr_t *) out; -static int parse_policy(const char *policy, size_t policy_len, uint8_t *out, size_t out_len) { - buffer_t in_buf = buffer_create((void *) policy, policy_len); - return parse_policy_map(&in_buf, out, out_len, 0, 0); + check_key_placeholder(r_policy_node_key_placeholder(&root->key_placeholder), 0, 0, 1); + + policy_node_tree_t *taptree = r_policy_node_tree(&root->tree); + + assert_int_equal(taptree->is_leaf, false); + + policy_node_tree_t *taptree_left = + (policy_node_tree_t *) r_policy_node_tree(&taptree->left_tree); + assert_int_equal(taptree_left->is_leaf, true); + policy_node_with_key_t *tapscript_left = + (policy_node_with_key_t *) r_policy_node(&taptree_left->script); + + assert_int_equal(tapscript_left->base.type, TOKEN_PK); + check_key_placeholder(r_policy_node_key_placeholder(&tapscript_left->key_placeholder), 1, 0, 1); + + policy_node_tree_t *taptree_right = + (policy_node_tree_t *) r_policy_node_tree(&taptree->right_tree); + assert_int_equal(taptree_right->is_leaf, true); + policy_node_with_key_t *tapscript_right = + (policy_node_with_key_t *) r_policy_node(&taptree_right->script); + + assert_int_equal(tapscript_right->base.type, TOKEN_PK); + check_key_placeholder(r_policy_node_key_placeholder(&tapscript_right->key_placeholder), + 2, + 5, + 7); +} + +static void test_parse_policy_tr_multisig(void **state) { + (void) state; + + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; + int res; + + // tr with a tree with two scripts: a multi_a and a sortedmulti_a: + res = parse_policy("tr(@0/**,{multi_a(1,@1/**,@2/**),sortedmulti_a(2,@3/**,@4/**,@5/**)})", + out, + sizeof(out)); + + assert_true(res >= 0); + + policy_node_tr_t *root = (policy_node_tr_t *) out; + + assert_int_equal(r_policy_node_key_placeholder(&root->key_placeholder)->key_index, 0); + assert_int_equal(r_policy_node_key_placeholder(&root->key_placeholder)->num_first, 0); + assert_int_equal(r_policy_node_key_placeholder(&root->key_placeholder)->num_second, 1); + + policy_node_tree_t *taptree = r_policy_node_tree(&root->tree); + + assert_int_equal(taptree->is_leaf, false); + + policy_node_tree_t *taptree_left = + (policy_node_tree_t *) r_policy_node_tree(&taptree->left_tree); + assert_int_equal(taptree_left->is_leaf, true); + policy_node_multisig_t *tapscript_left = + (policy_node_multisig_t *) r_policy_node(&taptree_left->script); + + assert_int_equal(tapscript_left->base.type, TOKEN_MULTI_A); + assert_int_equal(tapscript_left->k, 1); + assert_int_equal(tapscript_left->n, 2); + check_key_placeholder(&r_policy_node_key_placeholder(&tapscript_left->key_placeholders)[0], + 1, + 0, + 1); + check_key_placeholder(&r_policy_node_key_placeholder(&tapscript_left->key_placeholders)[1], + 2, + 0, + 1); + + policy_node_tree_t *taptree_right = + (policy_node_tree_t *) r_policy_node_tree(&taptree->right_tree); + assert_int_equal(taptree_right->is_leaf, true); + policy_node_multisig_t *tapscript_right = + (policy_node_multisig_t *) r_policy_node(&taptree_right->script); + + assert_int_equal(tapscript_right->base.type, TOKEN_SORTEDMULTI_A); + assert_int_equal(tapscript_right->k, 2); + assert_int_equal(tapscript_right->n, 3); + check_key_placeholder(&r_policy_node_key_placeholder(&tapscript_right->key_placeholders)[0], + 3, + 0, + 1); + check_key_placeholder(&r_policy_node_key_placeholder(&tapscript_right->key_placeholders)[1], + 4, + 0, + 1); + check_key_placeholder(&r_policy_node_key_placeholder(&tapscript_right->key_placeholders)[2], + 5, + 0, + 1); } -#define PARSE_POLICY(policy, out, out_len) parse_policy(policy, sizeof(policy) - 1, out, out_len) +static void test_get_policy_segwit_version(void **state) { + (void) state; + + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; + policy_node_t *policy = (policy_node_t *) out; + + // legacy policies (returning -1) + parse_policy("pkh(@0/**)", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == -1); + + parse_policy("sh(multi(2,@0/**,@1/**))", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == -1); + + // segwit v0 policies + parse_policy("wpkh(@0/**)", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == 0); + parse_policy("wsh(multi(2,@0/**,@1/**))", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == 0); + parse_policy("sh(wpkh(@0/**))", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == 0); + parse_policy("sh(wsh(multi(2,@0/**,@1/**)))", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == 0); + + // segwit v1 policies + parse_policy("tr(@0/**)", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == 1); + parse_policy("tr(@0/**,{pk(@1/**,multi(1,@2/**,@3/**)})", out, sizeof(out)); + assert(get_policy_segwit_version(policy) == 1); +} static void test_failures(void **state) { (void) state; - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; + uint8_t out[MAX_WALLET_POLICY_MEMORY_SIZE]; // excess byte not allowed - assert_true(0 > PARSE_POLICY("pkh(@0) ", out, sizeof(out))); + assert_true(0 > parse_policy("pkh(@0/**) ", out, sizeof(out))); // missing closing parenthesis - assert_true(0 > PARSE_POLICY("pkh(@0", out, sizeof(out))); + assert_true(0 > parse_policy("pkh(@0/**", out, sizeof(out))); // unknown token - assert_true(0 > PARSE_POLICY("yolo(@0)", out, sizeof(out))); - assert_true(0 > PARSE_POLICY("Pkh(@0)", out, sizeof(out))); // case-sensitive + assert_true(0 > parse_policy("yolo(@0/**)", out, sizeof(out))); + assert_true(0 > parse_policy("Pkh(@0/**)", out, sizeof(out))); // case-sensitive // missing or invalid key identifier - assert_true(0 > PARSE_POLICY("pkh()", out, sizeof(out))); - assert_true(0 > PARSE_POLICY("pkh(@)", out, sizeof(out))); - assert_true(0 > PARSE_POLICY("pkh(0)", out, sizeof(out))); + assert_true(0 > parse_policy("pkh()", out, sizeof(out))); + assert_true(0 > parse_policy("pkh(@)", out, sizeof(out))); + assert_true(0 > parse_policy("pkh(0)", out, sizeof(out))); // sh not top-level - assert_true(0 > PARSE_POLICY("sh(sh(pkh(@0)))", out, sizeof(out))); + assert_true(0 > parse_policy("sh(sh(pkh(@0/**)))", out, sizeof(out))); // wsh can only be inside sh - assert_true(0 > PARSE_POLICY("wsh(wsh(pkh(@0)))", out, sizeof(out))); + assert_true(0 > parse_policy("wsh(wsh(pkh(@0/**)))", out, sizeof(out))); // wpkh can only be inside sh - assert_true(0 > PARSE_POLICY("wsh(wpkh(@0)))", out, sizeof(out))); + assert_true(0 > parse_policy("wsh(wpkh(@0/**)))", out, sizeof(out))); // multi with invalid threshold - assert_true( - 0 > PARSE_POLICY("multi(6,@0,@1,@2,@3,@4)", out, sizeof(out))); // threshold larger than n - assert_true(0 > PARSE_POLICY("multi(0,@0,@1,@2,@3,@4)", out, sizeof(out))); + assert_true(0 > parse_policy("multi(6,@0/**,@1/**,@2/**,@3/**,@4/**)", + out, + sizeof(out))); // threshold larger than n + assert_true(0 > parse_policy("multi(0,@0/**,@1/**,@2/**,@3/**,@4/**)", out, sizeof(out))); // missing threshold or keys in multisig - assert_true(0 > PARSE_POLICY("multi(@0,@1,@2,@3,@4)", out, sizeof(out))); - assert_true(0 > PARSE_POLICY("multi(1)", out, sizeof(out))); - assert_true(0 > PARSE_POLICY("multi(1,)", out, sizeof(out))); + assert_true(0 > parse_policy("multi(@0/**,@1/**,@2/**,@3/**,@4/**)", out, sizeof(out))); + assert_true(0 > parse_policy("multi(1)", out, sizeof(out))); + assert_true(0 > parse_policy("multi(1,)", out, sizeof(out))); + + // syntactically invalid tr descriptors + assert_true(0 > parse_policy("tr(,pk(@0))", out, sizeof(out))); + assert_true(0 > parse_policy("tr(pk(@0))", out, sizeof(out))); + assert_true(0 > parse_policy("tr(pk(@0),@1/**)", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,)", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,{})", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,@1/**)", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,{pk(@1)})", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,{pk(@1),})", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,{,pk(@1)})", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,{@1/**,pk(@2)})", out, sizeof(out))); + + // invalid tokens within tr scripts + assert_true(0 > parse_policy("tr(@0/**,multi(2,@1,@2))", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,sortedmulti(2,@1,@2))", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,sh(pk(@0/**)))", out, sizeof(out))); + assert_true(0 > parse_policy("tr(@0/**,wsh(pk(@0/**)))", out, sizeof(out))); } -static void test_policy_is_multisig(void **state) { - (void) state; - - uint8_t out[MAX_POLICY_MAP_MEMORY_SIZE]; - policy_node_t *policy = (policy_node_t *) out; - - assert_int_equal(0, PARSE_POLICY("pkh(@0)", out, sizeof(out))); - assert_false(policy_is_multisig(policy)); - - assert_int_equal(0, PARSE_POLICY("sh(wpkh(@0))", out, sizeof(out))); - assert_false(policy_is_multisig(policy)); - - assert_int_equal(0, PARSE_POLICY("sh(wsh(pkh(@0)))", out, sizeof(out))); - assert_false(policy_is_multisig(policy)); - - assert_int_equal(0, PARSE_POLICY("sortedmulti(2,@0,@1,@2)", out, sizeof(out))); - assert_true(policy_is_multisig(policy)); - - assert_int_equal(0, PARSE_POLICY("wsh(multi(3,@0,@1,@2,@3,@4))", out, sizeof(out))); - assert_true(policy_is_multisig(policy)); - - assert_int_equal(0, PARSE_POLICY("sh(wsh(sortedmulti(3,@0,@1,@2,@3,@4)))", out, sizeof(out))); - assert_true(policy_is_multisig(policy)); +enum TestMode { + TESTMODE_INVALID = 0, + TESTMODE_VALID = 1, + TESTMODE_NONMAL = 2, + TESTMODE_NEEDSIG = 4, + TESTMODE_TIMELOCKMIX = 8, // ignored in our tests + //! Invalid only under P2WSH context + TESTMODE_P2WSH_INVALID = 16, + //! Invalid only under Tapscript context + TESTMODE_TAPSCRIPT_INVALID = 32 +}; + +static void Test(const char *ms, const char *hexscript, int mode, int opslimit, int stacklimit) { + char descriptor_wsh[1024]; + char descriptor_tr[1024]; + + if (strlen(ms) + sizeof("wsh(") + sizeof(")") > sizeof(descriptor_wsh)) { + assert(false); + } + + if (strlen(ms) + sizeof("tr(@0/<100;101>/*,") + sizeof(")") > sizeof(descriptor_tr)) { + assert(false); + } + + // Wrap the miniscript inside "wsh" + strcpy(descriptor_wsh, "wsh("); + strcat(descriptor_wsh, ms); + strcat(descriptor_wsh, ")"); + + // Wrap the miniscript inside a "tr" script. + // We make sure to use a key placeholder with derivations that + // are not used elsewhere in the descriptor template. + strcpy(descriptor_tr, "tr(@0/<100;101>/*,"); + strcat(descriptor_tr, ms); + strcat(descriptor_tr, ")"); + uint8_t out_wsh[MAX_WALLET_POLICY_MEMORY_SIZE]; + uint8_t out_tr[MAX_WALLET_POLICY_MEMORY_SIZE]; + + uint8_t *out; + + int res = 0; + if ((mode & TESTMODE_P2WSH_INVALID) != 0 && (mode & TESTMODE_TAPSCRIPT_INVALID) != 0) { + assert(false); // at most one of TESTMODE_P2WSH_INVALID and TESTMODE_TAPSCRIPT_INVALID + } + + buffer_t descriptor_template_wsh_buf = + buffer_create((void *) descriptor_wsh, strlen(descriptor_wsh)); + + int res_wsh = parse_descriptor_template(&descriptor_template_wsh_buf, + out_wsh, + sizeof(out_wsh), + WALLET_POLICY_VERSION_V2); + + buffer_t descriptor_template_tr_buf = + buffer_create((void *) descriptor_tr, strlen(descriptor_tr)); + + int res_tr = parse_descriptor_template(&descriptor_template_tr_buf, + out_tr, + sizeof(out_tr), + WALLET_POLICY_VERSION_V2); + + res = res_wsh; + out = out_wsh; + + if (mode & TESTMODE_P2WSH_INVALID) { + assert_true(res_wsh < 0); + + // we use the result of parsing as wsh script in the remaining + // tests below, unless that's invalid + res = res_tr; + out = out_tr; + } + + if (mode & TESTMODE_TAPSCRIPT_INVALID) { + assert_true(res_tr < 0); + } + + if (mode == TESTMODE_INVALID) { + assert_true(res < 0); + } else { + assert_true(res >= 0); + + const policy_node_t *miniscript; + int context; + if (((policy_node_t *) out)->type == TOKEN_WSH) { + miniscript = r_policy_node(&((policy_node_with_script_t *) out)->script); + context = MINISCRIPT_CONTEXT_P2WSH; + } else { + assert_true(((policy_node_t *) out)->type == TOKEN_TR); + policy_node_tr_t *tr = (policy_node_tr_t *) out; + assert_true(r_policy_node_tree(&tr->tree)->is_leaf); + + miniscript = r_policy_node(&r_policy_node_tree(&tr->tree)->script); + context = MINISCRIPT_CONTEXT_TAPSCRIPT; + } + + policy_node_ext_info_t ext_info; + res = compute_miniscript_policy_ext_info(miniscript, &ext_info, context); + + assert_true(res == 0); + + int is_expected_needsig = (mode & TESTMODE_NEEDSIG) ? 1 : 0; + int is_expected_nonmal = (mode & TESTMODE_NONMAL) ? 1 : 0; + + int is_k = (mode & TESTMODE_TIMELOCKMIX) ? 0 : 1; + + // the hexscript is only used to compare with the script length + // (since the pubkeys are missing in the descriptor template, the exact bytes are not known) + int scriptlen = strlen(hexscript) / 2; + + assert_true(ext_info.s == is_expected_needsig); + assert_true(ext_info.m == is_expected_nonmal); + + assert_true(ext_info.k == is_k); + + if (scriptlen >= 1) { + assert_int_equal(ext_info.script_size, scriptlen); + } + + if (opslimit != -1) { + // if ext_info.ops.sat, we want to use 0 (consistently with bitcoin-core's + // implementation) + int ops_sat = (ext_info.ops.sat == -1) ? 0 : ext_info.ops.sat; + + int computed_opslimit = ext_info.ops.count + ops_sat; + assert_int_equal(computed_opslimit, opslimit); + } + if (stacklimit != -1) { + assert_true(ext_info.ss.sat >= 0); + int computed_stacklimit = ext_info.ss.sat + 1; + assert_int_equal(computed_stacklimit, stacklimit); + } + } } -#define PARSE_POLICY_MAP_KEY_INFO(str) \ - do { \ - memset(&map, 0, sizeof(map)); \ - buffer_t buffer = buffer_create((void *) (str), strlen(str)); \ - assert_int_equal(0, parse_policy_map_key_info(&buffer, &map)); \ - assert_false(buffer_can_read(&buffer, 1)); \ - } while(0) - -static void test_parse_policy_map_key_info(void **state) { - policy_map_key_info_t map; - - PARSE_POLICY_MAP_KEY_INFO("[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"); - assert_memory_equal( - map.master_key_derivation, - &((uint32_t[MAX_BIP32_PATH_STEPS]) { BIP32_FIRST_HARDENED_CHILD | 44, BIP32_FIRST_HARDENED_CHILD | 0, BIP32_FIRST_HARDENED_CHILD | 0, 0, 0, 0 }), - sizeof(map.master_key_derivation) - ); - assert_memory_equal(map.master_key_fingerprint, &((uint8_t[4]) { 0xd3, 0x4d, 0xb3, 0x3f }), sizeof(map.master_key_fingerprint)); - assert_int_equal(map.master_key_derivation_len, 3); - assert_int_equal(map.has_key_origin, 1); - assert_int_equal(map.wildcard_id, KEY_WILDCARD_NONE); - assert_string_equal(map.ext_pubkey, "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"); - - PARSE_POLICY_MAP_KEY_INFO("[f5acc2fd/84'/1'/0']tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P/**"); - assert_memory_equal( - map.master_key_derivation, - &((uint32_t[MAX_BIP32_PATH_STEPS]) { BIP32_FIRST_HARDENED_CHILD | 84, BIP32_FIRST_HARDENED_CHILD | 1, BIP32_FIRST_HARDENED_CHILD | 0, 0, 0, 0 }), - sizeof(map.master_key_derivation) - ); - assert_memory_equal(map.master_key_fingerprint, &((uint8_t[4]) { 0xf5, 0xac, 0xc2, 0xfd }), sizeof(map.master_key_fingerprint)); - assert_int_equal(map.master_key_derivation_len, 3); - assert_int_equal(map.has_key_origin, 1); - assert_int_equal(map.wildcard_id, KEY_WILDCARD_ANY); - assert_string_equal(map.ext_pubkey, "tpubDCtKfsNyRhULjZ9XMS4VKKtVcPdVDi8MKUbcSD9MJDyjRu1A2ND5MiipozyyspBT9bg8upEp7a8EAgFxNxXn1d7QkdbL52Ty5jiSLcxPt1P"); - - PARSE_POLICY_MAP_KEY_INFO("xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"); - assert_int_equal(map.master_key_derivation_len, 0); - assert_int_equal(map.has_key_origin, 0); - assert_int_equal(map.wildcard_id, KEY_WILDCARD_NONE); - assert_string_equal(map.ext_pubkey, "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"); - - PARSE_POLICY_MAP_KEY_INFO("xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8/<0;1>/*"); - assert_int_equal(map.master_key_derivation_len, 0); - assert_int_equal(map.has_key_origin, 0); - assert_int_equal(map.wildcard_id, KEY_WILDCARD_STANDARD_CHAINS); - assert_string_equal(map.ext_pubkey, "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8"); - - PARSE_POLICY_MAP_KEY_INFO("xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8/0/*"); - assert_int_equal(map.master_key_derivation_len, 0); - assert_int_equal(map.has_key_origin, 0); - assert_int_equal(map.wildcard_id, KEY_WILDCARD_EXTERNAL_CHAIN); - assert_string_equal(map.ext_pubkey, "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8"); - - PARSE_POLICY_MAP_KEY_INFO("xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8/1/*"); - assert_int_equal(map.master_key_derivation_len, 0); - assert_int_equal(map.has_key_origin, 0); - assert_int_equal(map.wildcard_id, KEY_WILDCARD_INTERNAL_CHAIN); - assert_string_equal(map.ext_pubkey, "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8"); +static void test_miniscript_types(void **state) { + (void) state; + + // tests for miniscript type system + // Tests taken from + // https://github.com/bitcoin/bitcoin/blob/5bf65ec66e5986c9188e3f6234f1c5c0f8dc7f90/src/test/miniscript_tests.cpp, + // except that all key expressions are replaced with placeholders @0/**, @1/**, ... + + // clang-format off + Test("l:older(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // older(1): valid + Test("l:older(0)", "?", TESTMODE_INVALID, -1, -1); // older(0): k must be at least 1 + Test("l:older(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // older(2147483647): valid + Test("l:older(2147483648)", "?", TESTMODE_INVALID, -1, -1); // older(2147483648): k must be below 2^31 + Test("u:after(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // after(1): valid + Test("u:after(0)", "?", TESTMODE_INVALID, -1, -1); // after(0): k must be at least 1 + Test("u:after(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // after(2147483647): valid + Test("u:after(2147483648)", "?", TESTMODE_INVALID, -1, -1); // after(2147483648): k must be below 2^31 + Test("andor(0,1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // andor(Bdu,B,B): valid + Test("andor(a:0,1,1)", "?", TESTMODE_INVALID, -1, -1); // andor(Wdu,B,B): X must be B + Test("andor(0,a:1,a:1)", "?", TESTMODE_INVALID, -1, -1); // andor(Bdu,W,W): Y and Z must be B/V/K + Test("andor(1,1,1)", "?", TESTMODE_INVALID, -1, -1); // andor(Bu,B,B): X must be d + Test("andor(n:or_i(0,after(1)),1,1)", "?", TESTMODE_VALID, -1, -1); // andor(Bdu,B,B): valid + Test("andor(or_i(0,after(1)),1,1)", "?", TESTMODE_INVALID, -1, -1); // andor(Bd,B,B): X must be u + Test("c:andor(0,pk_k(@0/**),pk_k(@1/**))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, -1, -1); // andor(Bdu,K,K): valid + Test("t:andor(0,v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // andor(Bdu,V,V): valid + Test("and_v(v:1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // and_v(V,B): valid + Test("t:and_v(v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // and_v(V,V): valid + Test("c:and_v(v:1,pk_k(@0/**))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, -1, -1); // and_v(V,K): valid + Test("and_v(1,1)", "?", TESTMODE_INVALID, -1, -1); // and_v(B,B): X must be V + Test("and_v(pk_k(@0/**),1)", "?", TESTMODE_INVALID, -1, -1); // and_v(K,B): X must be V + Test("and_v(v:1,a:1)", "?", TESTMODE_INVALID, -1, -1); // and_v(K,W): Y must be B/V/K + Test("and_b(1,a:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // and_b(B,W): valid + Test("and_b(1,1)", "?", TESTMODE_INVALID, -1, -1); // and_b(B,B): Y must W + Test("and_b(v:1,a:1)", "?", TESTMODE_INVALID, -1, -1); // and_b(V,W): X must be B + Test("and_b(a:1,a:1)", "?", TESTMODE_INVALID, -1, -1); // and_b(W,W): X must be B + Test("and_b(pk_k(@0/**),a:1)", "?", TESTMODE_INVALID, -1, -1); // and_b(K,W): X must be B + Test("or_b(0,a:0)", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, -1, -1); // or_b(Bd,Wd): valid + Test("or_b(1,a:0)", "?", TESTMODE_INVALID, -1, -1); // or_b(B,Wd): X must be d + Test("or_b(0,a:1)", "?", TESTMODE_INVALID, -1, -1); // or_b(Bd,W): Y must be d + Test("or_b(0,0)", "?", TESTMODE_INVALID, -1, -1); // or_b(Bd,Bd): Y must W + Test("or_b(v:0,a:0)", "?", TESTMODE_INVALID, -1, -1); // or_b(V,Wd): X must be B + Test("or_b(a:0,a:0)", "?", TESTMODE_INVALID, -1, -1); // or_b(Wd,Wd): X must be B + Test("or_b(pk_k(@0/**),a:0)", "?", TESTMODE_INVALID, -1, -1); // or_b(Kd,Wd): X must be B + Test("t:or_c(0,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // or_c(Bdu,V): valid + Test("t:or_c(a:0,v:1)", "?", TESTMODE_INVALID, -1, -1); // or_c(Wdu,V): X must be B + Test("t:or_c(1,v:1)", "?", TESTMODE_INVALID, -1, -1); // or_c(Bu,V): X must be d + Test("t:or_c(n:or_i(0,after(1)),v:1)", "?", TESTMODE_VALID, -1, -1); // or_c(Bdu,V): valid + Test("t:or_c(or_i(0,after(1)),v:1)", "?", TESTMODE_INVALID, -1, -1); // or_c(Bd,V): X must be u + Test("t:or_c(0,1)", "?", TESTMODE_INVALID, -1, -1); // or_c(Bdu,B): Y must be V + Test("or_d(0,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL, -1, -1); // or_d(Bdu,B): valid + Test("or_d(a:0,1)", "?", TESTMODE_INVALID, -1, -1); // or_d(Wdu,B): X must be B + Test("or_d(1,1)", "?", TESTMODE_INVALID, -1, -1); // or_d(Bu,B): X must be d + Test("or_d(n:or_i(0,after(1)),1)", "?", TESTMODE_VALID, -1, -1); // or_d(Bdu,B): valid + Test("or_d(or_i(0,after(1)),1)", "?", TESTMODE_INVALID, -1, -1); // or_d(Bd,B): X must be u + Test("or_d(0,v:1)", "?", TESTMODE_INVALID, -1, -1); // or_d(Bdu,V): Y must be B + Test("or_i(1,1)", "?", TESTMODE_VALID, -1, -1); // or_i(B,B): valid + Test("t:or_i(v:1,v:1)", "?", TESTMODE_VALID, -1, -1); // or_i(V,V): valid + Test("c:or_i(pk_k(@0/**),pk_k(@1/**))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, -1, -1); // or_i(K,K): valid + Test("or_i(a:1,a:1)", "?", TESTMODE_INVALID, -1, -1); // or_i(W,W): X and Y must be B/V/K + Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID, -1, -1); // or_b(timelock, heighlock) valid + Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, -1, -1); // and_b(timelock, heighlock) invalid + Test("pk(@0/**)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, -1, -1); // alias to c:pk_k + Test("pkh(@0/**)", "76a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, -1, -1); // alias to c:pk_h + + // Randomly generated test set that covers the majority of type and node type combinations + Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4); + Test("uuj:and_v(v:multi(2,@0/**,@1/**),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 6); + Test("or_b(un:multi(2,@0/**,@1/**),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", TESTMODE_VALID | TESTMODE_TAPSCRIPT_INVALID, 14, 6); + Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", TESTMODE_VALID | TESTMODE_NONMAL, 11, 2); + Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4); + Test("t:andor(multi(3,@0/**,@1/**,@2/**),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 13, 6); + Test("or_d(multi(1,@0/**),or_b(multi(3,@1/**,@2/**,@3/**),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 15, 8); + Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 2); + Test("and_v(or_i(v:multi(2,@0/**,@1/**),v:multi(2,@2/**,@3/**)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 11, 6); + Test("j:and_b(multi(2,@0/**,@1/**),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 5); + Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", TESTMODE_VALID, 12, 2); + Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", TESTMODE_VALID, 16, 3); + Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", TESTMODE_VALID | TESTMODE_NONMAL, 15, 3); + Test("thresh(2,multi(2,@0/**,@1/**),a:multi(1,@2/**),ac:pk_k(@3/**))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 13, 7); + Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", TESTMODE_VALID, 14, 3); + Test("or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b2696892736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", TESTMODE_VALID, 15, 3); + Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,@0/**)),pk_k(@1/**))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 8, 3); + Test("c:and_v(or_c(multi(2,@0/**,@1/**),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(@2/**))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 10, 6); + Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", TESTMODE_VALID, 14, 3); + Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", TESTMODE_VALID, 20, 3); + Test("or_i(c:and_v(v:after(500000),pk_k(@0/**)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 3); + Test("thresh(2,c:pk_h(@0/**),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", TESTMODE_VALID, 18, 5); + Test("and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(@0/**)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 4); + Test("and_n(c:pk_k(@0/**),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 3); + Test("c:or_i(and_v(v:older(16),pk_h(@0/**)),pk_h(@1/**))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 4); + Test("or_d(c:pk_h(@0/**),andor(c:pk_k(@1/**),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", TESTMODE_VALID | TESTMODE_NONMAL, 13, 4); + Test("c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(@0/**),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(@1/**)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 4); + Test("c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(@0/**),or_i(pk_h(@1/**),pk_h(@2/**)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 5); + Test("c:or_i(andor(c:pk_h(@0/**),pk_h(@1/**),pk_h(@2/**)),pk_k(@3/**))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 6); + Test("thresh(1,c:pk_k(@0/**),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 4); + Test("thresh(2,c:pk_k(@0/**),ac:pk_k(@1/**),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 5); + + // Additional Tapscript-related tests + Test("and_v(v:multi_a(2,@0/**,@1/**),after(1231488000))", "20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aac205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba529d0400046749b1", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4, 3); + + // Since 'd:' is 'u' we can use it directly inside a thresh. But we can't under P2WSH. + Test("thresh(2,dv:older(42),s:pk(@0/**),s:pk(@1/**))", "7663012ab269687c205cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bcac937c20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 12, 4); + + // clang-format on } -int main(void) { +int main() { const struct CMUnitTest tests[] = { cmocka_unit_test(test_parse_policy_map_singlesig_1), cmocka_unit_test(test_parse_policy_map_singlesig_2), @@ -302,9 +628,11 @@ int main(void) { cmocka_unit_test(test_parse_policy_map_multisig_1), cmocka_unit_test(test_parse_policy_map_multisig_2), cmocka_unit_test(test_parse_policy_map_multisig_3), + cmocka_unit_test(test_parse_policy_tr), + cmocka_unit_test(test_parse_policy_tr_multisig), + cmocka_unit_test(test_get_policy_segwit_version), cmocka_unit_test(test_failures), - cmocka_unit_test(test_policy_is_multisig), - cmocka_unit_test(test_parse_policy_map_key_info) + cmocka_unit_test(test_miniscript_types), }; return cmocka_run_group_tests(tests, NULL, NULL);