diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..010ec9d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,14 @@ +[profile.release] +opt-level = 'z' +lto = true +codegen-units = 1 + +[unstable] +build-std = ["core", "alloc"] +build-std-features = ["compiler-builtins-mem"] + +# By default, heap size is enforced to 8192 bytes. +# Authorized values are [2048, 4096, 8192, 16384, 24576] +# We use 16k as we get some crashes on 8k when running the tests with big 20+ input TXs +[env] +HEAP_SIZE = "16384" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5462dbb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + allow: + - dependency-name: "ledger_device_sdk" + - dependency-name: "include_gif" diff --git a/.github/workflows/build_and_functional_tests.yml b/.github/workflows/build_and_functional_tests.yml new file mode 100644 index 0000000..3e87c18 --- /dev/null +++ b/.github/workflows/build_and_functional_tests.yml @@ -0,0 +1,49 @@ +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. +# +# The build part of this workflow is mandatory, this ensures that the app will be deployable in the Ledger App Store. +# While the test part of 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 + +permissions: + actions: write + contents: write + pull-requests: write + +on: + workflow_dispatch: + inputs: + golden_run: + type: choice + required: true + default: 'Raise an error (default)' + description: CI behavior if the test snaphots are different than expected. + options: + - 'Raise an error (default)' + - 'Open a PR' + 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" + builder: ledger-app-builder + + 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" + regenerate_snapshots: ${{ inputs.golden_run == 'Open a PR' }} diff --git a/.github/workflows/coding_style_checks.yml b/.github/workflows/coding_style_checks.yml new file mode 100644 index 0000000..79c61c4 --- /dev/null +++ b/.github/workflows/coding_style_checks.yml @@ -0,0 +1,21 @@ +name: Run coding style check + +# This workflow will run linting checks to ensure a level of code quality among all Ledger applications. +# +# The presence of this workflow is mandatory as a minimal level of linting is required. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + check_linting: + name: Check linting using the reusable workflow + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1 + with: + source: './src' diff --git a/.github/workflows/extra_checks.yml b/.github/workflows/extra_checks.yml new file mode 100644 index 0000000..2c023e6 --- /dev/null +++ b/.github/workflows/extra_checks.yml @@ -0,0 +1,28 @@ +name: Run extra checks + +# This workflow will run `run_extra_checks.sh` from the project root. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + run_extra_checks: + runs-on: ubuntu-24.04 + steps: + - name: Checkout the repository + uses: actions/checkout@v5 + + # Note: no need to install docker manually, because it's already installed, see + # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md + + - name: Run the checks + run: | + docker run --rm -v "$(realpath .):/app" -w /app \ + ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest \ + bash run_extra_checks.sh diff --git a/.github/workflows/guidelines_enforcer.yml b/.github/workflows/guidelines_enforcer.yml new file mode 100644 index 0000000..702c05f --- /dev/null +++ b/.github/workflows/guidelines_enforcer.yml @@ -0,0 +1,33 @@ +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/ + +permissions: + actions: write + contents: read + pull-requests: write + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + guidelines_enforcer: + # TODO: this is disabled because the app is still not in the ledger app database, + # so the "Check app_load_params" step fails. + # Once it's there, this must be re-enabled. + # See https://github.com/mintlayer/mintlayer-ledger-app/issues/20. + if: false + name: Call Ledger guidelines_enforcer + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 diff --git a/.github/workflows/misspellings_checks.yml b/.github/workflows/misspellings_checks.yml new file mode 100644 index 0000000..845be36 --- /dev/null +++ b/.github/workflows/misspellings_checks.yml @@ -0,0 +1,19 @@ +name: Misspellings checks + +# This workflow performs some misspelling checks on the repository +# It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked +# applications. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + misspell: + name: Check misspellings + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_spell_check.yml@v1 diff --git a/.github/workflows/python_tests_checks.yml b/.github/workflows/python_tests_checks.yml new file mode 100644 index 0000000..f035629 --- /dev/null +++ b/.github/workflows/python_tests_checks.yml @@ -0,0 +1,44 @@ +name: Checks on the Python tests + +# This workflow performs some checks on the Python client used by the Boilerplate tests +# It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked +# applications. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + + lint: + name: Boilerplate client linting + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v3 + - name: Installing PIP dependencies + run: | + pip install pylint + pip install --extra-index-url https://test.pypi.org/simple/ -r tests/requirements.txt + - name: Lint Python code + run: | + pylint --rc tests/setup.cfg tests/application_client/ + + mypy: + name: Type checking + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v3 + - name: Installing PIP dependencies + run: | + pip install mypy + pip install --extra-index-url https://test.pypi.org/simple/ -r tests/requirements.txt + - name: Mypy type checking + run: | + mypy tests/application_client/ diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..fb70e1e --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,31 @@ +name: Run unit tests + +# This workflow will run `run_unit_tests.sh` from the project root for each device model. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + run_extra_checks: + runs-on: ubuntu-24.04 + strategy: + matrix: + model: [nanox, nanosp, stax, flex, apex_p] + steps: + - name: Checkout the repository + uses: actions/checkout@v5 + + # Note: no need to install docker manually, because it's already installed, see + # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md + + - name: Run the checks + run: | + docker run --rm -v "$(realpath .):/app" -w /app \ + ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest \ + bash run_unit_tests.sh ${{ matrix.model }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..246e261 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +target +app.json +app_nanos.json +app_nanosplus.json +app_nanox.json +app_stax.json +app_flex.json + +# Temporary directory with snapshots taken during test runs +tests/snapshots-tmp/ + +.DS_Store + +# Python +*.pyc[cod] +*.egg +__pycache__/ +*.egg-info/ +.eggs/ +.python-version + +# Related to the Ledger VSCode extension +# Virtual env for sideload (macOS and Windows) +ledger/ +# Build directory +build/ + +# VSCode +.vscode/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5d07645 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1727 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.104", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + +[[package]] +name = "const-zero" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c6565524986fe3225da0beb9b4aa55ebc73cd57ff8cb4ccf016ca4c8d006af" + +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.104", + "unicode-xid", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-alloc" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815" +dependencies = [ + "critical-section", + "linked_list_allocator", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "image" +version = "0.25.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "include_gif" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59152fb9707e856f65a10a25ba0b7141589bcf5cbe7f653fcda2875e45fc78f7" +dependencies = [ + "flate2", + "image", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "ledger_device_sdk" +version = "1.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c105fccec785d1007a3fd5d86fdfe44eeb7283e28565584d315caea0cde4d" +dependencies = [ + "const-zero", + "include_gif", + "ledger_secure_sdk_sys", + "num-traits", + "numtoa", + "rand_core 0.6.4", + "serde_json", + "zeroize", +] + +[[package]] +name = "ledger_secure_sdk_sys" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7424fb1da714f47453c65a58ca4144fa576e91563c860522b31db4c6868408" +dependencies = [ + "bindgen", + "cc", + "critical-section", + "embedded-alloc", + "glob", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mintlayer-app" +version = "1.0.0" +dependencies = [ + "image", + "ledger_device_sdk", + "mintlayer-app-core", +] + +[[package]] +name = "mintlayer-app-core" +version = "1.0.0" +dependencies = [ + "bech32", + "chrono", + "const-hex", + "ledger_device_sdk", + "ledger_secure_sdk_sys", + "mintlayer-core-primitives", + "mintlayer-messages", + "test-utils", +] + +[[package]] +name = "mintlayer-core-primitives" +version = "1.0.0" +source = "git+https://github.com/mintlayer/mintlayer-core-primitives?rev=8644bfe06d932d687075939d2d175183ba1c369d#8644bfe06d932d687075939d2d175183ba1c369d" +dependencies = [ + "derive_more", + "fixed-hash", + "parity-scale-codec", + "strum", +] + +[[package]] +name = "mintlayer-messages" +version = "1.0.0" +dependencies = [ + "derive_more", + "ledger_device_sdk", + "mintlayer-core-primitives", + "num-traits", + "num_enum", + "parity-scale-codec", + "test-utils", +] + +[[package]] +name = "moxcms" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c588e11a3082784af229e23e8e4ecf5bcc6fbe4f69101e0421ce8d79da7f0b40" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "git+https://github.com/paritytech/parity-scale-codec.git?rev=5021525697edc0661591ebc71392c48d950a10b0#5021525697edc0661591ebc71392c48d950a10b0" +dependencies = [ + "arrayvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "git+https://github.com/paritytech/parity-scale-codec.git?rev=5021525697edc0661591ebc71392c48d950a10b0#5021525697edc0661591ebc71392c48d950a10b0" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn 2.0.104", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.104", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "pxfm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand 0.8.6", + "rand_chacha 0.3.1", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "test-utils" +version = "1.0.0" +dependencies = [ + "ledger_device_sdk", + "testmacro", +] + +[[package]] +name = "testmacro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e81321e8c082256753e5af547c7352ff5a9f1958c4c7e7de1eb8289ce65884f2" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b2ff483 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "mintlayer-app" +version.workspace = true +edition = "2021" + +[workspace] +members = ["crates/app-core", "crates/messages", "crates/test-utils"] +resolver = "2" + +[workspace.package] +version = "1.0.0" + +[workspace.dependencies] +bech32 = { version = "0.11", default-features = false } +chrono = { version = "0.4", default-features = false } +const-hex = { version = "1.19", default-features = false } +derive_more = { version = "2.1", default-features = false } +image = "0.25" +ledger_device_sdk = "1.35" +ledger_secure_sdk_sys = "1.16" +num_enum = { version = "0.7", default-features = false } +num-traits = { version = "0.2", default-features = false } +# Note: the testmacro crate is published by Ledger and its source code comes from the `testmacro` +# dir inside the sdk repo, i.e. https://github.com/LedgerHQ/ledger-device-rust-sdk/tree/cad196841dbd72c037cfa01bec81a4a3ae57a04e/testmacro +# (though the published version is a bit older). +testmacro = "0.1" + +mintlayer-app-core = { path = "crates/app-core" } +mintlayer-messages = { path = "crates/messages" } +test-utils = { path = "crates/test-utils" } + +[workspace.dependencies.parity-scale-codec] +git = "https://github.com/paritytech/parity-scale-codec.git" +# Use the specific commit "5021525697edc0661591ebc71392c48d950a10b0", +# which includes a fix for NanoX devices that do not support certain +# atomic operations. +# +# Fix reference: https://github.com/paritytech/parity-scale-codec/pull/751 +# This fix should be included in releases after version 3.7.5. +rev = "5021525697edc0661591ebc71392c48d950a10b0" +default-features = false + +[workspace.dependencies.mintlayer-core-primitives] +git = "https://github.com/mintlayer/mintlayer-core-primitives" +# The commit "Merge pull request #4 from mintlayer/fix_typo". +rev = "8644bfe06d932d687075939d2d175183ba1c369d" +package = "mintlayer-core-primitives" + +[dependencies] +ledger_device_sdk.workspace = true + +mintlayer-app-core.workspace = true + +[build-dependencies] +image.workspace = true + +[[bin]] +# Note: we only have this section to disable tests, but Cargo insists that "name" should also +# be specified. +name = "mintlayer-app" +# The app bin crate is a thin wrapper around `mintlayer-app-core` and it's not supposed to have +# unit tests of its own. So we disable tests completely to prevent Cargo from producing an uncompilable +# test binary. +test = false + +[package.metadata.ledger] +curve = ["secp256k1"] +flags = "0" +path = ["44'/19788'", "44'/1'"] +name = "Mintlayer" + +[package.metadata.ledger.nanox] +icon = "media/icons/mintlayer_14x14.gif" + +[package.metadata.ledger.nanosplus] +icon = "media/icons/mintlayer_14x14.gif" + +[package.metadata.ledger.stax] +icon = "media/icons/mintlayer_32x32.gif" + +[package.metadata.ledger.flex] +icon = "media/icons/mintlayer_40x40.gif" + +[package.metadata.ledger.apex_p] +icon = "media/icons/mintlayer_32x32.png" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 65bdee8..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Mintlayer - -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/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9878636 --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# Ledger Rust Mintlayer Application + +![Rule enforcer](https://github.com/mintlayer/mintlayer-ledger-app/actions/workflows/guidelines_enforcer.yml/badge.svg) ![Build and tests](https://github.com/mintlayer/mintlayer-ledger-app/actions/workflows/build_and_functional_tests.yml/badge.svg) + +This is the Mintlayer Ledger application for the Ledger Nano X, Nano S+, Stax, Flex and Nano Gen 5 devices. + +ℹ️ Nano Gen 5 is usually referred to by its codename, Apex P. + +⚠️ Nano S is not supported. + +## Quick start guide + +### With VS Code + +You can quickly set up a development environment on any platform (macOS, Linux or Windows) to build and test your application with [Ledger's VS Code extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools). + +By using Ledger's own developer tools [Docker image](https://github.com/LedgerHQ/ledger-app-builder/pkgs/container/ledger-app-builder%2Fledger-app-dev-tools), the extension allows you to **build** your apps with the latest SDK, **test** them on **Speculos** and **load** them on any supported device. + +- Install and run [Docker](https://www.docker.com/products/docker-desktop/). +- Make sure you have an X11 server running : + - On Ubuntu Linux, it should be running by default. + - On macOS, install and launch [XQuartz](https://www.xquartz.org/) (make sure to go to XQuartz > Preferences > Security and check "Allow client connections"). + - On Windows, install and launch [VcXsrv](https://sourceforge.net/projects/vcxsrv/) (make sure to configure it to disable access control). +- Install [VS Code](https://code.visualstudio.com/download) and add [Ledger's extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools). +- Open a terminal and clone `mintlayer-ledger-app` with `git clone git@github.com:mintlayer/mintlayer-ledger-app.git`. +- Open the `mintlayer-ledger-app` folder with VS Code. +- Use Ledger extension's sidebar menu or open the tasks menu with `ctrl + shift + b` (`command + shift + b` on a Mac) to conveniently execute actions : + - **Build** the app for the device model of your choice with `Build`. + - **Test** your binary on the [Speculos emulator](https://github.com/LedgerHQ/speculos) with `Run with emulator`. + - You can also **run functional tests**, load the app on a physical device, and more. + +ℹ️ The terminal tab of VS Code will show you what commands the extension runs behind the scene. + +## With a terminal + +### Prerequisites + +If you do not wish to use the [VS Code extension](#with-vs-code), you can follow the following steps to set up a development environment on Linux, Windows or macOS. + +- The [ledger-app-dev-tools](https://github.com/LedgerHQ/ledger-app-builder/pkgs/container/ledger-app-builder%2Fledger-app-dev-tools) Docker image contains all the required tools and libraries to build, test and load an application on a device. You can download it from the ghcr.io docker repository: + + ```bash + docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest + ``` + +- Make sure you have an X11 server running : + - On Ubuntu Linux, it should be running by default. + - On macOS, install and launch [XQuartz](https://www.xquartz.org/) (make sure to go to XQuartz > Preferences > Security and check "Allow client connections"). + - On Windows, install and launch [VcXsrv](https://sourceforge.net/projects/vcxsrv/) (make sure to configure it to disable access control). +- You can then enter into this development environment by executing the following command from the directory of the application (`git` repository): + - Linux (Ubuntu): + ```bash + docker run --user "$(id -u)":"$(id -g)" --rm -ti --privileged -v "/dev/bus/usb:/dev/bus/usb" -v "$(realpath .):/app" --publish 5000:5000 --publish 9999:9999 -e DISPLAY=$DISPLAY -v '/tmp/.X11-unix:/tmp/.X11-unix' ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest + ``` + - macOS: + ```bash + docker run --user "$(id -u)":"$(id -g)" --rm -ti --privileged -v "$(pwd -P):/app" --publish 5000:5000 --publish 9999:9999 -e DISPLAY='host.docker.internal:0' -v '/tmp/.X11-unix:/tmp/.X11-unix' ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest + ``` + - Windows (with PowerShell): + ```bash + docker run --rm -ti --privileged -v "$(Get-Location):/app" -e DISPLAY='host.docker.internal:0' --publish 5000:5000 --publish 9999:9999 ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest + ``` + +The application's code will be available from inside the docker container, you can proceed to the following compilation steps to build your app. + +### Building + +You can build the Mintlayer app with the following command executed in the root directory of the app. + +```bash +cargo ledger build nanox +``` + +This command will build the app for the Nano X, but you can use any supported device (`nanox`, `nanosplus`, `stax`, `flex`, `apex_p`). + +### Testing + +#### Ragger functional tests + +This Mintlayer app comes with functional tests implemented with Ledger's [Ragger](https://github.com/LedgerHQ/ragger) test framework. + +- Install the tests requirements + + ```bash + pip install -r tests/requirements.txt + ``` + +- Run the functional tests : + + ```bash + pytest tests/ --tb=short -v --device {nanosp | nanox | stax | flex | apex_p} + ``` + +ℹ️ Speculos uses `nanosp` for Nano S+ (whereas `ledger_app.toml` lists it as `nanos+`). + +#### Emulator + +You can also run the app directly on the [Speculos emulator](https://github.com/LedgerHQ/speculos) from the Docker container + +#### Nano S+ or X + +```bash +speculos --apdu-port 9999 --api-port 5000 --display headless --model nanosp target/nanosplus/release/mintlayer-app +``` + +⚠️ UI is displayed on `localhost:5000`. + +#### Stax, Flex or Nano Gen 5 + +```bash +speculos --apdu-port 9999 --api-port 5000 --model stax target/stax/release/mintlayer-app +``` + +⚠️ UI is displayed by your X server. + +You can then send APDU using `ledgercomm` (`pip install ledgercomm`): + +``` +ledgercomm-send file test.apdu +``` + +ℹ️ You can also specify the seed phrase via `-s`, e.g.: `-s "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"`. + +### Loading on device + +On Linux, loading the application on a device can be done inside the docker container via: + +```bash +cargo ledger build nanox --load +``` +(the `--privileged -v "/dev/bus/usb:/dev/bus/usb"` part of the Docker run command line allows this). + +It can also be done outside the docker container on all platforms via [ledgerblue](https://pypi.org/project/ledgerblue/): + +* Install `ledgerblue`: + ```bash + pip install ledgerblue + ``` + +* Build the app inside the docker container as usual. + +* Load the app on device, e.g. for Flex: + ```bash + python -m ledgerblue.runScript --targetId --fileName target/flex/release/mintlayer-app.apdu --apdu --scp + ``` + +ℹ️ Your device must be connected, unlocked and the screen showing the dashboard (not inside an application). + +#### About the device target ID + +ledgerblue needs the device's `targetId`. + +If you call ledgerblue manually, note that its `--targetId` defaults to `0x31100002` (Nano S) — wrong for every other device, +and it is **not** auto-detected from the connected device. +The cleanest option is to let ledgerblue read the id straight from the ELF with `--elfFile`, which overrides `--targetId`: + +```bash +python -m ledgerblue.runScript --elfFile target/flex/release/mintlayer-app --fileName target/flex/release/mintlayer-app.apdu --apdu --scp +``` + +If you instead need the raw target ID value (e.g. for a CI script), the [`tools/get_target_id.py`](tools/get_target_id.py) +helper extracts it from the `ledger.target_id` ELF section: +```bash +python tools/get_target_id.py target/flex/release/mintlayer-app # -> 0x33300004 +``` + +## Continuous Integration + +The following workflows are executed in [GitHub Actions](https://github.com/features/actions) : + +- Ledger guidelines enforcer which verifies that an app is compliant with Ledger guidelines. The successful completion of this 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 [ledger-app-workflow](https://github.com/LedgerHQ/ledger-app-workflows). +- Compilation of the application for all supported devices in the [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder) docker image. +- End-to-end tests with the [Speculos](https://github.com/LedgerHQ/speculos) emulator and [ragger](https://github.com/LedgerHQ/ragger) (see [tests/](tests/)). +- Various lint checks performed by Ledger's reusable workflows: + - Source code lint checks with `cargo fmt`. + - Python functional test code lint checks with `pylint` and `mypy`. +- Additional custom checks, run via `run_extra_checks.sh`. +- Unit tests, run via `run_unit_tests.sh`. + +## Additional documentation + +For the additional documentation, see the [docs](docs/) folder. + +- [docs/memory_usage.md](docs/memory_usage.md) - guidelines related to the app's memory usage. +- [docs/technical_specification.md](docs/technical_specification.md) - app's technical specification. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..0b997fb --- /dev/null +++ b/build.rs @@ -0,0 +1,75 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use std::process::Command; + +use image::{ImageFormat, ImageReader, Pixel}; + +// TODO: all image files currently contain the Rust logo; need to replace them with Mintlayer logo. +// See https://github.com/mintlayer/mintlayer-ledger-app/issues/10. + +fn main() { + println!("cargo:rerun-if-changed=script.ld"); + println!("cargo:rerun-if-changed=media/icons/mintlayer_14x14.gif"); + println!("cargo:rerun-if-changed=media/icons/mask_14x14.gif"); + + let icons_path = std::path::PathBuf::from("media/icons"); + let mut gray = ImageReader::open(icons_path.join("mintlayer_14x14.gif")) + .unwrap() + .decode() + .unwrap() + .into_luma8(); + + // Apply mask + let mask = ImageReader::open(icons_path.join("mask_14x14.gif")) + .unwrap() + .decode() + .unwrap() + .into_luma8(); + + for (x, y, mask_pixel) in mask.enumerate_pixels() { + let mask_value = mask_pixel[0]; + let mut gray_pixel = *gray.get_pixel(x, y); + if mask_value == 0 { + gray_pixel = image::Luma([0]); + } else { + gray_pixel.invert(); + } + gray.put_pixel(x, y, gray_pixel); + } + + let glyph_path = std::path::PathBuf::from("media/glyphs"); + gray.save_with_format(glyph_path.join("home_nano_nbgl.png"), ImageFormat::Png) + .unwrap(); + + let output = Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .expect("Failed to execute git command"); + + let git_hash = String::from_utf8(output.stdout).expect("Failed to convert git output to UTF-8"); + + // Expose the Git hash as an environment variable + // TODO: this is unused. Either implement a custom command that would return this info + // (e.g. in the form of a full semantic version), or remove this. + // See https://github.com/mintlayer/mintlayer-ledger-app/issues/11. + println!("cargo:rustc-env=GIT_HASH={}", git_hash.trim()); + + // Rerun the build script if .git/HEAD changes + println!("cargo:rerun-if-changed=.git/HEAD"); +} diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..8feef8a --- /dev/null +++ b/clippy.toml @@ -0,0 +1,3 @@ +# Clippy will complain if objects smaller that this are boxed. +# The default is 200 and we sometimes box slightly smaller objects. +too-large-for-stack = 150 diff --git a/crates/app-core/Cargo.toml b/crates/app-core/Cargo.toml new file mode 100644 index 0000000..44b98f2 --- /dev/null +++ b/crates/app-core/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "mintlayer-app-core" +version.workspace = true +edition = "2024" + +[dependencies] +bech32 = { workspace = true, default-features = false, features = ["alloc"] } +chrono = { workspace = true, default-features = false, features = ["alloc"] } +const-hex = { workspace = true, default-features = false, features = ["alloc"] } +ledger_device_sdk.workspace = true +ledger_secure_sdk_sys.workspace = true + +mintlayer-core-primitives.workspace = true + +mintlayer-messages.workspace = true + +[dev-dependencies] +test-utils.workspace = true + +[features] +default = ["ledger_device_sdk/nano_nbgl"] +debug = ["ledger_device_sdk/debug"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(target_os, values("apex_p", "stax", "flex", "nanos", "nanox", "nanosplus"))', +] } diff --git a/crates/app-core/src/app_ui/address.rs b/crates/app-core/src/app_ui/address.rs new file mode 100644 index 0000000..2eb2577 --- /dev/null +++ b/crates/app-core/src/app_ui/address.rs @@ -0,0 +1,48 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use ledger_device_sdk::{ + ecc::ECPublicKey, + nbgl::{NbglAddressReview, NbglGlyph}, +}; + +use mintlayer_messages::{Destination, PublicKey}; + +use crate::{ + StatusWord, + app_ui::utils::{compress_public_key, load_glyph, to_address}, + mlcp, +}; + +pub fn ui_display_pk( + public_key: &ECPublicKey<65, T>, + coin_type: mlcp::CoinType, +) -> Result { + let pk = compress_public_key(public_key)?; + + let dest = Destination::PublicKey(PublicKey::Secp256k1Schnorr(pk)); + let addr = to_address(&dest, coin_type)?; + + const MINTLAYER: NbglGlyph = load_glyph(); + + // Display the address confirmation screen. + Ok(NbglAddressReview::new() + .glyph(&MINTLAYER) + .review_title("Verify ML address") + .show(&addr)) +} diff --git a/crates/app-core/src/app_ui/menu.rs b/crates/app-core/src/app_ui/menu.rs new file mode 100644 index 0000000..783f649 --- /dev/null +++ b/crates/app-core/src/app_ui/menu.rs @@ -0,0 +1,32 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use ledger_device_sdk::nbgl::{NbglGlyph, NbglHomeAndSettings}; + +use crate::app_ui::utils::load_glyph; + +pub fn ui_menu_main() -> NbglHomeAndSettings { + const MINTLAYER: NbglGlyph = load_glyph(); + + // Display the home screen. + NbglHomeAndSettings::new().glyph(&MINTLAYER).infos( + "Mintlayer", + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_AUTHORS"), + ) +} diff --git a/crates/app-core/src/app_ui/mod.rs b/crates/app-core/src/app_ui/mod.rs new file mode 100644 index 0000000..3763f0a --- /dev/null +++ b/crates/app-core/src/app_ui/mod.rs @@ -0,0 +1,21 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +pub mod address; +pub mod menu; +pub mod sign; +pub mod utils; diff --git a/crates/app-core/src/app_ui/sign.rs b/crates/app-core/src/app_ui/sign.rs new file mode 100644 index 0000000..633efdb --- /dev/null +++ b/crates/app-core/src/app_ui/sign.rs @@ -0,0 +1,658 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use alloc::{format, string::String, string::ToString}; +use chrono::{TimeZone, Utc}; +use core::fmt::Write; + +use ledger_device_sdk::{ + ecc::ECPublicKey, + nbgl::{ + Field, NbglGlyph, NbglReview, NbglStreamingReview, NbglStreamingReviewStatus, + TransactionType, + }, +}; + +use mintlayer_messages::{ + AccountCommand, AccountSpending, AddrType, Amount, DelegationId, Destination, H256, + IsTokenFreezable, IsTokenUnfreezable, NftIssuance, OrderAccountCommand, OrderId, + OutputTimeLock, OutputValue, PoolId, PublicKey, TokenId, TokenIssuance, TokenTotalSupply, + TxOutput, VrfPublicKey, encode, +}; + +use crate::{ + StatusWord, + app_ui::utils::{ + bech32m_encode, compress_public_key, load_glyph, to_address, to_public_key_hash, + }, + handlers::sign_tx::{CoinOrTokenId, InputCommand, TxSummaryCollector, TxType}, + mlcp, + utils::{make_displayable, make_displayable_str, output_value_with_amount}, +}; + +struct FormattedOutput { + name: &'static str, + value: String, +} + +pub fn ui_new_streaming_review() -> NbglStreamingReview { + const MINTLAYER: NbglGlyph = load_glyph(); + + NbglStreamingReview::new() + .glyph(&MINTLAYER) + .tx_type(TransactionType::Transaction) +} + +pub fn ui_start_streaming_review(review: &NbglStreamingReview) -> bool { + review.start("Review transaction", None) +} + +pub fn ui_streaming_review_show_input( + review: &NbglStreamingReview, + input: &InputCommand, + coin: mlcp::CoinType, +) -> Result { + let input = format_input(input, coin)?; + + let fields = [Field { + name: input.name, + value: &input.value, + }]; + + let res = match review.next(&fields) { + NbglStreamingReviewStatus::Rejected => false, + NbglStreamingReviewStatus::Next | NbglStreamingReviewStatus::Skipped => true, + }; + + Ok(res) +} + +pub fn ui_streaming_review_show_output( + review: &NbglStreamingReview, + output: &TxOutput, + coin: mlcp::CoinType, +) -> Result { + let output = format_output(output, coin)?; + + let fields = [Field { + name: output.name, + value: &output.value, + }]; + + let res = match review.next(&fields) { + NbglStreamingReviewStatus::Rejected => false, + NbglStreamingReviewStatus::Next | NbglStreamingReviewStatus::Skipped => true, + }; + + Ok(res) +} + +pub fn ui_approve_streaming_review( + review: &NbglStreamingReview, + tx_summary: &TxSummaryCollector, + coin: mlcp::CoinType, +) -> Result { + let fees = tx_summary.fees_iter().try_fold( + String::new(), + |mut acc, res| -> Result<_, StatusWord> { + let (coin_or_token, fee) = res?; + + match coin_or_token { + CoinOrTokenId::Coin => writeln!( + acc, + "{}", + format_coin_amount_with_name(Amount::from_atoms(fee), coin), + ) + .map_err(|_| StatusWord::TxDisplayFail)?, + + CoinOrTokenId::TokenId(token_id) => { + if fee != 0 { + writeln!( + acc, + "{}", + format_token_amount_with_name(token_id, Amount::from_atoms(fee), coin)?, + ) + .map_err(|_| StatusWord::TxDisplayFail)?; + } + } + }; + + Ok(acc) + }, + )?; + + let fields = [Field { + name: "Fees:", + value: &fees, + }]; + + match review.next(&fields) { + NbglStreamingReviewStatus::Rejected => return Ok(false), + NbglStreamingReviewStatus::Next | NbglStreamingReviewStatus::Skipped => {} + }; + + let title = transaction_title(&tx_summary.tx_type()); + Ok(review.finish(title)) +} + +fn transaction_title(tx_type: &Option) -> &'static str { + match tx_type { + None | Some(TxType::ComplexTransaction) => "Sign transaction", + Some(TxType::Transfer) => "Sign transfer transaction", + Some(TxType::Burn) => "Sign burn transaction", + Some(TxType::Htlc) => "Sign create HTLC transaction", + Some(TxType::CreateDelegation) => "Sign create delegation transaction", + Some(TxType::DelegateStaking) => "Sign delegate staking transaction", + Some(TxType::DelegationWithdrawal) => "Sign delegation withdrawal transaction", + Some(TxType::CreateStakePool) => "Sign create stake pool transaction", + Some(TxType::DecommissionStakePool) => "Sign decommission stake pool transaction", + Some(TxType::CreateNft) => "Sign create NFT transaction", + Some(TxType::CreateToken) => "Sign create token transaction", + Some(TxType::MintTokens) => "Sign mint tokens transaction", + Some(TxType::UnmintTokens) => "Sign unmint tokens transaction", + Some(TxType::FreezeToken) => "Sign freeze tokens transaction", + Some(TxType::UnfreezeToken) => "Sign unfreeze tokens transaction", + Some(TxType::LockTokenSupply) => "Sign lock token supply transaction", + Some(TxType::ChangeTokenAuthority) => "Sign change token authority transaction", + Some(TxType::ChangeTokenMetadataUri) => "Sign change token metadata URI transaction", + Some(TxType::CreateOrder) => "Sign create order transaction", + Some(TxType::FillOrder) => "Sign fill order transaction", + Some(TxType::FreezeOrder) => "Sign freeze order transaction", + Some(TxType::ConcludeOrder) => "Sign conclude order transaction", + Some(TxType::DataDeposit) => "Sign data deposit transaction", + } +} + +/// Displays a message for review and signing confirmation on the device. +/// +/// This function handles both printable text and raw binary data by +/// displaying UTF-8 content directly and falling back to hex encoding for other data. +/// +/// # Arguments +/// +/// * `message` - The message to be signed. +/// * `public_key` - The public key corresponding to the private key that will be used for signing. +/// * `coin_type` - The coin type (mainnet, testnet etc). +/// * `addr_type` - The address type (pk or pkh); this determines how `public_key` will be displayed. +/// +/// # Returns +/// +/// * `Ok(true)` if the user approves the signing. +/// * `Ok(false)` if the user rejects. +/// * `Err(StatusWord)` on error. +pub fn ui_display_message( + message: &[u8], + public_key: &ECPublicKey<65, T>, + coin_type: mlcp::CoinType, + addr_type: AddrType, +) -> Result { + let pk = compress_public_key(public_key)?; + + let dest = match addr_type { + AddrType::PublicKey => Destination::PublicKey(PublicKey::Secp256k1Schnorr(pk)), + AddrType::PublicKeyHash => Destination::PublicKeyHash(to_public_key_hash(&pk)?), + }; + let addr = to_address(&dest, coin_type)?; + + let message_str = make_displayable_str(message); + + let msg_fields = [ + Field { + name: "Address", + value: addr.as_str(), + }, + Field { + name: "Message", + value: &message_str, + }, + ]; + + const MINTLAYER: NbglGlyph = load_glyph(); + + // Create the NBGL review flow with titles appropriate for message signing. + let review: NbglReview = NbglReview::new() + .titles( + // Title + "Review message", + // Subtitle; if non-empty, this will be shown on the second screen on nano devices + // and below the title on the first screen on touch devices. + "", + // Final confirmation prompt + "Sign message", + ) + .tx_type(TransactionType::Message) + .glyph(&MINTLAYER); + + // Show the review screen with the defined fields and return the user's choice. + Ok(review.show(&msg_fields)) +} + +fn vrf_to_address(key: &VrfPublicKey, coin: mlcp::CoinType) -> Result { + bech32m_encode(coin.vrf_public_key_address_prefix(), &encode(key)) +} + +fn id_to_address(id: &H256, hrp: &str) -> Result { + bech32m_encode(hrp, &id.0) +} + +fn token_id_to_address(id: &TokenId, coin: mlcp::CoinType) -> Result { + id_to_address(id.hash(), coin.token_id_address_prefix()) +} + +fn pool_id_to_address(id: &PoolId, coin: mlcp::CoinType) -> Result { + id_to_address(id.hash(), coin.pool_id_address_prefix()) +} + +fn delegation_id_to_address(id: &DelegationId, coin: mlcp::CoinType) -> Result { + id_to_address(id.hash(), coin.delegation_id_address_prefix()) +} + +fn order_id_to_address(id: &OrderId, coin: mlcp::CoinType) -> Result { + id_to_address(id.hash(), coin.order_id_address_prefix()) +} + +/// Format a coin amount as a decimal string. +fn format_coin_amount(amount: Amount, coin: mlcp::CoinType) -> String { + let decimals = coin.coin_decimals() as usize; + let mantissa = amount.into_atoms(); + + // ceil(log10(u128::MAX)) + 1 for decimal point = 40 + // This is not the maximum possible length, but a reasonable expectation of it. + let mut buffer = String::with_capacity(40); + write!(&mut buffer, "{mantissa:0>width$}", width = decimals + 1).unwrap(); + buffer.insert(buffer.len() - decimals, '.'); + buffer +} + +/// Same as format_coin_amount, but also append the coin ticker to the amount. +fn format_coin_amount_with_name(amount: Amount, coin: mlcp::CoinType) -> String { + let mut amount = format_coin_amount(amount, coin); + amount.push(' '); + amount.push_str(coin.coin_ticker()); + amount +} + +fn format_token_amount_with_name( + token_id: &TokenId, + amount: Amount, + coin: mlcp::CoinType, +) -> Result { + let atoms = amount.into_atoms(); + let id_str = id_to_address(token_id.hash(), coin.token_id_address_prefix())?; + + Ok(format!("{atoms} atoms of {id_str}")) +} + +fn format_atoms(amount: Amount) -> String { + format!("{} atoms", amount.into_atoms()) +} + +fn format_value(value: &OutputValue, coin: mlcp::CoinType) -> Result { + match value { + OutputValue::Coin(amount) => Ok(format_coin_amount_with_name(*amount, coin)), + OutputValue::TokenV1(token_id, amount) => { + format_token_amount_with_name(token_id, *amount, coin) + } + } +} + +fn format_timestamp(seconds_u64: u64) -> Result { + let seconds_i64: i64 = seconds_u64 + .try_into() + .map_err(|_| StatusWord::TxLockTimeInvalid)?; + let datetime = Utc + .timestamp_opt(seconds_i64, 0) + .earliest() + .ok_or(StatusWord::TxLockTimeInvalid)?; + + Ok(datetime.format("%Y-%m-%d %H:%M:%S").to_string()) +} + +fn format_lock(lock: &OutputTimeLock) -> Result { + let s = match lock { + OutputTimeLock::UntilHeight(h) => format!("Lock until block height {}", h.0), + OutputTimeLock::UntilTime(t) => format!("Lock until {}", format_timestamp(t.0.0)?), + OutputTimeLock::ForBlockCount(b) => format!("Lock for {} blocks", b.0), + OutputTimeLock::ForSeconds(s) => format!("Lock for {} seconds", s.0), + }; + Ok(s) +} + +/// Formats a transaction output into a FormattedOutput. +/// +/// # Arguments +/// * `output` - A reference to the `TxOutput` enum variant to format. +/// * `coin` - The coin type (mainnet, testnet etc). +/// +/// # Returns +/// A FormattedOutput containing the title and value of the output. +fn format_output(output: &TxOutput, coin: mlcp::CoinType) -> Result { + // Note: on nanox and nanosp the screen space is very limited. Moreover, if the name part of + // a field doesn't fit into one line, it will be shrunk instead of being wrapped to the next + // line, which will make it incomprehensible. + // The limit is about 11-12 characters (e.g. "Chg token auth" is already too long and becomes + // "Chg to...th"). + // So, choose names carefully and always check the screen snapshots after they're regenerated. + // Same for inputs. + + let (name, value) = match output { + TxOutput::Transfer(value, destination) => ( + "Transfer", + format!( + "Destination: {}\n{}\n", + to_address(destination, coin)?, + format_value(value, coin)? + ), + ), + + TxOutput::LockThenTransfer(value, destination, lock) => { + let address_short = format!( + "Destination: {}\n{}\n{}\n", + to_address(destination, coin)?, + format_lock(lock)?, + format_value(value, coin)? + ); + if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + ("LockThenXfer", address_short) + } else { + ("Lock then Transfer", address_short) + } + } + + TxOutput::Burn(value) => ("Burn", format_value(value, coin)?), + + TxOutput::CreateStakePool(pool_id, data) => { + let address_short = format!( + "Pool ID: {}\nStaker key: {}\nDecommission key: {}\nVRF public key: {}\nMargin ratio per thousand: {}\nCost per block: {}\nPledge: {}\n", + pool_id_to_address(pool_id, coin)?, + to_address(&data.staker, coin)?, + to_address(&data.decommission_key, coin)?, + vrf_to_address(&data.vrf_public_key, coin)?, + data.margin_ratio_per_thousand.0, + format_coin_amount_with_name(data.cost_per_block, coin), + format_coin_amount_with_name(data.pledge, coin) + ); + + let name = if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + "Create pool" + } else { + "Create staking pool" + }; + (name, address_short) + } + + TxOutput::ProduceBlockFromStake(destination, _pool_id) => { + let name = if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + "Prod block" + } else { + "Produce block from stake" + }; + + ( + name, + format!("New staker key: {}", to_address(destination, coin)?), + ) + } + TxOutput::CreateDelegationId(destination, pool_id) => { + let address_short = format!( + "Address: {}\nPoolId: {}", + to_address(destination, coin)?, + pool_id_to_address(pool_id, coin)? + ); + + let name = if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + "Create deleg" + } else { + "Create delegation" + }; + + (name, address_short) + } + + TxOutput::DelegateStaking(amount, delegation_id) => { + let name = if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + "Deleg stake" + } else { + "Delegate staking" + }; + + ( + name, + format!( + "\n{}\n{}", + delegation_id_to_address(delegation_id, coin)?, + format_value(&OutputValue::Coin(*amount), coin)?, + ), + ) + } + TxOutput::IssueFungibleToken(x) => { + let TokenIssuance::V1(data) = x; + + // Note: currently the consensus rules require that a token ticker can only be + // ascii alphanumeric, so we could just reject non-ascii tickers here. + // We use `make_displayable` here for simplicity and consistency. + let ticker = make_displayable(&data.token_ticker); + // Note: a metadata URI is allowed to have non-ascii chars, so `make_displayable` + // is not redundant here. + let metadata_uri = make_displayable(&data.metadata_uri); + + let total_supply_str = match data.total_supply { + TokenTotalSupply::Unlimited => "UNLIMITED".to_string(), + TokenTotalSupply::Lockable => "LOCKABLE".to_string(), + TokenTotalSupply::Fixed(amount) => { + let formatted_amount = format_atoms(amount); + format!("FIXED {}", formatted_amount) + } + }; + let is_freezable = match data.is_freezable { + IsTokenFreezable::Yes => "Yes", + IsTokenFreezable::No => "No", + }; + + let address_short = format!( + "Ticker: {}\nAuthority: {}\nMetadata URI: {}\nTotal token supply: {}\nNumber of decimals: {}\nIs freezable: {}", + ticker, + to_address(&data.authority, coin)?, + metadata_uri, + total_supply_str, + data.number_of_decimals, + is_freezable + ); + let name = if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + "Issue token" + } else { + "Issue fungible token" + }; + + (name, address_short) + } + + TxOutput::IssueNft(_nft_id, data, destination) => { + let NftIssuance::V0(data) = data; + // Note: + // 1. Consensus rules require that name, description and ticker are ascii alphanumeric. + // But same as in the IssueFungibleToken case, we use `make_displayable` for consistency + // and simplicity. + // 2. The URIs are allowed to have non-ascii chars, so `make_displayable` is not redundant + // for them. + // 3. There is no point in reviewing the NFT id - consensus rules require that the id is + // calculated from transaction inputs and if the host cheats or malfunctions, the + // transaction will become invalid (note that the id is only present in the output due + // to historical reasons and e.g. IssueFungibleToken doesn't contain the token id). + let address_short = format!( + "Name: {}\nDescription: {}\nCreator: {}\nTicker: {}\nAddress: {}\nIcon URI: {}\nAdditional metadata URI: {}\nMedia URI: {}, Media hash: {}", + make_displayable(&data.name), + make_displayable(&data.description), + data.creator + .clone() + .map(|creator| to_address(&Destination::PublicKey(creator), coin)) + .transpose()? + .unwrap_or_default(), + make_displayable(&data.ticker), + to_address(destination, coin)?, + make_displayable(&data.icon_uri), + make_displayable(&data.additional_metadata_uri), + make_displayable(&data.media_uri), + make_displayable(&data.media_hash), + ); + + ("Issue NFT", address_short) + } + + TxOutput::DataDeposit(data) => ("Data deposit", const_hex::encode(data)), + + TxOutput::Htlc(value, data) => { + let address_short = format!( + "Secret hash: {:x}\nSpend key: {}\nRefund key: {}\nRefund time lock: {}\n{}", + const_hex::display(data.secret_hash.0), + to_address(&data.spend_key, coin)?, + to_address(&data.refund_key, coin)?, + format_lock(&data.refund_timelock)?, + format_value(value, coin)? + ); + ("HTLC", address_short) + } + + TxOutput::CreateOrder(data) => { + let ask_amount = format_value(&data.ask, coin)?; + let give_amount = format_value(&data.give, coin)?; + let address_short = format!( + "Conclude key: {}\nAsk: {}\nGive: {}", + to_address(&data.conclude_key, coin)?, + ask_amount, + give_amount + ); + ("Create order", address_short) + } + }; + + Ok(FormattedOutput { name, value }) +} + +fn format_input(input: &InputCommand, coin: mlcp::CoinType) -> Result { + let (name, value) = match input { + InputCommand::AccountSpending(cmd) => match cmd { + AccountSpending::DelegationBalance(delegation_id, amount) => { + let address_short = format!( + "Delegation ID: {}\nAmount: {}", + delegation_id_to_address(delegation_id, coin)?, + format_coin_amount_with_name(*amount, coin) + ); + if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + ("Deleg wdrwl", address_short) + } else { + ("Delegation withdrawal", address_short) + } + } + }, + InputCommand::AccountCommand(cmd) => match cmd { + AccountCommand::MintTokens(token_id, amount) => { + let address_short = format!( + "Token ID: {}\nAmount: {}", + token_id_to_address(token_id, coin)?, + format_atoms(*amount) + ); + ("Mint tokens", address_short) + } + AccountCommand::UnmintTokens(token_id) => { + let address_short = format!("Token ID: {}", token_id_to_address(token_id, coin)?,); + ("Unmint tokens", address_short) + } + AccountCommand::LockTokenSupply(token_id) => { + let address_short = format!("Token ID: {}", token_id_to_address(token_id, coin)?); + if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + ("Lock supply", address_short) + } else { + ("Lock token supply", address_short) + } + } + AccountCommand::FreezeToken(token_id, is_unfreezable) => { + let address_short = format!( + "Token ID: {}\nIs unfreezable: {}", + token_id_to_address(token_id, coin)?, + if *is_unfreezable == IsTokenUnfreezable::Yes { + "Yes" + } else { + "No" + } + ); + ("Freeze token", address_short) + } + AccountCommand::UnfreezeToken(token_id) => { + let address_short = format!("Token ID: {}", token_id_to_address(token_id, coin)?,); + if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + ("Unfrz token", address_short) + } else { + ("Unfreeze token", address_short) + } + } + AccountCommand::ChangeTokenAuthority(token_id, new_authority) => { + let address_short = format!( + "Token ID: {}\nNew authority: {}", + token_id_to_address(token_id, coin)?, + to_address(new_authority, coin)? + ); + if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + ("Chg tkn auth", address_short) + } else { + ("Change token authority", address_short) + } + } + AccountCommand::ChangeTokenMetadataUri(token_id, new_metadata_uri) => { + let address_short = format!( + "Token ID: {}\nNew metadata URI: {}", + token_id_to_address(token_id, coin)?, + make_displayable(new_metadata_uri) + ); + if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + ("Chg tkn meta", address_short) + } else { + ("Change token metadata URI", address_short) + } + } + AccountCommand::ConcludeOrder(_) | AccountCommand::FillOrder(_, _, _) => { + return Err(StatusWord::OrdersV0NotSupported); + } + }, + InputCommand::OrderCommand(cmd, additional_info) => match cmd { + OrderAccountCommand::FillOrder(order_id, fill_amount) => { + let fill_value = + output_value_with_amount(&additional_info.initially_asked, *fill_amount); + let address_short = format!( + "Order ID: {}\nFill amount: {}", + order_id_to_address(order_id, coin)?, + format_value(&fill_value, coin)?, + ); + ("Fill order", address_short) + } + OrderAccountCommand::FreezeOrder(order_id) => { + let address_short = format!("Order ID: {}", order_id_to_address(order_id, coin)?); + ("Freeze order", address_short) + } + OrderAccountCommand::ConcludeOrder(order_id) => { + let address_short = format!("Order ID: {}", order_id_to_address(order_id, coin)?); + if cfg!(any(target_os = "nanosplus", target_os = "nanox")) { + ("Conclude ord", address_short) + } else { + ("Conclude order", address_short) + } + } + }, + }; + + Ok(FormattedOutput { name, value }) +} diff --git a/crates/app-core/src/app_ui/utils.rs b/crates/app-core/src/app_ui/utils.rs new file mode 100644 index 0000000..614d716 --- /dev/null +++ b/crates/app-core/src/app_ui/utils.rs @@ -0,0 +1,90 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use alloc::string::String; + +use ledger_device_sdk::{ecc::ECPublicKey, include_gif, nbgl::NbglGlyph}; + +use mintlayer_core_primitives::PUBLIC_KEY_HASH_SIZE; +use mintlayer_messages::{Destination, PublicKeyHash, Secp256k1PublicKey, encode, encode_to}; + +use crate::{StatusWord, hasher::Hasher, mlcp, utils::cut_array}; + +pub fn bech32m_encode(hrp: &str, data: &[u8]) -> Result { + let parsed_hrp = bech32::Hrp::parse(hrp).map_err(|_| StatusWord::AddressEncodingFail)?; + + let encoded = bech32::encode::(parsed_hrp, data) + .map_err(|_| StatusWord::AddressEncodingFail)?; + + Ok(encoded) +} + +pub fn to_address(destination: &Destination, coin: mlcp::CoinType) -> Result { + let hrp = coin.address_prefix(destination.into()); + bech32m_encode(hrp, &encode(destination)) +} + +/// Load glyph from file with include_gif macro. Creates an NBGL compatible glyph. +pub const fn load_glyph() -> NbglGlyph<'static> { + #[cfg(target_os = "apex_p")] + const MINTLAYER: NbglGlyph = + NbglGlyph::from_include(include_gif!("../../media/glyphs/mintlayer_48x48.png", NBGL)); + #[cfg(any(target_os = "stax", target_os = "flex"))] + const MINTLAYER: NbglGlyph = + NbglGlyph::from_include(include_gif!("../../media/glyphs/mintlayer_64x64.gif", NBGL)); + #[cfg(any(target_os = "nanosplus", target_os = "nanox"))] + const MINTLAYER: NbglGlyph = + NbglGlyph::from_include(include_gif!("../../media/icons/mintlayer_14x14.gif", NBGL)); + + MINTLAYER +} + +pub fn compress_public_key( + public_key: &ECPublicKey<65, T>, +) -> Result { + let uncompressed_key = &public_key.pubkey; + if uncompressed_key[0] != 0x04 { + return Err(StatusWord::InvalidUncompressedPublicKey); + } + + let mut compressed_key = [0u8; 33]; + + let y_coordinate = &uncompressed_key[33..65]; + let prefix = if y_coordinate[31].is_multiple_of(2) { + 0x02 + } else { + 0x03 + }; + + compressed_key[0] = prefix; + + let x_coordinate = &uncompressed_key[1..33]; + compressed_key[1..].copy_from_slice(x_coordinate); + + Ok(Secp256k1PublicKey(compressed_key)) +} + +pub fn to_public_key_hash(pk: &Secp256k1PublicKey) -> Result { + let mut hasher = Hasher::new(); + + encode_to(mlcp::PublicKey::Secp256k1Schnorr(*pk), &mut hasher); + + let full_hash = hasher.finalize()?; + let pkh: [u8; PUBLIC_KEY_HASH_SIZE] = cut_array(full_hash.as_fixed_bytes()); + + Ok(PublicKeyHash(pkh)) +} diff --git a/crates/app-core/src/errors.rs b/crates/app-core/src/errors.rs new file mode 100644 index 0000000..f4faa50 --- /dev/null +++ b/crates/app-core/src/errors.rs @@ -0,0 +1,56 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use ledger_device_sdk::ecc::CxError; +use mintlayer_messages::StatusWord; + +pub fn cx_err_to_status(e: CxError) -> StatusWord { + match e { + CxError::Carry => StatusWord::EccCarry, + CxError::Locked => StatusWord::EccLocked, + CxError::Unlocked => StatusWord::EccUnlocked, + CxError::NotLocked => StatusWord::EccNotLocked, + CxError::NotUnlocked => StatusWord::EccNotUnlocked, + CxError::InternalError => StatusWord::EccInternalError, + CxError::InvalidParameterSize => StatusWord::EccInvalidParameterSize, + CxError::InvalidParameterValue => StatusWord::EccInvalidParameterValue, + CxError::InvalidParameter => StatusWord::EccInvalidParameter, + CxError::NotInvertible => StatusWord::EccNotInvertible, + CxError::Overflow => StatusWord::EccOverflow, + CxError::MemoryFull => StatusWord::EccMemoryFull, + CxError::NoResidue => StatusWord::EccNoResidue, + CxError::PointAtInfinity => StatusWord::EccPointAtInfinity, + CxError::InvalidPoint => StatusWord::EccInvalidPoint, + CxError::InvalidCurve => StatusWord::EccInvalidCurve, + CxError::GenericError => StatusWord::EccGenericError, + } +} + +pub fn sdk_err_to_status(e: ledger_device_sdk::io::StatusWords) -> StatusWord { + match e { + ledger_device_sdk::io::StatusWords::Ok => StatusWord::Ok, + ledger_device_sdk::io::StatusWords::BadCla => StatusWord::ClaNotSupported, + ledger_device_sdk::io::StatusWords::NothingReceived => StatusWord::NothingReceived, + ledger_device_sdk::io::StatusWords::BadIns => StatusWord::InsNotSupported, + ledger_device_sdk::io::StatusWords::BadP1P2 => StatusWord::WrongP1P2, + ledger_device_sdk::io::StatusWords::BadLen => StatusWord::WrongApduLength, + ledger_device_sdk::io::StatusWords::UserCancelled => StatusWord::Deny, + ledger_device_sdk::io::StatusWords::Unknown => StatusWord::Unknown, + ledger_device_sdk::io::StatusWords::Panic => StatusWord::Panic, + ledger_device_sdk::io::StatusWords::DeviceLocked => StatusWord::DeviceLocked, + } +} diff --git a/crates/app-core/src/handlers/get_public_key.rs b/crates/app-core/src/handlers/get_public_key.rs new file mode 100644 index 0000000..acd5409 --- /dev/null +++ b/crates/app-core/src/handlers/get_public_key.rs @@ -0,0 +1,46 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use ledger_device_sdk::ecc::{Secp256k1, SeedDerive}; + +use mintlayer_messages::{ + ChainCode, GetPubKeyReq, PublicKeyResponse, UncompressedSecp256k1PublicKey, +}; + +use crate::{StatusWord, app_ui::address::ui_display_pk, utils::check_derivation_path}; + +pub fn handle_get_public_key( + req: GetPubKeyReq, + display: bool, +) -> Result { + check_derivation_path(req.path.as_ref(), req.coin_type.into())?; + + let (k, cc) = Secp256k1::derive_from(req.path.as_ref()); + let pk = k.public_key().map_err(|_| StatusWord::KeyDeriveFail)?; + let code = cc.ok_or(StatusWord::KeyDeriveFail)?; + + // Display address on device if requested + if display && !ui_display_pk(&pk, req.coin_type.into())? { + return Err(StatusWord::Deny); + } + let response = PublicKeyResponse { + public_key: UncompressedSecp256k1PublicKey(pk.pubkey), + chain_code: ChainCode(code.value), + }; + + Ok(response) +} diff --git a/crates/app-core/src/handlers/mod.rs b/crates/app-core/src/handlers/mod.rs new file mode 100644 index 0000000..82f67fc --- /dev/null +++ b/crates/app-core/src/handlers/mod.rs @@ -0,0 +1,20 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +pub mod get_public_key; +pub mod sign_message; +pub mod sign_tx; diff --git a/crates/app-core/src/handlers/sign_message.rs b/crates/app-core/src/handlers/sign_message.rs new file mode 100644 index 0000000..67f3736 --- /dev/null +++ b/crates/app-core/src/handlers/sign_message.rs @@ -0,0 +1,140 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use alloc::vec::Vec; + +use ledger_device_sdk::ecc::{ECPrivateKey, Secp256k1, SeedDerive}; +use ledger_secure_sdk_sys::*; + +use mintlayer_messages::{ + AddrType, Bip32Path, MsgSignatureResponse, SignMessageStartReq, Signature, +}; + +use crate::{ + DataContext, StatusWord, app_ui::sign::ui_display_message, errors::cx_err_to_status, + hasher::Hasher, mlcp, utils::check_derivation_path, +}; + +pub struct SignMessageContext { + path: Bip32Path, + coin: mlcp::CoinType, + addr_type: AddrType, + review_finished: bool, +} + +impl SignMessageContext { + pub fn new(req: SignMessageStartReq) -> Self { + Self { + path: req.path, + coin: req.coin.into(), + addr_type: req.addr_type, + review_finished: false, + } + } + + pub fn finished(&self) -> bool { + self.review_finished + } +} + +pub fn setup_sign_message(req: SignMessageStartReq) -> Result { + check_derivation_path(req.path.as_ref(), req.coin.into())?; + + Ok(DataContext::SignMessageContext(SignMessageContext::new( + req, + ))) +} + +// TODO: implement stateful message signing, where the message is received and displayed for review +// in portions, to allow signing messages of arbitrary sizes. +// See https://github.com/mintlayer/mintlayer-ledger-app/issues/13. +pub fn handle_sign_message( + message: &[u8], + ctx: &mut SignMessageContext, +) -> Result { + let private_key = Secp256k1::derive_from_path(ctx.path.as_ref()); + let public_key = private_key + .public_key() + .map_err(|_| StatusWord::KeyDeriveFail)?; + + // Display review. If user approves, sign it. + // Otherwise, return a "deny" status word. + if ui_display_message(message, &public_key, ctx.coin, ctx.addr_type)? { + ctx.review_finished = true; + Ok(compute_signature(&private_key, message)?) + } else { + ctx.review_finished = true; + Err(StatusWord::Deny) + } +} + +fn compute_signature( + private_key: &ECPrivateKey, + message: &[u8], +) -> Result { + const MESSAGE_MAGIC_PREFIX: &str = "===MINTLAYER MESSAGE BEGIN===\n"; + const MESSAGE_MAGIC_SUFFIX: &str = "\n===MINTLAYER MESSAGE END==="; + + let message = MESSAGE_MAGIC_PREFIX + .as_bytes() + .iter() + .chain(message.iter()) + .chain(MESSAGE_MAGIC_SUFFIX.as_bytes().iter()) + .copied() + .collect::>(); + + let message_hash = Hasher::hash(&message)?; + let message_hash2 = Hasher::hash(message_hash.as_bytes())?; + + let sig = schnorr_sign(private_key, message_hash2.as_bytes())?; + + let response = MsgSignatureResponse { + signature: Signature(sig), + }; + + Ok(response) +} + +pub fn schnorr_sign( + private_key: &ECPrivateKey, + msg: &[u8], +) -> Result<[u8; 64], StatusWord> { + const HASH_ALGORITHM_ID: u8 = CX_SHA256; + const SIGNING_MODE: u32 = CX_ECSCHNORR_BIP0340 | CX_RND_PROVIDED | CX_LAST; + + let mut sig = [0u8; 64]; + let mut sig_len = 64; + + // The `unsafe` block is required for FFI. + let err_code = unsafe { + cx_ecschnorr_sign_no_throw( + private_key as *const ECPrivateKey as *const cx_ecfp_256_private_key_s, + SIGNING_MODE, + HASH_ALGORITHM_ID, + msg.as_ptr(), + msg.len(), + sig.as_mut_ptr(), + &mut sig_len, + ) + }; + + if err_code != CX_OK { + Err(cx_err_to_status(err_code.into())) + } else { + Ok(sig) + } +} diff --git a/crates/app-core/src/handlers/sign_tx/mod.rs b/crates/app-core/src/handlers/sign_tx/mod.rs new file mode 100644 index 0000000..20a6f16 --- /dev/null +++ b/crates/app-core/src/handlers/sign_tx/mod.rs @@ -0,0 +1,516 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use alloc::{boxed::Box, vec::Vec}; + +use ledger_device_sdk::{ + ecc::{Secp256k1, SeedDerive}, + nbgl::{NbglSpinner, NbglStreamingReview}, +}; + +use mintlayer_messages::{ + H256, InputAddressPath, OrderAccountCommand, Response, SignTxNextReq, SignTxStartReq, + Signature, TransactionVersion, TxInputCommitmentData, TxInputData, TxInputSignatureResponse, + TxInputWithAdditionalInfo, TxOutputData, encode_as_compact_to, encode_to, +}; + +use crate::{ + DataContext, StatusWord, + app_ui::sign::{ + ui_approve_streaming_review, ui_new_streaming_review, ui_start_streaming_review, + ui_streaming_review_show_input, ui_streaming_review_show_output, + }, + handlers::sign_message::schnorr_sign, + hasher::Hasher, + mlcp, + utils::{CompressedDerivationPathForTxSigning, check_derivation_path_for_tx_signing}, +}; + +mod summary_collector; + +pub use summary_collector::{CoinOrTokenId, InputCommand, TxSummaryCollector, TxType}; + +// u32 is enough to cover max possible number of inputs and outputs (note that usize is also +// 32-bit on Ledger, but we want to be specific about size) +type Index = u32; + +/// A "signature target". Usually, one input will produce one SigTarget (more than one SigTarget +/// is possible in the case of multisig; it can also be zero for non-signable pseudo-inputs, such +/// as FillOrder). +pub struct SigTarget { + pub path: CompressedDerivationPathForTxSigning, + pub input_idx: Index, + pub multisig_idx: Option, +} + +impl SigTarget { + fn new( + addr: InputAddressPath, + input_idx: Index, + coin: mlcp::CoinType, + ) -> Result { + let path = check_derivation_path_for_tx_signing(addr.path.as_ref(), coin)?; + + Ok(Self { + path, + input_idx, + multisig_idx: addr.multisig_idx, + }) + } +} + +pub struct TxMetadata { + coin: mlcp::CoinType, + num_inputs: Index, + num_outputs: Index, +} + +pub struct TxInputsProcessingContext { + metadata: TxMetadata, + + tx_hasher: Hasher, + + // Note: input commitments have to be sent together with the inputs, because they contain + // the actual amounts that the inputs consume. But they can't be put into the transaction hasher + // until all inputs have been processed, so they'll have to be sent again via a separate pass. + // We hash the commitments to ensure that the same ones are sent during both passes. + input_commitments_hasher: Hasher, + + summary: TxSummaryCollector, + sig_targets: Vec, + + spinner: NbglSpinner, + + num_inputs_parsed: Index, +} + +impl TxInputsProcessingContext { + fn advance_next_input_step(mut self: Box) -> Result { + self.num_inputs_parsed += 1; + let finished_with_inputs = self.num_inputs_parsed >= self.metadata.num_inputs; + + if finished_with_inputs { + if self.sig_targets.is_empty() { + return Err(StatusWord::NothingToSign); + } + + // Update hash for input commitments and proceed with outputs + self.tx_hasher + .update(&self.metadata.num_inputs.to_le_bytes()); + + let input_commitments_hash = self.input_commitments_hasher.finalize()?; + + Ok(TxProcessingContext::ProcessingInputCommitments(Box::new( + TxInputCommitmentsProcessingContext { + metadata: self.metadata, + tx_hasher: self.tx_hasher, + input_commitments_hasher: Hasher::new(), + expected_input_commitments_hash: input_commitments_hash, + summary: self.summary, + sig_targets: self.sig_targets, + spinner: self.spinner, + num_inputs_parsed: 0, + }, + ))) + } else { + Ok(TxProcessingContext::ProcessingInputs(self)) + } + } +} + +pub struct TxInputCommitmentsProcessingContext { + metadata: TxMetadata, + + tx_hasher: Hasher, + input_commitments_hasher: Hasher, + expected_input_commitments_hash: H256, + + summary: TxSummaryCollector, + sig_targets: Vec, + + spinner: NbglSpinner, + + num_inputs_parsed: Index, +} + +impl TxInputCommitmentsProcessingContext { + fn advance_next_input_additional_info_step( + mut self: Box, + review: &NbglStreamingReview, + ) -> Result { + self.num_inputs_parsed += 1; + + let finished_with_inputs = self.num_inputs_parsed >= self.metadata.num_inputs; + + if finished_with_inputs { + // Make sure the hashes match before continuing with the outputs + let input_commitments_hash = self.input_commitments_hasher.finalize()?; + + if input_commitments_hash != self.expected_input_commitments_hash { + return Err(StatusWord::DifferentInputCommitmentHash); + } + + if !ui_start_streaming_review(review) { + return Err(StatusWord::Deny); + } + + if let Some(command) = self.summary.input_command() + && !ui_streaming_review_show_input(review, command, self.metadata.coin)? + { + return Err(StatusWord::Deny); + } + + encode_as_compact_to(self.metadata.num_outputs, &mut self.tx_hasher); + + if self.metadata.num_outputs > 0 { + let new_context = + TxProcessingContext::ProcessingOutputs(Box::new(TxOutputsProcessingContext { + metadata: self.metadata, + tx_hasher: self.tx_hasher, + summary: self.summary, + sig_targets: self.sig_targets, + spinner: self.spinner, + num_outputs_parsed: 0, + })); + Ok(new_context) + } else { + switch_to_signing( + review, + self.tx_hasher, + self.summary, + self.metadata, + self.sig_targets, + self.spinner, + ) + } + } else { + Ok(TxProcessingContext::ProcessingInputCommitments(self)) + } + } +} + +pub struct TxOutputsProcessingContext { + metadata: TxMetadata, + + tx_hasher: Hasher, + + summary: TxSummaryCollector, + sig_targets: Vec, + + spinner: NbglSpinner, + + num_outputs_parsed: Index, +} + +impl TxOutputsProcessingContext { + pub fn coin(&self) -> mlcp::CoinType { + self.metadata.coin + } + + pub fn summary(&self) -> &TxSummaryCollector { + &self.summary + } + + fn advance_next_output_state( + mut self: Box, + review: &NbglStreamingReview, + ) -> Result { + if self.num_outputs_parsed < (self.metadata.num_outputs - 1) { + self.num_outputs_parsed += 1; + Ok(TxProcessingContext::ProcessingOutputs(self)) + } else { + switch_to_signing( + review, + self.tx_hasher, + self.summary, + self.metadata, + self.sig_targets, + self.spinner, + ) + } + } +} + +fn switch_to_signing( + review: &NbglStreamingReview, + tx_hasher: Hasher, + summary: TxSummaryCollector, + metadata: TxMetadata, + sig_targets: Vec, + spinner: NbglSpinner, +) -> Result { + if ui_approve_streaming_review(review, &summary, metadata.coin)? { + // Finalize the tx hash for signing + let first_hash = tx_hasher.finalize()?; + let tx_hash = Hasher::hash(first_hash.as_bytes())?; + + Ok(TxProcessingContext::Signing(Box::new(TxSigningContext { + metadata, + sig_targets, + spinner, + num_sigs_produced: 0, + tx_hash, + }))) + } else { + Err(StatusWord::Deny) + } +} + +pub struct TxSigningContext { + metadata: TxMetadata, + tx_hash: H256, + + sig_targets: Vec, + + spinner: NbglSpinner, + + num_sigs_produced: Index, +} + +impl TxSigningContext { + fn compute_signature_and_append( + mut self: Box, + ) -> Result<(TxInputSignatureResponse, TxProcessingContext), StatusWord> { + let sig_target = self + .sig_targets + .get(self.num_sigs_produced as usize) + .ok_or(StatusWord::WrongContext)?; + + let path = sig_target.path.to_full_path(self.metadata.coin); + let private_key = Secp256k1::derive_from_path(&path); + let sig = schnorr_sign(&private_key, self.tx_hash.as_bytes())?; + + let signature = Signature(sig); + let input_idx = sig_target.input_idx; + let multisig_idx = sig_target.multisig_idx; + + let has_next = ((self.num_sigs_produced + 1) as usize) < self.sig_targets.len(); + + let response = TxInputSignatureResponse { + signature, + multisig_idx, + input_idx, + has_next, + }; + + let new_ctx = if has_next { + self.num_sigs_produced += 1; + TxProcessingContext::Signing(self) + } else { + TxProcessingContext::Finished + }; + + Ok((response, new_ctx)) + } +} + +pub enum TxProcessingContext { + ProcessingInputs(Box), + ProcessingInputCommitments(Box), + ProcessingOutputs(Box), + Signing(Box), + Finished, +} + +impl TxProcessingContext { + pub fn new( + SignTxStartReq { + coin, + version, + num_inputs, + num_outputs, + }: SignTxStartReq, + ) -> Result { + if num_inputs == 0 { + return Err(StatusWord::TxWithZeroInputs); + } + + match version { + TransactionVersion::V1 => { + const VERSION_1: u8 = 1; + const SIG_HASH_TYPE_ALL: u8 = 1; + + let mut tx_hasher = Hasher::new(); + // mode + tx_hasher.update(&[SIG_HASH_TYPE_ALL]); + // version + tx_hasher.update(&[VERSION_1]); + // flags + tx_hasher.update(&[0; 16]); + + tx_hasher.update(&num_inputs.to_le_bytes()); + + Ok(Self::ProcessingInputs(Box::new( + TxInputsProcessingContext { + metadata: TxMetadata { + coin: coin.into(), + num_inputs, + num_outputs, + }, + tx_hasher, + spinner: NbglSpinner::new(), + summary: TxSummaryCollector::new(), + num_inputs_parsed: 0, + input_commitments_hasher: Hasher::new(), + sig_targets: Vec::new(), + }, + ))) + } + } + } + + /// Shows a spinner while processing the inputs and input commitments if there are more than a few, + /// as well as while signing and returning the signatures. + pub fn show_spinner(&mut self) { + let (metadata, spinner) = match self { + Self::ProcessingInputs(ctx) => (&ctx.metadata, &mut ctx.spinner), + Self::ProcessingInputCommitments(ctx) => (&ctx.metadata, &mut ctx.spinner), + Self::Signing(ctx) => { + ctx.spinner.show("Signing..."); + return; + } + // While parsing outputs we are showing the review and not the spinner. + + // TODO: inputs may need to be reviewed one by one, just like outputs. + // See https://github.com/mintlayer/mintlayer-ledger-app/issues/14. + // Also see the TODO near `TxSummaryCollector::input_command`. + + // TODO: change outputs should be detected and not presented for review; once this is + // implemented, the spinner logic may need to be revised. + // See https://github.com/mintlayer/mintlayer-ledger-app/issues/17. + Self::ProcessingOutputs(_) | Self::Finished => return, + }; + + // We show a spinner while processing the inputs and input commitments if there are more than 5; + // 5 was chosen somewhat arbitrarily + let transaction_has_many_inputs = metadata.num_inputs > 5; + + if transaction_has_many_inputs { + spinner.show("Processing transaction..."); + } + } + + pub fn finished(&self) -> bool { + matches!(self, Self::Finished) + } +} + +pub fn setup_sign_tx(req: SignTxStartReq) -> Result { + let mut tx_ctx = TxProcessingContext::new(req)?; + + tx_ctx.show_spinner(); + + Ok(DataContext::TxContext(tx_ctx, ui_new_streaming_review())) +} + +fn handle_input( + input_data: Box, + mut ctx: Box, +) -> Result { + // FillOrder inputs are pseudo-inputs that should not be signed; if the host requests + // a signature in such a case, it is doing something wrong, so we reject it. + // Note that we only check for V1 orders here, since V0 are not supported by the app + // (see StatusWord::OrdersV0NotSupported). + if is_v1_fill_order_input(&input_data.input) && !input_data.addresses.is_empty() { + return Err(StatusWord::FillOrderSigRequested); + } + + let num_inputs_parsed = ctx.num_inputs_parsed; + let sig_targets = input_data + .addresses + .into_iter() + .map(|a| SigTarget::new(a, num_inputs_parsed, ctx.metadata.coin)) + .collect::, StatusWord>>()?; + ctx.sig_targets.extend(sig_targets); + + ctx.summary.process_input(&input_data.input)?; + + let (input, commitment) = input_data.input.into_input_and_commitment(); + encode_to(&commitment, &mut ctx.input_commitments_hasher); + encode_to(&input, &mut ctx.tx_hasher); + ctx.advance_next_input_step() +} + +fn is_v1_fill_order_input(input: &TxInputWithAdditionalInfo) -> bool { + match input { + TxInputWithAdditionalInfo::OrderAccountCommand(cmd, _) => match cmd { + OrderAccountCommand::FillOrder(_, _) => true, + OrderAccountCommand::FreezeOrder(_) | OrderAccountCommand::ConcludeOrder(_) => false, + }, + TxInputWithAdditionalInfo::Utxo(_, _) + | TxInputWithAdditionalInfo::Account(_) + | TxInputWithAdditionalInfo::AccountCommand(_, _) => false, + } +} + +fn handle_input_commitment( + comm_data: &TxInputCommitmentData, + mut ctx: Box, + review: &NbglStreamingReview, +) -> Result { + encode_to(&comm_data.commitment, &mut ctx.input_commitments_hasher); + encode_to(&comm_data.commitment, &mut ctx.tx_hasher); + ctx.advance_next_input_additional_info_step(review) +} + +fn handle_output( + output_data: &TxOutputData, + mut ctx: Box, + review: &NbglStreamingReview, +) -> Result { + if ui_streaming_review_show_output(review, &output_data.output, ctx.metadata.coin)? { + ctx.summary.process_output(&output_data.output)?; + encode_to(&output_data.output, &mut ctx.tx_hasher); + ctx.advance_next_output_state(review) + } else { + Err(StatusWord::Deny) + } +} + +pub fn handle_sign_tx( + req: SignTxNextReq, + ctx: TxProcessingContext, + review: &mut NbglStreamingReview, +) -> Result<(Response, TxProcessingContext), StatusWord> { + match (req, ctx) { + (SignTxNextReq::ProcessInput(req), TxProcessingContext::ProcessingInputs(ctx)) => { + let new_ctx = handle_input(req, ctx)?; + Ok((Response::TxNext, new_ctx)) + } + ( + SignTxNextReq::ProcessInputCommitment(req), + TxProcessingContext::ProcessingInputCommitments(ctx), + ) => { + let new_ctx = handle_input_commitment(req.as_ref(), ctx, review)?; + Ok((Response::TxNext, new_ctx)) + } + (SignTxNextReq::ProcessOutput(req), TxProcessingContext::ProcessingOutputs(ctx)) => { + let new_ctx = handle_output(req.as_ref(), ctx, review)?; + Ok((Response::TxNext, new_ctx)) + } + (SignTxNextReq::ReturnNextSignature, TxProcessingContext::Signing(ctx)) => { + let (response, mut new_ctx) = ctx.compute_signature_and_append()?; + new_ctx.show_spinner(); + + Ok((Response::TxInputSignature(response), new_ctx)) + } + (SignTxNextReq::ReturnNextSignature, TxProcessingContext::Finished) => { + Err(StatusWord::TxAlreadyFinished) + } + _ => Err(StatusWord::WrongContext), + } +} diff --git a/crates/app-core/src/handlers/sign_tx/summary_collector.rs b/crates/app-core/src/handlers/sign_tx/summary_collector.rs new file mode 100644 index 0000000..2425ea8 --- /dev/null +++ b/crates/app-core/src/handlers/sign_tx/summary_collector.rs @@ -0,0 +1,1066 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use alloc::collections::BTreeMap; + +use mintlayer_messages::{ + AccountCommand, AccountSpending, AdditionalOrderInfo, AdditionalUtxoInfo, Amount, + OrderAccountCommand, OutputValue, TokenId, TxInputWithAdditionalInfo, TxOutput, +}; + +use crate::StatusWord; + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum CoinOrTokenId { + Coin, + TokenId(TokenId), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TxType { + Transfer, + Burn, + Htlc, + CreateDelegation, + DelegateStaking, + DelegationWithdrawal, + CreateStakePool, + DecommissionStakePool, + CreateNft, + CreateToken, + MintTokens, + UnmintTokens, + FreezeToken, + UnfreezeToken, + LockTokenSupply, + ChangeTokenAuthority, + ChangeTokenMetadataUri, + FillOrder, + FreezeOrder, + CreateOrder, + ConcludeOrder, + DataDeposit, + ComplexTransaction, +} + +pub enum InputCommand { + AccountSpending(AccountSpending), + AccountCommand(AccountCommand), + OrderCommand(OrderAccountCommand, AdditionalOrderInfo), +} + +pub struct TxSummaryCollector { + tx_type: Option, + input_command: Option, + total_inputs: BTreeMap, + total_outputs: BTreeMap, +} + +impl TxSummaryCollector { + pub fn new() -> Self { + Self { + tx_type: None, + input_command: None, + total_inputs: BTreeMap::new(), + total_outputs: BTreeMap::new(), + } + } + + // TODO: + // 1) currently consensus only forbids multiple account commands (AccountCommand or OrderAccountCommand) per tx, + // but the number of account spendings is unlimited and they can co-exist with an account command; + // 2) probably the app shouldn't try being smart and assume any number of account commands is possible, asking + // the user to approve them as they arrive, same as it's done for outputs; + // 3) if the app does try to be smart, then it should fail when multiple commands are encountered, instead of + // silently overwriting `input_command`. + // See https://github.com/mintlayer/mintlayer-ledger-app/issues/14. + // Also see the TODO near `TxProcessingContext::show_spinner`. + pub fn input_command(&self) -> Option<&InputCommand> { + self.input_command.as_ref() + } + + pub fn tx_type(&self) -> Option { + self.tx_type + } + + pub fn total_inputs(&self) -> &BTreeMap { + &self.total_inputs + } + + pub fn total_outputs(&self) -> &BTreeMap { + &self.total_outputs + } + + pub fn fees_iter( + &self, + ) -> impl Iterator> + '_ { + // TODO: if an asset is only present in total_outputs, this will not fail with TxFeeUnderflow, + // but it should. + // See https://github.com/mintlayer/mintlayer-ledger-app/issues/15. + self.total_inputs() + .iter() + .map(move |(coin_or_token, amount)| { + let out = *self + .total_outputs() + .get(coin_or_token) + .unwrap_or(&Amount::ZERO); + + let fee = amount + .into_atoms() + .checked_sub(out.into_atoms()) + .ok_or(StatusWord::TxFeeUnderflow)?; + + Ok((coin_or_token, fee)) + }) + } + + pub fn process_output(&mut self, out: &TxOutput) -> Result<(), StatusWord> { + match &out { + TxOutput::Transfer(value, _) | TxOutput::LockThenTransfer(value, _, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::Transfer); + + let (coin_or_token_id, amount) = into_coin_or_token_id_and_amount(value)?; + self.increase_output_totals(coin_or_token_id, amount)?; + } + TxOutput::Burn(value) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::Burn); + + let (coin_or_token_id, amount) = into_coin_or_token_id_and_amount(value)?; + self.increase_output_totals(coin_or_token_id, amount)?; + } + TxOutput::Htlc(value, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::Htlc); + + let (coin_or_token_id, amount) = into_coin_or_token_id_and_amount(value)?; + self.increase_output_totals(coin_or_token_id, amount)?; + } + TxOutput::CreateStakePool(_, data) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::CreateStakePool); + + self.increase_output_totals(CoinOrTokenId::Coin, data.pledge)?; + } + TxOutput::ProduceBlockFromStake(_, _) => {} + TxOutput::DelegateStaking(amount, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::DelegateStaking); + self.increase_output_totals(CoinOrTokenId::Coin, *amount)?; + } + TxOutput::CreateDelegationId(_, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::CreateDelegation); + } + TxOutput::IssueFungibleToken(_) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::CreateToken); + } + TxOutput::DataDeposit(_) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::DataDeposit); + } + TxOutput::IssueNft(_, _, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::CreateNft); + } + TxOutput::CreateOrder(order_data) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::CreateOrder); + let (coin_or_token_id, amount) = + into_coin_or_token_id_and_amount(&order_data.give)?; + self.increase_output_totals(coin_or_token_id, amount)?; + } + } + + Ok(()) + } + + pub fn process_input(&mut self, inp: &TxInputWithAdditionalInfo) -> Result<(), StatusWord> { + match inp { + TxInputWithAdditionalInfo::Utxo(_, info) => match info { + AdditionalUtxoInfo::UtxoWithPoolData { + utxo: _, + staker_balance, + } => { + self.tx_type = merge_tx_type(self.tx_type, TxType::DecommissionStakePool); + self.increase_input_totals(CoinOrTokenId::Coin, *staker_balance)?; + } + AdditionalUtxoInfo::Utxo(utxo) => { + match &utxo { + TxOutput::Transfer(value, _) + | TxOutput::LockThenTransfer(value, _, _) + | TxOutput::Htlc(value, _) => { + let (coin_or_token_id, amount) = + into_coin_or_token_id_and_amount(value)?; + self.increase_input_totals(coin_or_token_id, amount)?; + } + TxOutput::Burn(_) + | TxOutput::ProduceBlockFromStake(_, _) + | TxOutput::CreateDelegationId(_, _) + | TxOutput::IssueFungibleToken(_) + | TxOutput::DataDeposit(_) + | TxOutput::DelegateStaking(_, _) + | TxOutput::CreateOrder(_) => return Err(StatusWord::TxInvalidInputUtxo), + TxOutput::CreateStakePool(_, data) => { + self.increase_input_totals(CoinOrTokenId::Coin, data.pledge)?; + } + TxOutput::IssueNft(nft_id, _, _) => { + self.increase_input_totals( + CoinOrTokenId::TokenId(*nft_id), + Amount::from_atoms(1), + )?; + } + }; + } + }, + TxInputWithAdditionalInfo::Account(acc) => { + self.input_command = Some(InputCommand::AccountSpending(acc.spending.clone())); + match acc.spending { + AccountSpending::DelegationBalance(_, amount) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::DelegationWithdrawal); + self.increase_input_totals(CoinOrTokenId::Coin, amount)?; + } + } + } + TxInputWithAdditionalInfo::AccountCommand(_, cmd) => { + self.input_command = Some(InputCommand::AccountCommand(cmd.clone())); + match cmd { + AccountCommand::MintTokens(token_id, amount) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::MintTokens); + self.increase_input_totals(CoinOrTokenId::TokenId(*token_id), *amount)?; + } + AccountCommand::ConcludeOrder(_) | AccountCommand::FillOrder(_, _, _) => { + return Err(StatusWord::OrdersV0NotSupported); + } + AccountCommand::UnmintTokens(_) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::UnmintTokens); + } + AccountCommand::LockTokenSupply(_) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::LockTokenSupply); + } + AccountCommand::FreezeToken(_, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::FreezeToken); + } + AccountCommand::UnfreezeToken(_) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::UnfreezeToken); + } + AccountCommand::ChangeTokenAuthority(_, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::ChangeTokenAuthority); + } + AccountCommand::ChangeTokenMetadataUri(_, _) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::ChangeTokenMetadataUri); + } + } + } + TxInputWithAdditionalInfo::OrderAccountCommand(cmd, additional_info) => { + self.input_command = Some(InputCommand::OrderCommand( + cmd.clone(), + additional_info.clone(), + )); + match cmd { + OrderAccountCommand::FillOrder(_, fill_amount) => { + let (fill_coin_or_token_id, asked_amount) = + into_coin_or_token_id_and_amount(&additional_info.initially_asked)?; + let (given_coin_or_token_id, given_amount) = + into_coin_or_token_id_and_amount(&additional_info.initially_given)?; + + self.increase_output_totals(fill_coin_or_token_id, *fill_amount)?; + + let atoms = given_amount + .into_atoms() + .checked_mul(fill_amount.into_atoms()) + .ok_or(StatusWord::TxNumericOperationFail)? + .checked_div(asked_amount.into_atoms()) + .ok_or(StatusWord::TxNumericOperationFail)?; + let amount = Amount::from_atoms(atoms); + self.increase_input_totals(given_coin_or_token_id, amount)?; + + self.tx_type = merge_tx_type(self.tx_type, TxType::FillOrder); + } + OrderAccountCommand::ConcludeOrder(_) => { + let (coin_or_token_id, _) = + into_coin_or_token_id_and_amount(&additional_info.initially_asked)?; + self.increase_input_totals(coin_or_token_id, additional_info.ask_balance)?; + + let (coin_or_token_id, _) = + into_coin_or_token_id_and_amount(&additional_info.initially_given)?; + self.increase_input_totals(coin_or_token_id, additional_info.give_balance)?; + + self.tx_type = merge_tx_type(self.tx_type, TxType::ConcludeOrder); + } + OrderAccountCommand::FreezeOrder(_) => { + self.tx_type = merge_tx_type(self.tx_type, TxType::FreezeOrder); + } + } + } + }; + + Ok(()) + } + + fn increase_input_totals( + &mut self, + key: CoinOrTokenId, + amount: Amount, + ) -> Result<(), StatusWord> { + let total = self + .total_inputs + .entry(key) + .or_insert(Amount::from_atoms(0)); + let new_total = total + .into_atoms() + .checked_add(amount.into_atoms()) + .ok_or(StatusWord::TxNumericOperationFail)?; + *total = Amount::from_atoms(new_total); + Ok(()) + } + + fn increase_output_totals( + &mut self, + key: CoinOrTokenId, + amount: Amount, + ) -> Result<(), StatusWord> { + let total = self + .total_outputs + .entry(key) + .or_insert(Amount::from_atoms(0)); + let new_total = total + .into_atoms() + .checked_add(amount.into_atoms()) + .ok_or(StatusWord::TxNumericOperationFail)?; + *total = Amount::from_atoms(new_total); + Ok(()) + } +} + +// TODO: this logic makes the tx type depend on the outputs order: +// if the outputs are Burn and Transfer, the tx type will be Burn, but if it's Transfer, Burn, +// then the tx type will be ComplexTransaction. +// See https://github.com/mintlayer/mintlayer-ledger-app/issues/21. +fn merge_tx_type(tx_type: Option, new_type: TxType) -> Option { + match tx_type { + None => Some(new_type), + // Transfers are a lower priority (as they can be change outputs) so keep the previous type + Some(_) if new_type == TxType::Transfer => tx_type, + Some(_) => Some(TxType::ComplexTransaction), + } +} + +fn into_coin_or_token_id_and_amount( + value: &OutputValue, +) -> Result<(CoinOrTokenId, Amount), StatusWord> { + match value { + OutputValue::Coin(amount) => Ok((CoinOrTokenId::Coin, *amount)), + OutputValue::TokenV1(token_id, amount) => Ok((CoinOrTokenId::TokenId(*token_id), *amount)), + } +} + +#[cfg(test)] +mod tests { + use mintlayer_messages::{AdditionalOrderInfo, AdditionalUtxoInfo, TxInputWithAdditionalInfo}; + use test_utils::prelude::*; + + use crate::{StatusWord, mlcp}; + + use super::*; + + fn make_utxo_outpoint() -> mlcp::UtxoOutPoint { + mlcp::UtxoOutPoint::new( + mlcp::OutPointSourceId::Transaction(mlcp::Id::new(mlcp::H256::zero())), + 0, + ) + } + + // TODO: these tests can be improved: + // 1) Each test should better check everything (total inputs, total outputs, fees, tx type etc), + // even if it's only dealing with one aspect of the summary collector (e.g. tx outputs). + // 2) More tests for fee calculation would be nice: + // a) non-trivial successful case; + // b) cases dealing with more than once currency (both successful and not), in particular + // the case where one currency is only present in the total outputs but not total inputs. + // 3) Maybe something else. + // See https://github.com/mintlayer/mintlayer-ledger-app/issues/15. + + #[test_item] + fn test_new_and_getters() { + let collector = TxSummaryCollector::new(); + assert!(collector.tx_type().is_none()); + assert!(collector.input_command().is_none()); + assert!(collector.total_inputs().is_empty()); + assert!(collector.total_outputs().is_empty()); + } + + #[test_item] + fn test_process_output_transfer() { + let mut collector = TxSummaryCollector::new(); + + // Transfer Coin + let coin_amount = mlcp::Amount::from_atoms(100); + let out_coin = mlcp::TxOutput::Transfer( + mlcp::OutputValue::Coin(coin_amount), + mlcp::Destination::AnyoneCanSpend, + ); + collector.process_output(&out_coin).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::Transfer)); + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&coin_amount) + ); + + // Transfer Token + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let token_amount = mlcp::Amount::from_atoms(200); + let out_token = mlcp::TxOutput::Transfer( + mlcp::OutputValue::TokenV1(token_id, token_amount), + mlcp::Destination::AnyoneCanSpend, + ); + collector.process_output(&out_token).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::Transfer)); + assert_eq!( + collector + .total_outputs() + .get(&CoinOrTokenId::TokenId(token_id)), + Some(&token_amount) + ); + } + + #[test_item] + fn test_process_output_lock_then_transfer() { + let mut collector = TxSummaryCollector::new(); + let amount = mlcp::Amount::from_atoms(150); + let out = mlcp::TxOutput::LockThenTransfer( + mlcp::OutputValue::Coin(amount), + mlcp::Destination::AnyoneCanSpend, + mlcp::OutputTimeLock::ForBlockCount(mlcp::BlocksCount(10)), + ); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::Transfer)); + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&amount) + ); + } + + #[test_item] + fn test_process_output_burn() { + let mut collector = TxSummaryCollector::new(); + let burn_amount = mlcp::Amount::from_atoms(50); + let out = mlcp::TxOutput::Burn(mlcp::OutputValue::Coin(burn_amount)); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::Burn)); + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&burn_amount) + ); + } + + #[test_item] + fn test_process_output_htlc() { + let mut collector = TxSummaryCollector::new(); + let htlc = mlcp::HashedTimelockContract { + secret_hash: [0; 20].into(), + spend_key: mlcp::Destination::AnyoneCanSpend, + refund_timelock: mlcp::OutputTimeLock::ForBlockCount(mlcp::BlocksCount(10)), + refund_key: mlcp::Destination::AnyoneCanSpend, + }; + let htlc_amount = mlcp::Amount::from_atoms(300); + let out = mlcp::TxOutput::Htlc(mlcp::OutputValue::Coin(htlc_amount), htlc); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::Htlc)); + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&htlc_amount) + ); + } + + #[test_item] + fn test_process_output_create_stake_pool() { + let mut collector = TxSummaryCollector::new(); + let pledge_amount = mlcp::Amount::from_atoms(40000); + let data = mlcp::StakePoolData { + pledge: pledge_amount, + staker: mlcp::Destination::AnyoneCanSpend, + vrf_public_key: mlcp::VrfPublicKey::Schnorrkel(mlcp::SchnorrkelPublicKey([0; 32])), + decommission_key: mlcp::Destination::AnyoneCanSpend, + margin_ratio_per_thousand: mlcp::PerThousand(10), + cost_per_block: mlcp::Amount::from_atoms(0), + }; + let out = mlcp::TxOutput::CreateStakePool(mlcp::Id::new(mlcp::H256::zero()), data); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::CreateStakePool)); + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&pledge_amount) + ); + } + + #[test_item] + fn test_process_output_produce_block_from_stake() { + let mut collector = TxSummaryCollector::new(); + let out = mlcp::TxOutput::ProduceBlockFromStake( + mlcp::Destination::AnyoneCanSpend, + mlcp::Id::new(mlcp::H256::zero()), + ); + collector.process_output(&out).unwrap(); + // ProduceBlockFromStake is a no-op, tx_type should remain None + assert!(collector.tx_type().is_none()); + assert!(collector.total_outputs().is_empty()); + } + + #[test_item] + fn test_process_output_delegate_staking() { + let mut collector = TxSummaryCollector::new(); + let delegate_amount = mlcp::Amount::from_atoms(500); + let out = + mlcp::TxOutput::DelegateStaking(delegate_amount, mlcp::Id::new(mlcp::H256::zero())); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::DelegateStaking)); + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&delegate_amount) + ); + } + + #[test_item] + fn test_process_output_create_delegation_id() { + let mut collector = TxSummaryCollector::new(); + let out = mlcp::TxOutput::CreateDelegationId( + mlcp::Destination::AnyoneCanSpend, + mlcp::Id::new(mlcp::H256::zero()), + ); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::CreateDelegation)); + } + + #[test_item] + fn test_process_output_issue_fungible_token() { + let mut collector = TxSummaryCollector::new(); + let token_issuance = mlcp::TokenIssuance::V1(mlcp::TokenIssuanceV1 { + token_ticker: alloc::vec::Vec::new(), + number_of_decimals: 8, + metadata_uri: alloc::vec::Vec::new(), + total_supply: mlcp::TokenTotalSupply::Unlimited, + authority: mlcp::Destination::AnyoneCanSpend, + is_freezable: mlcp::IsTokenFreezable::No, + }); + let out = mlcp::TxOutput::IssueFungibleToken(token_issuance); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::CreateToken)); + } + + #[test_item] + fn test_process_output_data_deposit() { + let mut collector = TxSummaryCollector::new(); + let out = mlcp::TxOutput::DataDeposit(alloc::vec::Vec::new()); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::DataDeposit)); + } + + #[test_item] + fn test_process_output_issue_nft() { + let mut collector = TxSummaryCollector::new(); + let nft_issuance = mlcp::NftIssuance::V0(mlcp::NftIssuanceV0 { + creator: None, + name: alloc::vec::Vec::new(), + description: alloc::vec::Vec::new(), + ticker: alloc::vec::Vec::new(), + icon_uri: alloc::vec::Vec::new(), + additional_metadata_uri: alloc::vec::Vec::new(), + media_uri: alloc::vec::Vec::new(), + media_hash: alloc::vec::Vec::new(), + }); + let out = mlcp::TxOutput::IssueNft( + mlcp::Id::new(mlcp::H256::zero()), + nft_issuance, + mlcp::Destination::AnyoneCanSpend, + ); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::CreateNft)); + } + + #[test_item] + fn test_process_output_create_order() { + let mut collector = TxSummaryCollector::new(); + let ask_amount = mlcp::Amount::from_atoms(100); + let give_amount = mlcp::Amount::from_atoms(50); + let order_data = mlcp::OrderData { + conclude_key: mlcp::Destination::AnyoneCanSpend, + ask: mlcp::OutputValue::Coin(ask_amount), + give: mlcp::OutputValue::Coin(give_amount), + }; + let out = mlcp::TxOutput::CreateOrder(order_data); + collector.process_output(&out).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::CreateOrder)); + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&give_amount) + ); + } + + #[test_item] + fn test_process_input_utxo_transfer() { + let mut collector = TxSummaryCollector::new(); + + let transfer_amount = mlcp::Amount::from_atoms(250); + let inp = TxInputWithAdditionalInfo::Utxo( + make_utxo_outpoint(), + AdditionalUtxoInfo::Utxo(mlcp::TxOutput::Transfer( + mlcp::OutputValue::Coin(transfer_amount), + mlcp::Destination::AnyoneCanSpend, + )), + ); + collector.process_input(&inp).unwrap(); + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&transfer_amount) + ); + } + + #[test_item] + fn test_process_input_utxo_lock_then_transfer() { + let mut collector = TxSummaryCollector::new(); + + let lock_amount = mlcp::Amount::from_atoms(120); + let inp = TxInputWithAdditionalInfo::Utxo( + make_utxo_outpoint(), + AdditionalUtxoInfo::Utxo(mlcp::TxOutput::LockThenTransfer( + mlcp::OutputValue::Coin(lock_amount), + mlcp::Destination::AnyoneCanSpend, + mlcp::OutputTimeLock::ForBlockCount(mlcp::BlocksCount(10)), + )), + ); + collector.process_input(&inp).unwrap(); + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&lock_amount) + ); + } + + #[test_item] + fn test_process_input_utxo_htlc() { + let mut collector = TxSummaryCollector::new(); + let htlc = mlcp::HashedTimelockContract { + secret_hash: [0; 20].into(), + spend_key: mlcp::Destination::AnyoneCanSpend, + refund_timelock: mlcp::OutputTimeLock::ForBlockCount(mlcp::BlocksCount(10)), + refund_key: mlcp::Destination::AnyoneCanSpend, + }; + + let htlc_amount = mlcp::Amount::from_atoms(80); + let inp = TxInputWithAdditionalInfo::Utxo( + make_utxo_outpoint(), + AdditionalUtxoInfo::Utxo(mlcp::TxOutput::Htlc( + mlcp::OutputValue::Coin(htlc_amount), + htlc, + )), + ); + collector.process_input(&inp).unwrap(); + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&htlc_amount) + ); + } + + #[test_item] + fn test_process_input_utxo_create_stake_pool() { + let mut collector = TxSummaryCollector::new(); + let pledge_amount = mlcp::Amount::from_atoms(40000); + let data = mlcp::StakePoolData { + pledge: pledge_amount, + staker: mlcp::Destination::AnyoneCanSpend, + vrf_public_key: mlcp::VrfPublicKey::Schnorrkel(mlcp::SchnorrkelPublicKey([0; 32])), + decommission_key: mlcp::Destination::AnyoneCanSpend, + margin_ratio_per_thousand: mlcp::PerThousand(10), + cost_per_block: mlcp::Amount::from_atoms(0), + }; + + let inp = TxInputWithAdditionalInfo::Utxo( + make_utxo_outpoint(), + AdditionalUtxoInfo::Utxo(mlcp::TxOutput::CreateStakePool( + mlcp::Id::new(mlcp::H256::zero()), + data, + )), + ); + collector.process_input(&inp).unwrap(); + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&pledge_amount) + ); + } + + #[test_item] + fn test_process_input_utxo_issue_nft() { + let mut collector = TxSummaryCollector::new(); + let nft_issuance = mlcp::NftIssuance::V0(mlcp::NftIssuanceV0 { + creator: None, + name: alloc::vec::Vec::new(), + description: alloc::vec::Vec::new(), + ticker: alloc::vec::Vec::new(), + icon_uri: alloc::vec::Vec::new(), + additional_metadata_uri: alloc::vec::Vec::new(), + media_uri: alloc::vec::Vec::new(), + media_hash: alloc::vec::Vec::new(), + }); + let nft_id = mlcp::Id::new(mlcp::H256::zero()); + + let inp = TxInputWithAdditionalInfo::Utxo( + make_utxo_outpoint(), + AdditionalUtxoInfo::Utxo(mlcp::TxOutput::IssueNft( + nft_id, + nft_issuance, + mlcp::Destination::AnyoneCanSpend, + )), + ); + collector.process_input(&inp).unwrap(); + assert_eq!( + collector + .total_inputs() + .get(&CoinOrTokenId::TokenId(nft_id)), + Some(&mlcp::Amount::from_atoms(1)) + ); + } + + #[test_item] + fn test_process_input_utxo_with_pool_data() { + let mut collector = TxSummaryCollector::new(); + let staker_balance_amount = mlcp::Amount::from_atoms(50000); + let inp = TxInputWithAdditionalInfo::Utxo( + make_utxo_outpoint(), + AdditionalUtxoInfo::UtxoWithPoolData { + utxo: mlcp::TxOutput::ProduceBlockFromStake( + mlcp::Destination::AnyoneCanSpend, + mlcp::Id::new(mlcp::H256::zero()), + ), + staker_balance: staker_balance_amount, + }, + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::DecommissionStakePool)); + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&staker_balance_amount) + ); + } + + #[test_item] + fn test_process_input_account() { + let mut collector = TxSummaryCollector::new(); + let delegation_balance = mlcp::Amount::from_atoms(700); + let acc = mlcp::AccountOutPoint { + nonce: mlcp::AccountNonce(0), + spending: mlcp::AccountSpending::DelegationBalance( + mlcp::Id::new(mlcp::H256::zero()), + delegation_balance, + ), + }; + let inp = TxInputWithAdditionalInfo::Account(acc); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::DelegationWithdrawal)); + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&delegation_balance) + ); + assert!(matches!( + collector.input_command(), + Some(InputCommand::AccountSpending( + mlcp::AccountSpending::DelegationBalance(_, _) + )) + )); + } + + #[test_item] + fn test_process_input_account_command_mint() { + let mut collector = TxSummaryCollector::new(); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let mint_amount = mlcp::Amount::from_atoms(1000); + let inp = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(1), + mlcp::AccountCommand::MintTokens(token_id, mint_amount), + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::MintTokens)); + assert_eq!( + collector + .total_inputs() + .get(&CoinOrTokenId::TokenId(token_id)), + Some(&mint_amount) + ); + assert!(matches!( + collector.input_command(), + Some(InputCommand::AccountCommand( + mlcp::AccountCommand::MintTokens(_, _) + )) + )); + } + + #[test_item] + fn test_process_input_account_command_unmint() { + let mut collector = TxSummaryCollector::new(); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let inp = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(2), + mlcp::AccountCommand::UnmintTokens(token_id), + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::UnmintTokens)); + } + + #[test_item] + fn test_process_input_account_command_lock_token_supply() { + let mut collector = TxSummaryCollector::new(); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let inp = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(3), + mlcp::AccountCommand::LockTokenSupply(token_id), + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::LockTokenSupply)); + } + + #[test_item] + fn test_process_input_account_command_freeze_token() { + let mut collector = TxSummaryCollector::new(); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let inp = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(4), + mlcp::AccountCommand::FreezeToken(token_id, mlcp::IsTokenUnfreezable::Yes), + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::FreezeToken)); + } + + #[test_item] + fn test_process_input_account_command_unfreeze_token() { + let mut collector = TxSummaryCollector::new(); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let inp = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(5), + mlcp::AccountCommand::UnfreezeToken(token_id), + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::UnfreezeToken)); + } + + #[test_item] + fn test_process_input_account_command_change_authority() { + let mut collector = TxSummaryCollector::new(); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let inp = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(6), + mlcp::AccountCommand::ChangeTokenAuthority(token_id, mlcp::Destination::AnyoneCanSpend), + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::ChangeTokenAuthority)); + } + + #[test_item] + fn test_process_input_account_command_change_metadata_uri() { + let mut collector = TxSummaryCollector::new(); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let inp = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(7), + mlcp::AccountCommand::ChangeTokenMetadataUri(token_id, alloc::vec::Vec::new()), + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::ChangeTokenMetadataUri)); + } + + #[test_item] + fn test_process_input_order_command_fill() { + let mut collector = TxSummaryCollector::new(); + let order_id = mlcp::Id::new(mlcp::H256::zero()); + let fill_amount = mlcp::Amount::from_atoms(10); + let initially_asked = mlcp::Amount::from_atoms(100); + let initially_given = mlcp::Amount::from_atoms(200); + + let additional_info = AdditionalOrderInfo { + initially_asked: mlcp::OutputValue::Coin(initially_asked), + initially_given: mlcp::OutputValue::Coin(initially_given), + ask_balance: mlcp::Amount::from_atoms(0), + give_balance: mlcp::Amount::from_atoms(0), + }; + let inp = TxInputWithAdditionalInfo::OrderAccountCommand( + mlcp::OrderAccountCommand::FillOrder(order_id, fill_amount), + additional_info, + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::FillOrder)); + // Fill order output totals increases by fill_amount (10 coins) + assert_eq!( + collector.total_outputs().get(&CoinOrTokenId::Coin), + Some(&fill_amount) + ); + // Fill order input totals increases by (given_amount * fill_amount / asked_amount) + // 200 * 10 / 100 = 20 + let expected_input_amount = Amount::from_atoms( + (initially_given.into_atoms() * fill_amount.into_atoms()) + / initially_asked.into_atoms(), + ); + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&expected_input_amount) + ); + assert!(matches!( + collector.input_command(), + Some(InputCommand::OrderCommand( + mlcp::OrderAccountCommand::FillOrder(_, _), + AdditionalOrderInfo { .. } + )) + )); + } + + #[test_item] + fn test_process_input_order_command_conclude() { + let mut collector = TxSummaryCollector::new(); + let order_id = mlcp::Id::new(mlcp::H256::zero()); + let ask_balance = mlcp::Amount::from_atoms(30); + let give_balance = mlcp::Amount::from_atoms(60); + let token_id = mlcp::Id::new(mlcp::H256::zero()); + let additional_info = AdditionalOrderInfo { + initially_asked: mlcp::OutputValue::Coin(mlcp::Amount::from_atoms(100)), + initially_given: mlcp::OutputValue::TokenV1(token_id, mlcp::Amount::from_atoms(200)), + ask_balance, + give_balance, + }; + let inp = TxInputWithAdditionalInfo::OrderAccountCommand( + mlcp::OrderAccountCommand::ConcludeOrder(order_id), + additional_info, + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::ConcludeOrder)); + // Conclude order increases inputs by ask_balance and give_balance + assert_eq!( + collector.total_inputs().get(&CoinOrTokenId::Coin), + Some(&ask_balance) + ); + assert_eq!( + collector + .total_inputs() + .get(&CoinOrTokenId::TokenId(token_id)), + Some(&give_balance) + ); + } + + #[test_item] + fn test_process_input_order_command_freeze() { + let mut collector = TxSummaryCollector::new(); + let order_id = mlcp::Id::new(mlcp::H256::zero()); + let additional_info = AdditionalOrderInfo { + initially_asked: mlcp::OutputValue::Coin(mlcp::Amount::from_atoms(100)), + initially_given: mlcp::OutputValue::Coin(mlcp::Amount::from_atoms(200)), + ask_balance: mlcp::Amount::from_atoms(0), + give_balance: mlcp::Amount::from_atoms(0), + }; + let inp = TxInputWithAdditionalInfo::OrderAccountCommand( + mlcp::OrderAccountCommand::FreezeOrder(order_id), + additional_info, + ); + collector.process_input(&inp).unwrap(); + assert_eq!(collector.tx_type(), Some(TxType::FreezeOrder)); + } + + #[test_item] + fn test_process_input_errors() { + let mut collector = TxSummaryCollector::new(); + + // 1. Burn output as input UTXO is invalid + let inp_burn = TxInputWithAdditionalInfo::Utxo( + make_utxo_outpoint(), + AdditionalUtxoInfo::Utxo(mlcp::TxOutput::Burn(mlcp::OutputValue::Coin( + mlcp::Amount::from_atoms(100), + ))), + ); + assert_eq!( + collector.process_input(&inp_burn).unwrap_err(), + StatusWord::TxInvalidInputUtxo + ); + + // 2. OrdersV0NotSupported in AccountCommand + let inp_conclude_v0 = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(8), + mlcp::AccountCommand::ConcludeOrder(mlcp::Id::new(mlcp::H256::zero())), + ); + assert_eq!( + collector.process_input(&inp_conclude_v0).unwrap_err(), + StatusWord::OrdersV0NotSupported + ); + + let inp_fill_v0 = TxInputWithAdditionalInfo::AccountCommand( + mlcp::AccountNonce(9), + mlcp::AccountCommand::FillOrder( + mlcp::Id::new(mlcp::H256::zero()), + mlcp::Amount::from_atoms(100), + mlcp::Destination::AnyoneCanSpend, + ), + ); + assert_eq!( + collector.process_input(&inp_fill_v0).unwrap_err(), + StatusWord::OrdersV0NotSupported + ); + } + + #[test_item] + fn test_tx_type_merging() { + assert_eq!( + merge_tx_type(None, TxType::Transfer), + Some(TxType::Transfer) + ); + assert_eq!( + merge_tx_type(Some(TxType::Transfer), TxType::Transfer), + Some(TxType::Transfer) + ); + assert_eq!( + merge_tx_type(Some(TxType::Burn), TxType::Transfer), + Some(TxType::Burn) + ); + assert_eq!( + merge_tx_type(Some(TxType::Burn), TxType::Htlc), + Some(TxType::ComplexTransaction) + ); + } + + #[test_item] + fn test_fees_calculation_overflow_and_underflow() { + let mut collector = TxSummaryCollector::new(); + + // Underflow: out (120) > inp (100) + collector + .total_inputs + .insert(CoinOrTokenId::Coin, mlcp::Amount::from_atoms(100)); + collector + .total_outputs + .insert(CoinOrTokenId::Coin, mlcp::Amount::from_atoms(120)); + let fees_res = collector.fees_iter().next().unwrap(); + assert_eq!(fees_res.unwrap_err(), StatusWord::TxFeeUnderflow); + + // Numeric overflow in increase_input_totals + let mut collector = TxSummaryCollector::new(); + collector + .increase_input_totals(CoinOrTokenId::Coin, mlcp::Amount::from_atoms(u128::MAX)) + .unwrap(); + assert_eq!( + collector + .increase_input_totals(CoinOrTokenId::Coin, mlcp::Amount::from_atoms(1)) + .unwrap_err(), + StatusWord::TxNumericOperationFail + ); + + // Numeric overflow in increase_output_totals + let mut collector = TxSummaryCollector::new(); + collector + .increase_output_totals(CoinOrTokenId::Coin, mlcp::Amount::from_atoms(u128::MAX)) + .unwrap(); + assert_eq!( + collector + .increase_output_totals(CoinOrTokenId::Coin, mlcp::Amount::from_atoms(1)) + .unwrap_err(), + StatusWord::TxNumericOperationFail + ); + } +} diff --git a/crates/app-core/src/hasher.rs b/crates/app-core/src/hasher.rs new file mode 100644 index 0000000..85932b4 --- /dev/null +++ b/crates/app-core/src/hasher.rs @@ -0,0 +1,75 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use crate::StatusWord; + +use mintlayer_messages::{H256, parity_scale_codec}; + +use ledger_device_sdk::hash::{HashInit, blake2::Blake2b_512}; + +/// The hasher that produces Mintlayer-specific hashes. +/// +/// Note: we want to implement `parity_scale_codec::Output` for `Hasher`, which means that +/// its `update` method has to be infallible. But we don't want to `expect` on errors, so +/// on failure we just set `update_failed` to true, which will then cause `finalize` to fail. +pub struct Hasher { + hasher: Blake2b_512, + update_failed: bool, +} + +impl Hasher { + pub fn new() -> Self { + Self { + hasher: Blake2b_512::new(), + update_failed: false, + } + } + + pub fn update(&mut self, input: &[u8]) { + if self.update_failed { + return; + } + + if self.hasher.update(input).is_err() { + self.update_failed = true; + } + } + + pub fn finalize(mut self) -> Result { + if self.update_failed { + return Err(StatusWord::HashFail); + } + + let mut hash: [u8; 64] = [0u8; 64]; + self.hasher + .finalize(&mut hash) + .map_err(|_| StatusWord::HashFail)?; + Ok(H256::from_slice(&hash[..32])) + } + + pub fn hash(input: &[u8]) -> Result { + let mut hasher = Self::new(); + hasher.update(input); + hasher.finalize() + } +} + +impl parity_scale_codec::Output for Hasher { + fn write(&mut self, bytes: &[u8]) { + self.update(bytes) + } +} diff --git a/crates/app-core/src/lib.rs b/crates/app-core/src/lib.rs new file mode 100644 index 0000000..8b8f2cd --- /dev/null +++ b/crates/app-core/src/lib.rs @@ -0,0 +1,372 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +#![no_std] +// See the comment in `crates/test-utils/src/lib.rs` for the meaning of these attributes. +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(ledger_device_sdk::testing::sdk_test_runner)] +#![reexport_test_harness_main = "test_main"] + +#[cfg(test)] +test_utils::impl_panic_handler!(); +#[cfg(test)] +test_utils::impl_main!(); + +mod app_ui; +mod errors; +mod handlers; +mod hasher; +mod utils; + +// Required for using String, Vec, format!... +extern crate alloc; +use alloc::vec::Vec; + +use ledger_device_sdk::{ + io::{ApduHeader, Comm, Reply}, + nbgl::{NbglHomeAndSettings, NbglReviewStatus, NbglStreamingReview, StatusType, init_comm}, +}; + +use mintlayer_core_primitives as mlcp; + +use mintlayer_messages::{ + APDU_CLASS, GetPubKeyP1, Ins, MAX_APDU_DATA_LEN, PingP1, Response, SignMsgP1, SignTxP1, + StatusWord, decode_all, encode, +}; + +use self::{ + app_ui::menu::ui_menu_main, + errors::sdk_err_to_status, + handlers::{ + get_public_key::handle_get_public_key, + sign_message::{SignMessageContext, handle_sign_message, setup_sign_message}, + sign_tx::{TxProcessingContext, handle_sign_tx, setup_sign_tx}, + }, +}; + +/// 16 max APDUs (= 4080 bytes) was chosen because: +/// * it should be enough to hold our biggest TxOutput (IssueNft with max possible URIs and other +/// fields will be slightly bigger than 3Kb); +/// * a buffer bigger than this will likely lead to OOM and crash (reallocating a 4Kb Vec will +/// temporarily consume 8Kb of RAM, which is half of the entire available Rust heap, see HEAP_SIZE +/// in .cargo/config.toml). +pub const MAX_BUFFER_LEN: usize = 16 * MAX_APDU_DATA_LEN; + +/// Represents a fully assembled Low-Level Instruction. +/// Contains the aggregated data from one or more APDUs (if P2 indicated more data). +pub struct RawInstruction { + pub ins: u8, + pub p1: u8, + pub data: Vec, +} + +pub enum ReceiveInstructionResult { + ExpectingNextChunk, + Instruction(RawInstruction), +} + +/// State machine to handle APDU packet chaining (P2_MORE / P2_DONE). +pub struct ApduTransport { + buffer: Vec, + current_ins: Option, + current_p1: Option, +} + +impl Default for ApduTransport { + fn default() -> Self { + Self { + buffer: Vec::with_capacity(u8::MAX as usize), // Pre-alloc for at least one standard APDU + current_ins: None, + current_p1: None, + } + } +} + +impl ApduTransport { + /// Reads the next APDU from `comm`. + /// + /// - If `P2 == P2_MORE`, it accumulates the data and returns `Ok(None)`. + /// It also sends a `StatusWord::Ok` to the host to request the next chunk. + /// - If `P2 == P2_DONE`, it finishes accumulation and returns `Ok(Some(RawInstruction))`. + pub fn receive(&mut self, comm: &mut Comm) -> Result { + let header: ApduHeader = comm.next_command(); + let data = comm.get_data().map_err(sdk_err_to_status)?; + + // Validation: If we are in the middle of a stream, INS and P1 must match + if let (Some(curr_ins), Some(curr_p1)) = (self.current_ins, self.current_p1) { + if header.ins != curr_ins { + self.reset(); + return Err(StatusWord::WrongInstruction); + } + if header.p1 != curr_p1 { + self.reset(); + return Err(StatusWord::WrongP1P2); + } + } else { + // New command sequence starting + self.current_ins = Some(header.ins); + self.current_p1 = Some(header.p1); + } + + if self.buffer.len() + data.len() > MAX_BUFFER_LEN { + self.reset(); + return Err(StatusWord::MaxBufferLenExceeded); + } + + self.buffer.extend_from_slice(data); + + match header.p2 { + mintlayer_messages::P2_MORE => Ok(ReceiveInstructionResult::ExpectingNextChunk), + mintlayer_messages::P2_DONE => { + // Construct the full instruction + let raw = RawInstruction { + ins: header.ins, + p1: header.p1, + data: core::mem::take(&mut self.buffer), + }; + self.reset(); + Ok(ReceiveInstructionResult::Instruction(raw)) + } + _ => { + self.reset(); + Err(StatusWord::WrongP1P2) + } + } + } + + fn reset(&mut self) { + self.buffer.clear(); + self.current_ins = None; + self.current_p1 = None; + } +} + +pub enum Command { + GetPubKey { p1: GetPubKeyP1, data: Vec }, + SignTx { p1: SignTxP1, data: Vec }, + SignMessage { p1: SignMsgP1, data: Vec }, + Ping, +} + +impl TryFrom for Command { + type Error = StatusWord; + + fn try_from(raw: RawInstruction) -> Result { + match raw.ins { + Ins::GET_PUB_KEY => { + let p1: GetPubKeyP1 = raw.p1.try_into()?; + Ok(Command::GetPubKey { p1, data: raw.data }) + } + Ins::SIGN_TX => { + let p1: SignTxP1 = raw.p1.try_into()?; + Ok(Command::SignTx { p1, data: raw.data }) + } + Ins::SIGN_MSG => { + let p1: SignMsgP1 = raw.p1.try_into()?; + Ok(Command::SignMessage { p1, data: raw.data }) + } + Ins::PING => { + let _p1: PingP1 = raw.p1.try_into()?; + + // Ping doesn't have any associated data. + if !raw.data.is_empty() { + Err(StatusWord::InvalidData) + } else { + Ok(Command::Ping) + } + } + _ => Err(StatusWord::InsNotSupported), + } + } +} + +fn show_status_and_home_if_needed(cmd: &Command, ctx: &mut AppContext, status: StatusWord) { + let (show_status, status_type) = match (cmd, status) { + (Command::GetPubKey { p1, .. }, StatusWord::Deny | StatusWord::Ok) if p1.display() => { + (true, StatusType::Address) + } + (Command::SignTx { .. }, StatusWord::Deny | StatusWord::Ok) if ctx.finished() => { + (true, StatusType::Transaction) + } + (Command::SignMessage { .. }, StatusWord::Deny | StatusWord::Ok) if ctx.finished() => { + (true, StatusType::Message) + } + (Command::Ping, StatusWord::Ok) => { + ctx.home.show_and_return(); + return; + } + (_, _) => (false, StatusType::Transaction), // Default fallback + }; + + if show_status { + let success = status == StatusWord::Ok; + NbglReviewStatus::new() + .status_type(status_type) + .show(success); + + // call home.show_and_return() to show home and setting screen + ctx.home.show_and_return(); + } +} + +pub enum DataContext { + TxContext(TxProcessingContext, NbglStreamingReview), + SignMessageContext(SignMessageContext), +} + +struct AppContext { + pub data_context: Option, + pub home: NbglHomeAndSettings, +} + +impl AppContext { + fn new() -> Self { + Self { + data_context: None, + home: Default::default(), + } + } + + fn finished(&self) -> bool { + self.data_context.as_ref().is_some_and(|ctx| match ctx { + DataContext::SignMessageContext(ctx) => ctx.finished(), + DataContext::TxContext(ctx, _) => ctx.finished(), + }) + } +} + +pub fn mintlayer_main() { + let mut comm = Comm::new().set_expected_cla(APDU_CLASS); + + let mut app_ctx = AppContext::new(); + + // Initialize reference to Comm instance for NBGL API calls. + init_comm(&mut comm); + app_ctx.home = ui_menu_main(); + app_ctx.home.show_and_return(); + + let mut transport = ApduTransport::default(); + + loop { + let raw_instruction = match transport.receive(&mut comm) { + Ok(ReceiveInstructionResult::Instruction(raw)) => raw, + Ok(ReceiveInstructionResult::ExpectingNextChunk) => { + // Signal host that we received the chunk and are waiting for more + comm.append(&encode(Response::ExpectingNextChunk)); + comm.reply(Reply(StatusWord::Ok as u16)); + continue; // Waiting for more chunks, loop around + } + Err(sw) => { + comm.reply(Reply(sw as u16)); + continue; + } + }; + + let command = match Command::try_from(raw_instruction) { + Ok(cmd) => cmd, + Err(sw) => { + comm.reply(Reply(sw as u16)); + continue; + } + }; + + let status = match handle_command(&command, &mut app_ctx) { + Ok(response) => { + comm.append(&encode(response)); + comm.reply_ok(); + StatusWord::Ok + } + Err(sw) => { + comm.reply(Reply(sw as u16)); + sw + } + }; + + show_status_and_home_if_needed(&command, &mut app_ctx, status); + } +} + +// TODO: +// 1) On all errors, the context should probably be reset. +// 2) Note that the simple `ctx.data_context = None` doesn't reset the UI, while `show_status_and_home_if_needed` +// doesn't reset the UI on any error. +// See https://github.com/mintlayer/mintlayer-ledger-app/issues/12. +// Also see the TODO inside the function. +fn handle_command(cmd: &Command, ctx: &mut AppContext) -> Result { + match cmd { + Command::GetPubKey { p1, data } => { + // TODO: the context should be reset here, especially if `display` is true. + // Note that `show_status_and_home_if_needed` calls `ctx.home.show_and_return()` + // on Pings, so: + // a) the context should be reset on Command::Ping below as well, + // b) since a Ping resets the context, then GetPubKey should do it even if `display` + // is false; + // c) Command::SignMessage below should also reset the context when it's done. + // In any case, it's better to put UI update (at least on success) in the same place where + // the context is changed. + let req = decode_all(data).ok_or(StatusWord::DeserializeFail)?; + handle_get_public_key(req, p1.display()).map(Response::PublicKey) + } + Command::SignTx { p1, data } => match p1 { + SignTxP1::Start => { + let req = decode_all(data).ok_or(StatusWord::DeserializeFail)?; + ctx.data_context = Some(setup_sign_tx(req)?); + Ok(Response::TxSetup) + } + SignTxP1::Next => { + let (mut tx_ctx, mut review) = match ctx.data_context.take() { + Some(DataContext::TxContext(c, r)) => (c, r), + _ => return Err(StatusWord::WrongContext), + }; + + let req = decode_all(data).ok_or(StatusWord::DeserializeFail)?; + + // TODO: it makes sense to drop `data` right after decoding, to free the memory. + + tx_ctx.show_spinner(); + + match handle_sign_tx(req, tx_ctx, &mut review) { + Ok((response, new_ctx)) => { + ctx.data_context = Some(DataContext::TxContext(new_ctx, review)); + Ok(response) + } + Err(sw) => { + ctx.data_context = None; + Err(sw) + } + } + } + }, + Command::SignMessage { p1, data } => match p1 { + SignMsgP1::Start => { + let req = decode_all(data).ok_or(StatusWord::DeserializeFail)?; + ctx.data_context = Some(setup_sign_message(req)?); + Ok(Response::MessageSetup) + } + SignMsgP1::Next => { + let msg_ctx = match ctx.data_context.as_mut() { + Some(DataContext::SignMessageContext(ctx)) => ctx, + _ => return Err(StatusWord::WrongContext), + }; + handle_sign_message(data, msg_ctx).map(Response::MessageSignature) + } + }, + Command::Ping => Ok(Response::Pong), + } +} diff --git a/crates/app-core/src/utils.rs b/crates/app-core/src/utils.rs new file mode 100644 index 0000000..e9a4f3c --- /dev/null +++ b/crates/app-core/src/utils.rs @@ -0,0 +1,216 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use alloc::{borrow::Cow, format}; +use core::fmt; + +use mintlayer_messages::{Amount, OutputValue, StatusWord}; + +use crate::mlcp; + +const DERIV_PATH_IDX_BIP44: usize = 0; +const DERIV_PATH_IDX_COIN_TYPE: usize = 1; +const DERIV_PATH_IDX_ACCOUNT_IDX: usize = 2; +const DERIV_PATH_IDX_ADDR_PURPOSE: usize = 3; +const DERIV_PATH_IDX_ADDR_IDX: usize = 4; + +// Path should be at least [bip44, coin_type, account_index] +const DERIV_PATH_MIN_LEN: usize = 3; +// For tx signing the path should also contain the purpose and the index. +const DERIV_PATH_LEN_FOR_TX_SIGNING: usize = 5; + +const DERIV_PATH_BIP44_ITEM: u32 = 44 + (1 << 31); + +pub fn check_derivation_path(path: &[u32], coin_type: mlcp::CoinType) -> Result<(), StatusWord> { + if path.len() < DERIV_PATH_MIN_LEN { + return Err(StatusWord::InvalidPath); + } + + if path[DERIV_PATH_IDX_BIP44] != DERIV_PATH_BIP44_ITEM { + return Err(StatusWord::InvalidPath); + } + + if path[DERIV_PATH_IDX_COIN_TYPE] != coin_type.bip44_coin_type() { + return Err(StatusWord::InvalidPath); + } + + Ok(()) +} + +pub fn check_derivation_path_for_tx_signing( + path: &[u32], + coin_type: mlcp::CoinType, +) -> Result { + check_derivation_path(path, coin_type)?; + + if path.len() != DERIV_PATH_LEN_FOR_TX_SIGNING { + return Err(StatusWord::InvalidPath); + } + + Ok(CompressedDerivationPathForTxSigning { + account_index: path[DERIV_PATH_IDX_ACCOUNT_IDX], + addr_purpose: path[DERIV_PATH_IDX_ADDR_PURPOSE], + addr_index: path[DERIV_PATH_IDX_ADDR_IDX], + }) +} + +pub struct CompressedDerivationPathForTxSigning { + pub account_index: u32, + pub addr_purpose: u32, + pub addr_index: u32, +} + +impl CompressedDerivationPathForTxSigning { + pub fn to_full_path(&self, coin_type: mlcp::CoinType) -> [u32; DERIV_PATH_LEN_FOR_TX_SIGNING] { + [ + DERIV_PATH_BIP44_ITEM, + coin_type.bip44_coin_type(), + self.account_index, + self.addr_purpose, + self.addr_index, + ] + } +} + +/// Cut an array of Copy types to a smaller size. +/// +/// Fail at compile time if the destination size is bigger than the original. +pub fn cut_array( + orig: &[T; ORIG_SIZE], +) -> [T; DEST_SIZE] +where + T: Default + Copy, +{ + const { assert!(DEST_SIZE <= ORIG_SIZE) }; + + let mut result = [T::default(); DEST_SIZE]; + result.copy_from_slice(&orig[..DEST_SIZE]); + result +} + +/// If `bytes` consists of only "displayable" ASCII chars, return the ASCII string directly +/// in the borrowed form. +/// Otherwise return hex-encode bytes as a newly allocated string. +pub fn make_displayable_str(bytes: &[u8]) -> Cow<'_, str> { + if let Some(s) = as_displayable_chars(bytes) { + Cow::Borrowed(s) + } else { + Cow::Owned(format!("{:#x}", const_hex::display(bytes))) + } +} + +/// This is similar to `make_displayable_str`, but instead of returning a string it returns +/// an object that will produce the string during formatting. +/// +/// This should be preferred to `make_displayable_str` if the result has to be passed to +/// `format!` anyway, because it should result in fewer reallocations. +pub fn make_displayable<'a>(bytes: &'a [u8]) -> impl fmt::Display + 'a { + if let Some(s) = as_displayable_chars(bytes) { + DisplayableMessage::Str(s) + } else { + DisplayableMessage::Hex(bytes) + } +} + +enum DisplayableMessage<'a> { + Str(&'a str), + Hex(&'a [u8]), +} + +impl<'a> fmt::Display for DisplayableMessage<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Str(s) => f.write_str(s), + Self::Hex(bytes) => { + write!(f, "{:#x}", const_hex::display(bytes)) + } + } + } +} + +fn as_displayable_chars(bytes: &[u8]) -> Option<&'_ str> { + let as_str = core::str::from_utf8(bytes).ok()?; + as_str + .bytes() + .all(|b| (0x20..=0x7E).contains(&b)) + .then_some(as_str) +} + +pub fn output_value_with_amount(value: &OutputValue, amount: Amount) -> OutputValue { + match value { + OutputValue::Coin(_) => OutputValue::Coin(amount), + OutputValue::TokenV1(token_id, _) => OutputValue::TokenV1(*token_id, amount), + } +} + +#[cfg(test)] +mod tests { + use test_utils::prelude::*; + + use super::*; + + #[test_item] + fn test_make_displayable() { + // ASCII displayable string. + { + let data = "qwe123".as_bytes(); + let expected_res = "qwe123"; + + let res = make_displayable_str(data); + assert_eq!(res, expected_res); + + let res = format!("{}", make_displayable(data)); + assert_eq!(res, expected_res); + } + + // ASCII non-displayable string. + { + let data = "qwe\t123".as_bytes(); + let expected_res = "0x71776509313233"; + + let res = make_displayable_str(data); + assert_eq!(res, expected_res); + + let res = format!("{}", make_displayable(data)); + assert_eq!(res, expected_res); + } + + // Non-ASCII string. + { + let data = "今日は".as_bytes(); + let expected_res = "0xe4bb8ae697a5e381af"; + + let res = make_displayable_str(data); + assert_eq!(res, expected_res); + + let res = format!("{}", make_displayable(data)); + assert_eq!(res, expected_res); + } + + // Not a string. + { + let data = [0xAA, 0xBB, 0xCC]; + let expected_res = "0xaabbcc"; + + let res = make_displayable_str(&data); + assert_eq!(res, expected_res); + + let res = format!("{}", make_displayable(&data)); + assert_eq!(res, expected_res); + } + } +} diff --git a/crates/messages/Cargo.toml b/crates/messages/Cargo.toml new file mode 100644 index 0000000..438ef1e --- /dev/null +++ b/crates/messages/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mintlayer-messages" +version.workspace = true +edition = "2024" + +[dependencies] +derive_more = { workspace = true, default-features = false, features = ["display"]} +num_enum = { workspace = true, default-features = false } +num-traits = { workspace = true, default-features = false } +# Note: normally we would enable the "chain-error" feature of parity-scale-codec to make decode errors +# more informative. But in the Ledger app we never examine or even print those errors, so enabling this +# feature would only increase the size of the binary and make the app use more stack during decoding. +parity-scale-codec = { workspace = true, default-features = false, features = ["derive"] } + +mintlayer-core-primitives.workspace = true + +[dev-dependencies] +ledger_device_sdk.workspace = true + +test-utils.workspace = true diff --git a/crates/messages/src/lib.rs b/crates/messages/src/lib.rs new file mode 100644 index 0000000..5aa632c --- /dev/null +++ b/crates/messages/src/lib.rs @@ -0,0 +1,624 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +#![no_std] +// See the comment in `crates/test-utils/src/lib.rs` for the meaning of these attributes. +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(ledger_device_sdk::testing::sdk_test_runner)] +#![reexport_test_harness_main = "test_main"] + +#[cfg(test)] +test_utils::impl_panic_handler!(); +#[cfg(test)] +test_utils::impl_main!(); + +#[cfg(test)] +mod tests; + +// TODO: need tests that ensure encoding stability - encode a certain message or a message part and +// expect concrete bytes, decode it back, expect the same object. +// See https://github.com/mintlayer/mintlayer-ledger-app/issues/16. + +// TODO: types from mintlayer core primitives should probably not be used as part of the protocol +// (but note that this will increase the size of the binary slightly - a test attempt at using distinct +// types increased the binary from ~100Kb to ~106Kb). +// Alternatively, we may want to keep some basic mlcp types in the protocol (the primitives that +// won't ever change). Though note that we'll have to have a separate type for Destination if we +// want to be able to detect change addresses, so all types that contain Destination will have to +// be distinct as well. +// See https://github.com/mintlayer/mintlayer-ledger-app/issues/18. +// Also see the TODO in `tests/application_client/__init__.py`. + +// Required for using String, Vec, format!... +extern crate alloc; + +use alloc::{boxed::Box, vec::Vec}; +use core::iter::ExactSizeIterator; + +use derive_more::Display; +use mintlayer_core_primitives as mlcp; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use parity_scale_codec::{Decode, DecodeAll}; + +pub use parity_scale_codec::{self, Encode}; + +pub use mlcp::{ + AccountCommand, AccountNonce, AccountOutPoint, AccountSpending, Amount, BlockHeight, + BlockTimestamp, BlocksCount, DelegationId, Destination, GenBlockId, H256, + HashedTimelockContract, IsTokenFreezable, IsTokenUnfreezable, NftIssuance, OrderAccountCommand, + OrderData, OrderId, OutPointSourceId, OutputTimeLock, OutputValue, PerThousand, PoolId, + PublicKey, PublicKeyHash, ScriptId, SecondsCount, Secp256k1PublicKey, SighashInputCommitment, + StakePoolData, TokenId, TokenIssuance, TokenTotalSupply, TransactionId, TxInput, TxOutput, + UtxoOutPoint, VrfPublicKey, +}; + +pub const APDU_CLASS: u8 = 0xE1; +pub const MAX_APDU_DATA_LEN: usize = u8::MAX as usize; + +// P2 is used to indicate APDU chunking. +// `P2_DONE` marks the final chunk, while `P2_MORE` indicates that more chunks follow. +pub const P2_DONE: u8 = 0x00; +pub const P2_MORE: u8 = 0x80; + +fn wrong_p1p2(_: u8) -> StatusWord { + StatusWord::WrongP1P2 +} + +pub struct Ins {} + +impl Ins { + pub const GET_PUB_KEY: u8 = 0x00; + pub const SIGN_TX: u8 = 0x01; + pub const SIGN_MSG: u8 = 0x02; + pub const PING: u8 = 0x03; +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] +#[num_enum(error_type(name = StatusWord, constructor = wrong_p1p2))] +#[repr(u8)] +pub enum GetPubKeyP1 { + NoDisplayAddress = 0, + DisplayAddress = 1, +} + +impl GetPubKeyP1 { + pub fn display(&self) -> bool { + *self == Self::DisplayAddress + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] +#[num_enum(error_type(name = StatusWord, constructor = wrong_p1p2))] +#[repr(u8)] +pub enum SignTxP1 { + Start = 0, + Next = 1, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] +#[num_enum(error_type(name = StatusWord, constructor = wrong_p1p2))] +#[repr(u8)] +pub enum SignMsgP1 { + Start = 0, + Next = 1, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] +#[num_enum(error_type(name = StatusWord, constructor = wrong_p1p2))] +#[repr(u8)] +pub enum PingP1 { + // Ping doesn't have parameters, so its P1 must always be zero. + Dummy = 0, +} + +#[derive(Encode, Decode)] +pub struct GetPubKeyReq { + pub coin_type: CoinType, + pub path: Bip32Path, +} + +#[derive(Encode, Decode)] +pub struct SignMessageStartReq { + pub coin: CoinType, + pub addr_type: AddrType, + pub path: Bip32Path, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)] +#[repr(u8)] +pub enum TransactionVersion { + #[codec(index = 0)] + V1, +} + +#[derive(Encode, Decode)] +pub struct SignTxStartReq { + pub coin: CoinType, + pub version: TransactionVersion, + pub num_inputs: u32, + pub num_outputs: u32, +} + +#[derive(Encode, Decode)] +pub enum SignTxNextReq { + #[codec(index = 0)] + ProcessInput(Box), + + #[codec(index = 1)] + ProcessInputCommitment(Box), + + #[codec(index = 2)] + ProcessOutput(Box), + + #[codec(index = 3)] + ReturnNextSignature, +} + +// Note: +// 1) `addresses` can contain multiple entries in the case of multisig or no entries at all in +// the case of a non-signable pseudo-input (at this moment FillOrder is the only possible +// pseudo-input). +// 2) Derivation paths in `addresses` are not checked against the actual destinations that the +// consensus requires the input to be signed against (note that in the non-utxo cases it's +// not really possible to verify their consistency, because we don't commit to the actual +// destination in those cases). +// This is not a problem in general, but note that since we always use SigHashType::ALL in this +// app, each input signature is a signature over the entire tx. So e.g. if inputs 0 and 1 require +// keys A and B respectively, a malfunctioning host may request input 0 to be signed with key B +// and input 1 with key A; in such a case the signature 0 will be valid for input 1 and vice versa. +// This does not allow the host to change the reviewed transaction, but it means that the app must +// not promise the user that a particular input was signed by the key specified in `addresses`. +#[derive(Encode, Decode)] +pub struct TxInputData { + pub addresses: Vec, + pub input: TxInputWithAdditionalInfo, +} + +#[derive(Encode, Decode)] +pub struct TxInputCommitmentData { + pub commitment: SighashInputCommitment, +} + +// TODO: +// 1) In order to be able to detect change outputs, there should be a way of specifying the destination +// via a derivation path. +// Note: the contents of Destination::PublicKeyHash and Destination::PublicKey should probably be +// enums of the form `enum PublicKeyHash { Own(derivation path), Foreign(actual hash) }`. +// 2) Possible ways of handling change outputs (simplified version of what the cardano app seems to do): +// * a) require that all derivation paths (in inputs and in outputs) belong to the same account, +// fail if they don't; +// b) if an output is a simple Transfer to a change address in the account, omit it from review; +// if it's something more complicated, don't omit it from review, but mark is as change output. +// * Same as above, but only track (without failing) whether all inputs are signed with keys from the +// same account; if so and an output is a simple Transfer to a change address in that same account, +// omit the output from review. If the output references a change address but multiple accounts +// are referenced by inputs, or if the output is not a simple Transfer, then don't omit it, +// but mark it as change. +// See https://github.com/mintlayer/mintlayer-ledger-app/issues/17. +#[derive(Encode, Decode)] +pub struct TxOutputData { + pub output: TxOutput, +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct AdditionalOrderInfo { + pub initially_asked: OutputValue, + pub initially_given: OutputValue, + pub ask_balance: Amount, + pub give_balance: Amount, +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub enum AdditionalUtxoInfo { + #[codec(index = 0)] + Utxo(TxOutput), + + #[codec(index = 1)] + UtxoWithPoolData { + utxo: TxOutput, + staker_balance: Amount, + }, +} + +impl From for SighashInputCommitment { + fn from(value: AdditionalUtxoInfo) -> Self { + match value { + AdditionalUtxoInfo::Utxo(output) => SighashInputCommitment::Utxo(output), + AdditionalUtxoInfo::UtxoWithPoolData { + utxo, + staker_balance, + } => SighashInputCommitment::ProduceBlockFromStakeUtxo { + utxo, + staker_balance, + }, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub enum TxInputWithAdditionalInfo { + #[codec(index = 0)] + Utxo(UtxoOutPoint, AdditionalUtxoInfo), + + #[codec(index = 1)] + Account(AccountOutPoint), + + #[codec(index = 2)] + AccountCommand(AccountNonce, AccountCommand), + + #[codec(index = 3)] + OrderAccountCommand(OrderAccountCommand, AdditionalOrderInfo), +} + +impl TxInputWithAdditionalInfo { + pub fn into_input_and_commitment(self) -> (TxInput, SighashInputCommitment) { + match self { + TxInputWithAdditionalInfo::Utxo(utxo, info) => (TxInput::Utxo(utxo), info.into()), + TxInputWithAdditionalInfo::Account(acc) => { + (TxInput::Account(acc), SighashInputCommitment::None) + } + TxInputWithAdditionalInfo::AccountCommand(nonce, cmd) => ( + TxInput::AccountCommand(nonce, cmd), + SighashInputCommitment::None, + ), + TxInputWithAdditionalInfo::OrderAccountCommand(cmd, info) => { + let commitment = match &cmd { + OrderAccountCommand::FillOrder(_, _) => { + SighashInputCommitment::FillOrderAccountCommand { + initially_asked: info.initially_asked, + initially_given: info.initially_given, + } + } + OrderAccountCommand::ConcludeOrder(_) => { + SighashInputCommitment::ConcludeOrderAccountCommand { + initially_asked: info.initially_asked, + initially_given: info.initially_given, + ask_balance: info.ask_balance, + give_balance: info.give_balance, + } + } + OrderAccountCommand::FreezeOrder(_) => SighashInputCommitment::None, + }; + (TxInput::OrderAccountCommand(cmd), commitment) + } + } + } +} + +#[derive(Encode, Decode, Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive)] +#[repr(u8)] +pub enum CoinType { + Mainnet = 0, + Testnet = 1, + Regtest = 2, + Signet = 3, +} + +impl From for mlcp::CoinType { + fn from(value: CoinType) -> Self { + match value { + CoinType::Mainnet => Self::Mainnet, + CoinType::Testnet => Self::Testnet, + CoinType::Regtest => Self::Regtest, + CoinType::Signet => Self::Signet, + } + } +} + +#[repr(u8)] +#[derive(Encode, Decode, Clone, Copy, IntoPrimitive)] +pub enum AddrType { + PublicKey = 0, + PublicKeyHash = 1, +} + +/// BIP32 path stored as an array of [`u32`]. +#[derive(Default, Encode, Decode, Clone)] +pub struct Bip32Path(pub Vec); + +impl AsRef<[u32]> for Bip32Path { + fn as_ref(&self) -> &[u32] { + &self.0 + } +} + +/// Address path to be signed for an input +#[derive(Encode, Decode)] +pub struct InputAddressPath { + pub path: Bip32Path, + pub multisig_idx: Option, +} + +#[derive(Encode, Decode)] +pub struct UncompressedSecp256k1PublicKey(pub [u8; 65]); + +#[derive(Encode, Decode)] +pub struct ChainCode(pub [u8; 32]); + +#[derive(Encode, Decode)] +pub struct PublicKeyResponse { + pub public_key: UncompressedSecp256k1PublicKey, + pub chain_code: ChainCode, +} + +#[derive(Encode, Decode)] +pub struct Signature(pub [u8; 64]); + +#[derive(Encode, Decode)] +pub struct TxInputSignatureResponse { + pub signature: Signature, + pub input_idx: u32, + pub multisig_idx: Option, + pub has_next: bool, +} + +#[derive(Encode, Decode)] +pub struct MsgSignatureResponse { + pub signature: Signature, +} + +#[derive(Encode, Decode)] +pub enum Response { + #[codec(index = 0)] + ExpectingNextChunk, + #[codec(index = 1)] + PublicKey(PublicKeyResponse), + #[codec(index = 2)] + TxSetup, + #[codec(index = 3)] + TxNext, + #[codec(index = 4)] + TxInputSignature(TxInputSignatureResponse), + #[codec(index = 5)] + MessageSetup, + #[codec(index = 6)] + MessageSignature(MsgSignatureResponse), + #[codec(index = 7)] + Pong, +} + +pub fn encode(t: T) -> Vec { + t.encode() +} + +pub fn encode_to(t: T, output: &mut O) +where + T: Encode, + O: parity_scale_codec::Output, +{ + t.encode_to(output) +} + +pub fn encode_as_compact(num: N) -> Vec +where + // Note: without the Num bound, if N is a reference, the compilation would fail with + // "overflow evaluating the requirement `for<'b> CompactRef<'b, _>: Encode`". + // With the bound, the error is much clearer. + N: num_traits::Num, + N: parity_scale_codec::HasCompact, + ::Type: Encode, +{ + ::Type::from(num).encode() +} + +pub fn encode_as_compact_to(num: N, output: &mut O) +where + // Same note as in encode_as_compact. + N: num_traits::Num, + N: parity_scale_codec::HasCompact, + ::Type: Encode, + O: parity_scale_codec::Output, +{ + ::Type::from(num).encode_to(output) +} + +pub fn decode_all(mut bytes: &[u8]) -> Option { + T::decode_all(&mut bytes).ok() +} + +/// This represents an APDU used in communication with Mintlayer Ledger app. +/// +/// Note that the class byte is not present here, because it's the same for all our APDUs. +/// +/// Also, we don't have the second parameter byte here either, because its meaning is the same +/// across all APDUs - it specifies whether this APDU represents the last chunk of the instruction. +pub struct Apdu<'a> { + instruction_byte: u8, + param1_byte: u8, + command_data: &'a [u8], + is_last_chunk: bool, +} + +impl<'a> Apdu<'a> { + /// Create an APDU with data; this will fail if the data length exceeds the allowed maximum. + pub fn new_with_data( + instruction_byte: u8, + param1_byte: u8, + command_data: &'a [u8], + ) -> Option { + (command_data.len() <= MAX_APDU_DATA_LEN).then_some(Self { + instruction_byte, + param1_byte, + command_data, + is_last_chunk: true, + }) + } + + /// Returns an ExactSizeIterator of APDUs by chunking the data to MAX_APDU_DATA_LEN. + pub fn new_chunks( + instruction_byte: u8, + param1_byte: u8, + data: &'a [u8], + ) -> impl ExactSizeIterator { + // Note: the standard `chunks` method returns zero-length iterator if the data has zero + // length, but we need to return 1 APDU with zero-length data in such a case. + let chunk_count = data.len().div_ceil(MAX_APDU_DATA_LEN).max(1); + let chunk_iter = (0..chunk_count).map(|i| { + let start = i * MAX_APDU_DATA_LEN; + let end = (start + MAX_APDU_DATA_LEN).min(data.len()); + &data[start..end] + }); + + chunk_iter.enumerate().map(move |(chunk_idx, chunk)| Self { + instruction_byte, + param1_byte, + command_data: chunk, + is_last_chunk: chunk_idx == chunk_count - 1, + }) + } + + pub fn is_last(&self) -> bool { + self.is_last_chunk + } + + /// The number of bytes that will be written by `write_bytes`. + /// + /// This can be used to reserve the required capacity in the destination collection + /// (note that `Extend::extend_reserve is` still nightly-only, so we can't use it). + pub fn bytes_count(&self) -> usize { + // class, instruction, param1 and param2 bytes, then 1 byte for data length, then the + // data itself. + 5 + self.command_data.len() + } + + pub fn write_bytes(&self, collection: &mut impl core::iter::Extend) { + let param2_byte = if self.is_last_chunk { P2_DONE } else { P2_MORE }; + + collection.extend([ + APDU_CLASS, + self.instruction_byte, + self.param1_byte, + param2_byte, + ]); + // Should be true by construction + assert!(self.command_data.len() <= u8::MAX as usize); + collection.extend(core::iter::once(self.command_data.len() as u8)); + collection.extend(self.command_data.iter().copied()); + } +} + +/// Application status words. +#[repr(u16)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Display, IntoPrimitive, TryFromPrimitive)] +pub enum StatusWord { + // Standard Ledger APDU Codes + #[display("Success")] + Ok = 0x9000, + #[display("Nothing received")] + NothingReceived = 0x6982, + #[display("User cancelled")] + Deny = 0x6985, + #[display("CLA not supported")] + ClaNotSupported = 0x6E00, + #[display("Instruction not supported")] + InsNotSupported = 0x6E01, + #[display("Wrong P1/P2 parameters")] + WrongP1P2 = 0x6E02, + #[display("Wrong APDU length")] + WrongApduLength = 0x6E03, + #[display("Unknown")] + Unknown = 0x6D00, + #[display("Panic")] + Panic = 0xE000, + #[display("Device locked")] + DeviceLocked = 0x5515, + + // App Specific Errors (0xB...) + #[display("Transaction display failed")] + TxDisplayFail = 0xB000, + #[display("Transaction lock time value is invalid")] + TxLockTimeInvalid = 0xB001, + #[display("Transaction wrong length")] + TxWrongLength = 0xB002, + #[display("Hashing failed")] + HashFail = 0xB003, + #[display("Address encoding failed")] + AddressEncodingFail = 0xB004, + #[display("Different instruction than expected")] + WrongInstruction = 0xB005, + #[display("Key derivation failed")] + KeyDeriveFail = 0xB006, + #[display("Orders V0 not supported")] + OrdersV0NotSupported = 0xB007, + #[display("Wrong context")] + WrongContext = 0xB008, + #[display("Deserialization failed")] + DeserializeFail = 0xB009, + #[display("Invalid input UTXO")] + TxInvalidInputUtxo = 0xB00A, + #[display("Numeric operation failed")] + TxNumericOperationFail = 0xB00B, + #[display("Tx fee underflow")] + TxFeeUnderflow = 0xB00C, + #[display("Invalid data")] + InvalidData = 0xB00D, + #[display("Nothing to sign")] + NothingToSign = 0xB00E, + #[display("Transaction already finished")] + TxAlreadyFinished = 0xB00F, + #[display("Invalid path")] + InvalidPath = 0xB010, + #[display("Invalid uncompressed public key")] + InvalidUncompressedPublicKey = 0xB011, + #[display("Max buffer length exceeded")] + MaxBufferLenExceeded = 0xB012, + #[display("Different input commitment hash")] + DifferentInputCommitmentHash = 0xB013, + #[display("Invalid timestamp")] + InvalidTimestamp = 0xB014, + #[display("Signature for FillOrder input requested")] + FillOrderSigRequested = 0xB015, + #[display("Transaction has zero inputs")] + TxWithZeroInputs = 0xB016, + + // Ecc Errors + #[display("ECC Carry")] + EccCarry = 0xB100, + #[display("ECC Locked")] + EccLocked = 0xB101, + #[display("ECC Unlocked")] + EccUnlocked = 0xB102, + #[display("ECC Not Locked")] + EccNotLocked = 0xB103, + #[display("ECC Not Unlocked")] + EccNotUnlocked = 0xB104, + #[display("ECC Internal Error")] + EccInternalError = 0xB105, + #[display("ECC Invalid Parameter Size")] + EccInvalidParameterSize = 0xB106, + #[display("ECC Invalid Parameter Value")] + EccInvalidParameterValue = 0xB107, + #[display("ECC Invalid Parameter")] + EccInvalidParameter = 0xB108, + #[display("ECC Not Invertible")] + EccNotInvertible = 0xB109, + #[display("ECC Overflow")] + EccOverflow = 0xB10A, + #[display("ECC Memory Full")] + EccMemoryFull = 0xB10B, + #[display("ECC No Residue")] + EccNoResidue = 0xB10C, + #[display("ECC Point At Infinity")] + EccPointAtInfinity = 0xB10D, + #[display("ECC Invalid Point")] + EccInvalidPoint = 0xB10E, + #[display("ECC Invalid Curve")] + EccInvalidCurve = 0xB10F, + #[display("ECC Generic Error")] + EccGenericError = 0xB110, +} diff --git a/crates/messages/src/tests.rs b/crates/messages/src/tests.rs new file mode 100644 index 0000000..aba727f --- /dev/null +++ b/crates/messages/src/tests.rs @@ -0,0 +1,74 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +use alloc::vec::Vec; + +use test_utils::prelude::*; + +use super::*; + +#[test_item] +fn test_apdu_chunking() { + let byte_iter = || core::iter::repeat(0..=9).flatten(); + + let ins = 0xaa; + let p1 = 0xbb; + + // One chunk + { + let data = byte_iter().take(100).collect::>(); + let chunks = Apdu::new_chunks(ins, p1, &data).collect::>(); + assert_eq!(chunks.len(), 1); + assert_eq!(chunks[0].instruction_byte, ins); + assert_eq!(chunks[0].param1_byte, p1); + assert_eq!(chunks[0].command_data, &data); + assert!(chunks[0].is_last_chunk); + } + + // One chunk, data is zero length + { + let chunks = Apdu::new_chunks(ins, p1, &[]).collect::>(); + assert_eq!(chunks.len(), 1); + assert_eq!(chunks[0].instruction_byte, ins); + assert_eq!(chunks[0].param1_byte, p1); + assert_eq!(chunks[0].command_data, &[]); + assert!(chunks[0].is_last_chunk); + } + + // Multiple chunks + { + let data = byte_iter().take(600).collect::>(); + let chunks = Apdu::new_chunks(ins, p1, &data).collect::>(); + + assert_eq!(chunks.len(), 3); + + assert_eq!(chunks[0].instruction_byte, ins); + assert_eq!(chunks[0].param1_byte, p1); + assert_eq!(chunks[0].command_data, &data[0..255]); + assert!(!chunks[0].is_last_chunk); + + assert_eq!(chunks[1].instruction_byte, ins); + assert_eq!(chunks[1].param1_byte, p1); + assert_eq!(chunks[1].command_data, &data[255..510]); + assert!(!chunks[1].is_last_chunk); + + assert_eq!(chunks[2].instruction_byte, ins); + assert_eq!(chunks[2].param1_byte, p1); + assert_eq!(chunks[2].command_data, &data[510..]); + assert!(chunks[2].is_last_chunk); + } +} diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml new file mode 100644 index 0000000..db8b193 --- /dev/null +++ b/crates/test-utils/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "test-utils" +version.workspace = true +edition = "2024" + +[dependencies] +ledger_device_sdk = { workspace = true, features = ["unit_test"] } +testmacro.workspace = true + +[lib] +# Note: this crate doesn't have unit tests at the moment. If we ever need them, a custom runner +# has to be set up the same way it's done in `mintlayer-app-core`. For now we just disable tests +# completely to prevent Cargo from producing an uncompilable test binary. +test = false diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs new file mode 100644 index 0000000..2e33220 --- /dev/null +++ b/crates/test-utils/src/lib.rs @@ -0,0 +1,93 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +//! Every crate that needs unit tests that can be run on Speculos must have the following: +//! 1. It must be a lib crate. +//! 2. At the top of its `lib.rs` specify this: +//! 1. Disable the generation of `fn main`. +//! +//! #![cfg_attr(test, no_main)] +//! +//! 2. "custom_test_frameworks" must be enabled to be able to specify the custom runner and use +//! the `#[test_case]` attribute (used internally by `testmacro::test_item`). +//! +//! #![feature(custom_test_frameworks)] +//! +//! 3. Specify the custom test runner. All test cases collected by `#[test_case]` will be passed +//! to this function. In particular, `sdk_test_runner` will loop over the array of test cases +//! and: +//! a) fix references stored inside the test case via pic_rs/pic; +//! b) invoke the closure associated with the test case. +//! +//! #![test_runner(ledger_device_sdk::testing::sdk_test_runner)] +//! +//! +//! 4. The following will put `fn test_main` at the test crate's root, which will call the runner +//! that we've specified above. The crate's `sample_main` will have to call `test_main`. +//! +//! #![reexport_test_harness_main = "test_main"] +//! 3. Under `#[cfg(test)]` call: +//! 1. test_utils::impl_panic_handler!(); +//! 2. test_utils::impl_main!(); + +#![no_std] + +extern crate alloc; + +use alloc::{borrow::ToOwned as _, format}; + +pub mod prelude { + // testmacro::test_item expects `TestType` to be imported. + pub use ledger_device_sdk::testing::TestType; + + pub use testmacro::test_item; +} + +#[macro_export] +macro_rules! impl_panic_handler { + () => { + #[panic_handler] + fn panic_handler(info: &core::panic::PanicInfo) -> ! { + test_utils::handle_panic(info) + } + }; +} + +#[allow(clippy::crate_in_macro_def)] +#[macro_export] +macro_rules! impl_main { + () => { + #[unsafe(no_mangle)] + extern "C" fn sample_main() { + crate::test_main(); + ledger_device_sdk::exit_app(0); + } + }; +} + +pub fn handle_panic(info: &core::panic::PanicInfo) -> ! { + ledger_device_sdk::error!( + "Panic occurred at {}: {}", + info.location().map_or_else( + || "???".to_owned(), + |loc| format!("{}:{}", loc.file(), loc.line()) + ), + info.message(), + ); + + ledger_device_sdk::exit_app(1); +} diff --git a/docs/memory_usage.md b/docs/memory_usage.md new file mode 100644 index 0000000..71a8fba --- /dev/null +++ b/docs/memory_usage.md @@ -0,0 +1,94 @@ +# Memory usage + +## Device memory size + +Judging by `.ld` files in [the Ledger Rust SDK](https://github.com/LedgerHQ/ledger-device-rust-sdk/tree/cad196841dbd72c037cfa01bec81a4a3ae57a04e/ledger_secure_sdk_sys/devices), +the amount of SRAM each model has is: +| Device | SRAM | +| ----------------- | ---- | +| apex_p, nanosplus | 40KB | +| flex, stax | 36KB | +| nanox | 28KB | + +The first part of the RAM will be occupied by the app's globals (one of which will be the heap used by the Rust code) and the rest is stack. + +The `HEAP_SIZE` variable in `.cargo/config.toml` specifies the size of the Rust heap (which is just [a static array under the hood](https://github.com/LedgerHQ/ledger-device-rust-sdk/blob/cad196841dbd72c037cfa01bec81a4a3ae57a04e/ledger_secure_sdk_sys/src/lib.rs#L64)). + +I.e. the bigger `HEAP_SIZE` is, the less is the stack. And with the `HEAP_SIZE` of 16KB, we'll have less than 12KB of stack at nanox. + +## Reducing app stack usage + +- Function's parameters and the return value consume stack space (unless the object is small enough to be put into a register). +- Moving an object around inside the function body may increase stack consumption as well. + +So, +- Box large types if you need to pass/return them by value. +- Avoid unboxing boxed large objects when passing them by value. E.g. even if a function only needs `LargeObj`, + pass `Box` to it anyway (which would be discouraged by the "normal" best practices), because passing it + unboxed would increase the stack usage.\ + This includes the case when a member function consumes `self` - declare it as `self: Box` instead. +- `sizeof` of 200 bytes is probably large enough. E.g. in the past boxing certain objects of roughly this size + decreased stack usage by roughly 1.3KB (which is more than 10% of all stack space available on nanox). + +### Determining the current stack usage of the app + +Build the app with `emit-stack-sizes`: +``` +RUSTFLAGS="-Z emit-stack-sizes" cargo ledger build nanox +``` +After that you can use `llvm-readobj` to obtain sizes of stack frames of each function: +``` +llvm-readobj --stack-sizes --demangle target/nanox/release/mintlayer-app +``` + +You can also force `llvm-readobj` to emit json and use `jq` to sort the output by the stack size. E.g. the following +will print 20 functions with the biggest stack frame size: +``` +llvm-readobj --stack-sizes --demangle --elf-output-style=JSON target/nanox/release/mintlayer-app | jq -r '.[].StackSizes | sort_by(.Entry.Size) | reverse | .[:20][] | .Entry | "\(.Size)\t\(.Functions | join(", "))"' +``` + +### Determining the actual available stack + +At least in the current version of the SDK, the linker script emits symbols that +can be used to determine the actual stack size, e.g. via `llvm-readelf`: +``` +llvm-readelf -s target/nanox/release/mintlayer-app | rg '_stack|_estack' +``` +Example output: +``` +1581: da7a425c 0 NOTYPE GLOBAL DEFAULT 6 app_stack_canary +1624: da7a7000 0 NOTYPE GLOBAL DEFAULT 6 _estack +1697: da7a4260 0 NOTYPE GLOBAL DEFAULT 6 _stack +``` +Here `_estack` is the end of the stack area, `_stack` is the beginning of it and `app_stack_canary` is a 4-byte marker +placed just below `_stack` and used to detect stack overflows. The difference between `_estack` and `_stack` will be +the stack size, in this case it's da7a7000-da7a4260=2DA0 (11680 in decimal). + +### Other notes + +This code: +``` +fn foo(x: &X) { + match x { + X::A => { /*do stuff*/ }, + X::B => { /*do other stuff*/ }, + } +} +``` +may use more stack than: +``` +fn foo(x: &X) { + match x { + X::A => stuff(), + X::B => other_stuff(), + } +} + +#[inline(never)] fn stuff() { /*do stuff*/ } +#[inline(never)] fn other_stuff() { /*do other stuff*/ } +``` +I.e. it seems that LLVM cannot always reuse stack slots between different branches of the `match`, and with bigger enums +and bigger stack usage in each branch the overhead becomes bigger as well. So, splitting a large `match` into separate +non-inlinable functions may be a way of reducing the app's stack usage, but this should probably be the last resort, +because if all large objects are boxed, the stack usage in each branch should be relatively small, which will make +the overhead relatively small as well. diff --git a/docs/technical_specification.md b/docs/technical_specification.md new file mode 100644 index 0000000..7535768 --- /dev/null +++ b/docs/technical_specification.md @@ -0,0 +1,279 @@ +# Mintlayer application: Technical Specifications + +This page details the protocol implemented for the Mintlayer Ledger app. + +## Framework + +### APDUs + +The messaging format of the app is compatible with the standard [APDU protocol](https://developers.ledger.com/docs/device-app/explanation/io#apdu-interpretation-loop). + +The class byte used for all standard commands is `CLA = 0xE1`. + +| CLA | INS | COMMAND NAME | DESCRIPTION | +| --- | --- | -------------- | ------------------------------------------------------------------ | +| E1 | 00 | GET_PUBLIC_KEY | Return (and optionally show on screen) a public key and chain code | +| E1 | 01 | SIGN_TX | Sign a transaction through a multi-step process | +| E1 | 02 | SIGN_MSG | Sign a generic message | +| E1 | 03 | PING | Simple ping request to check connectivity | + +### Chunking and P2 + +The APDU buffer on the Ledger device has a maximum data length (`MAX_APDU_DATA_LEN` = 255 bytes). To accommodate larger payloads, the app implements a chunking mechanism controlled by the `P2` parameter: + +- `P2 = 0x00` (`P2_DONE`): This is the final chunk (or the only chunk) of the instruction. The app will assemble the buffer and execute the command. +- `P2 = 0x80` (`P2_MORE`): More chunks follow. The app accumulates the data into a buffer (up to a maximum of `4080` bytes) and returns SCALE-encoded `Response::ExpectingNextChunk` to ask the client for the next chunk. + +_Note: For chunked commands, `INS` and `P1` must remain identical across all chunks of the same sequence._ + +### Data Serialization and Responses + +The app uses **SCALE codec** (`parity_scale_codec`) for the serialization and deserialization of the data payloads. Vector length prefixes and standard variable-length integer types are represented using SCALE's Compact encoding. + +#### Response Wrapper + +On a successful command execution (returning status word `0x9000`), the app encodes the returned payload inside a top-level SCALE-encoded `Response` enum. + +The SCALE representation of an enum begins with a single byte representing the variant index, followed by the encoded fields of that variant. + +The `Response` enum structure and its variant indices are: + +| Index | Name | Inner Payload Type | Description | +|-------|----------------------|------------------------------|------------------------------------------------------------------------| +| `0` | `ExpectingNextChunk` | None | Returned when more APDU chunks are expected (`P2_MORE` sequence) | +| `1` | `PublicKey` | `PublicKeyResponse` | Public key and chain code response | +| `2` | `TxSetup` | None | Acknowledges transaction initialization (`SIGN_TX` with `P1 = 0`) | +| `3` | `TxNext` | None | Acknowledges receipt of a transaction chunk | +| `4` | `TxInputSignature` | `TxInputSignatureResponse` | Contains an input signature | +| `5` | `MessageSetup` | None | Acknowledges message signing initialization (`SIGN_MSG` with `P1 = 0`) | +| `6` | `MessageSignature` | `MsgSignatureResponse` | Contains the final message signature | +| `7` | `Pong` | None | Pong response for the `PING` instruction | + +Any successful command output described below is prefixed by its corresponding 1-byte variant index. + +## Status Words + +The application returns standard Ledger status words as well as app-specific and ECC cryptographic errors. + +| SW | SW name | Description | +| ------ | ------------------------------ | ---------------------------------------------- | +| 0x5515 | `DeviceLocked` | Device locked | +| 0x6982 | `NothingReceived` | Nothing received | +| 0x6985 | `Deny` | Rejected by user / User cancelled | +| 0x6D00 | `Unknown` | Unknown | +| 0x6E00 | `ClaNotSupported` | CLA not supported | +| 0x6E01 | `InsNotSupported` | Instruction not supported | +| 0x6E02 | `WrongP1P2` | Wrong P1/P2 parameters | +| 0x6E03 | `WrongApduLength` | Wrong APDU length | +| 0x9000 | `Ok` | Success | +| 0xB000 | `TxDisplayFail` | Transaction display failed | +| 0xB001 | `TxLockTimeInvalid` | Transaction lock time value is invalid | +| 0xB002 | `TxWrongLength` | Transaction wrong length | +| 0xB003 | `HashFail` | Hashing failed | +| 0xB004 | `AddressEncodingFail` | Address encoding failed | +| 0xB005 | `WrongInstruction` | Different instruction than expected | +| 0xB006 | `KeyDeriveFail` | Key derivation failed | +| 0xB007 | `OrdersV0NotSupported` | Orders V0 not supported | +| 0xB008 | `WrongContext` | Wrong context (e.g., Next called before Start) | +| 0xB009 | `DeserializeFail` | SCALE deserialization failed | +| 0xB00A | `TxInvalidInputUtxo` | Invalid input UTXO | +| 0xB00B | `TxNumericOperationFail` | Numeric operation failed | +| 0xB00C | `TxFeeUnderflow` | Tx fee underflow | +| 0xB00D | `InvalidData` | Invalid data | +| 0xB00E | `NothingToSign` | Nothing to sign | +| 0xB00F | `TxAlreadyFinished` | Transaction already finished | +| 0xB010 | `InvalidPath` | Invalid BIP32 path | +| 0xB011 | `InvalidUncompressedPublicKey` | Invalid uncompressed public key | +| 0xB012 | `MaxBufferLenExceeded` | Max buffer length exceeded (Chunking limit) | +| 0xB013 | `DifferentInputCommitmentHash` | Different input commitment hash | +| 0xB014 | `InvalidTimestamp` | Invalid timestamp | +| 0xB015 | `FillOrderSigRequested` | Signature for FillOrder input requested | +| 0xB016 | `TxWithZeroInputs` | Transaction has zero inputs | +| 0xB100 | `EccCarry` | ECC Carry | +| 0xB101 | `EccLocked` | ECC Locked | +| 0xB102 | `EccUnlocked` | ECC Unlocked | +| 0xB103 | `EccNotLocked` | ECC Not Locked | +| 0xB104 | `EccNotUnlocked` | ECC Not Unlocked | +| 0xB105 | `EccInternalError` | ECC Internal Error | +| 0xB106 | `EccInvalidParameterSize` | ECC Invalid Parameter Size | +| 0xB107 | `EccInvalidParameterValue` | ECC Invalid Parameter Value | +| 0xB108 | `EccInvalidParameter` | ECC Invalid Parameter | +| 0xB109 | `EccNotInvertible` | ECC Not Invertible | +| 0xB10A | `EccOverflow` | ECC Overflow | +| 0xB10B | `EccMemoryFull` | ECC Memory Full | +| 0xB10C | `EccNoResidue` | ECC No Residue | +| 0xB10D | `EccPointAtInfinity` | ECC Point At Infinity | +| 0xB10E | `EccInvalidPoint` | ECC Invalid Point | +| 0xB10F | `EccInvalidCurve` | ECC Invalid Curve | +| 0xB110 | `EccGenericError` | ECC Generic Error | +| 0xE000 | `Panic` | Panic | + +--- + +## Commands + +### GET_PUBLIC_KEY + +Returns a public key and chain code at the given derivation path. +Optionally displays the generated address on the device screen for user verification. + +#### Encoding + +**Command** + +| _CLA_ | _INS_ | _P1_ | +| ----- | ----- | -------------------- | +| E1 | 00 | `0` or `1` (Display) | + +**Input data (`GetPubKeyReq` - SCALE encoded)** + +| Type | Name | Description | +| ----------- | ----------- | --------------------------------------------------------- | +| `u8` (Enum) | `coin_type` | `0` = Mainnet, `1` = Testnet, `2` = Regtest, `3` = Signet | +| `Vec` | `path` | The BIP32 derivation path | + +**Output data (`Response::PublicKey(PublicKeyResponse)` - SCALE encoded)** + +| Length | Description | +| ------ | ------------------------------------------------------------------- | +| `1` | Variant index byte (`0x01`) | +| `65` | The uncompressed public key (`PublicKeyResponse.public_key`) | +| `32` | The chain code (`PublicKeyResponse.chain_code`) | + +#### Description + +If `P1` is `0`, the application derives the public key and returns it silently. +If `P1` is `1`, the application will display the address derived from the requested path on the device screen. +The command will only return `Ok` (0x9000) if the user approves it, otherwise, it returns `Deny` (0x6985). + +--- + +### SIGN_TX + +Signs a Mintlayer transaction. +Because transactions can be larger than available APDU buffers and RAM, the parsing and signing process is split into an interactive state machine using `P1 = 0` (Start) and `P1 = 1` (Next). + +#### Encoding + +**Command** + +| _CLA_ | _INS_ | _P1_ | +| ----- | ----- | ------------------------- | +| E1 | 01 | `0` (Start) or `1` (Next) | + +**Input Data for `P1 = 0` (Start) (`SignTxStartReq` - SCALE encoded)** + +| Type | Name | Description | +| ----- | ------------- | --------------------------------------------------------- | +| `u8` | `coin` | `0` = Mainnet, `1` = Testnet, `2` = Regtest, `3` = Signet | +| `u8` | `version` | Transaction version (0 = V1) | +| `u32` | `num_inputs` | Total number of inputs in the transaction | +| `u32` | `num_outputs` | Total number of outputs in the transaction | + +**Input Data for `P1 = 1` (Next) (`SignTxNextReq` Enum - SCALE encoded)** + +The client sends a sequence of `SignTxNextReq` variants. The variant index dictates the type of data being sent: + +- `ProcessInput` (Index 0): Contains `TxInputData` (input address paths and UTXO/Account info). +- `ProcessInputCommitment` (Index 1): Contains `TxInputCommitmentData`. +- `ProcessOutput` (Index 2): Contains `TxOutputData`. +- `ReturnNextSignature` (Index 3): Requests the device to yield the next available signature. + +**Output data (`Response::TxInputSignature(TxInputSignatureResponse)` - SCALE encoded)** + +Yielded during `ReturnNextSignature` sequences. + +The response payload is prefixed with the `TxInputSignature` variant index (`0x04`), followed by the `TxInputSignatureResponse` fields: + +| Type | Name | Description | +| ------------- | -------------- | ------------------------------------------- | +| `[u8; 64]` | `signature` | The 64-byte cryptographic signature | +| `u32` | `input_idx` | The index of the input that was just signed | +| `Option` | `multisig_idx` | Optional multisig index | +| `bool` | `has_next` | True if there are more signatures remaining | + +*Note: For `Start` (`P1 = 0`) and intermediate `Next` (`P1 = 1`) data chunks (before signatures), the app returns `Response::TxSetup` (variant index `0x02`) and `Response::TxNext` (variant index `0x03`) respectively, with no extra fields.* + +#### Description + +To sign a transaction, the client must follow a strict order: + +1. Call `SIGN_TX` with `P1 = 0` (Start) passing `SignTxStartReq`. +2. Sequentially call `SIGN_TX` with `P1 = 1` (Next) to stream inputs (`ProcessInput`), input commitments (`ProcessInputCommitment`) and then outputs (`ProcessOutput`). +3. After all data is verified by the user on the device's secure screen, the client requests signatures by repeatedly calling `SIGN_TX` with `P1 = 1` and the `ReturnNextSignature` variant. +4. The device will yield `TxInputSignatureResponse` payloads until `has_next` is false. + +--- + +### SIGN_MSG + +Signs a generic message using a BIP-32 derived key. + +#### Encoding + +**Command** + +| _CLA_ | _INS_ | _P1_ | +| ----- | ----- | ------------------------- | +| E1 | 02 | `0` (Start) or `1` (Next) | + +**Input Data for `P1 = 0` (Start) (`SignMessageStartReq` - SCALE encoded)** + +| Type | Name | Description | +| ----------- | ----------- | --------------------------------------------- | +| `u8` (Enum) | `coin` | Coin type (Mainnet, Testnet, Regtest, Signet) | +| `u8` (Enum) | `addr_type` | `0` = PublicKey, `1` = PublicKeyHash | +| `Vec` | `path` | The BIP32 derivation path to use for signing | + +**Input Data for `P1 = 1` (Next)** + +| Type | Description | +| ------------ | ------------------------------------------------------------- | +| `` | The raw byte chunks of the message payload to be accumulated. | + +**Output data (`Response::MessageSignature(MsgSignatureResponse)` - SCALE encoded)** + +The response payload is prefixed with the `MessageSignature` variant index (`0x06`), followed by the `MsgSignatureResponse` fields: + +| Length | Description | +| ------ | ----------------------------------- | +| `64` | The 64-byte cryptographic signature | + +*Note: For `Start` (`P1 = 0`) initialization, the app returns `Response::MessageSetup` (variant index `0x05`) with no extra fields.* + +#### Description + +The `SIGN_MSG` flow initializes the signing context via `Start`. +The client then sends the message chunks iteratively using `Next`. +Once the message transmission is completed and the user validates the request on the hardware screen, +the device returns the 64-byte signature. +Chunking the payload via the `P2` parameter mechanism is utilized here if the data exceeds a single APDU packet limit. + +--- + +### PING + +Simple ping request to check connectivity. + +#### Encoding + +**Command** + +| _CLA_ | _INS_ | _P1_ | +| ----- | ----- | ---- | +| E1 | 03 | `0` | + +**Input data** + +None. + +**Output data (`Response::Pong` - SCALE encoded)** + +| Length | Description | +| ------ | ----------------------------------------------------------- | +| `1` | Variant index byte (`0x07`) representing `Response::Pong` | + +#### Description + +The client sends a `PING` command to verify that the app is running and responsive. +The app returns `Response::Pong` (success indicator) on success, and returns the home screen to the user. diff --git a/ledger_app.toml b/ledger_app.toml new file mode 100644 index 0000000..853c6c5 --- /dev/null +++ b/ledger_app.toml @@ -0,0 +1,7 @@ +[app] +build_directory = "./" +sdk = "Rust" +devices = ["nanox", "nanos+", "stax", "flex", "apex_p"] + +[tests] +pytest_directory = "./tests/" diff --git a/media/glyphs/home_nano_nbgl.png b/media/glyphs/home_nano_nbgl.png new file mode 100644 index 0000000..4fa2028 Binary files /dev/null and b/media/glyphs/home_nano_nbgl.png differ diff --git a/media/glyphs/mintlayer_48x48.png b/media/glyphs/mintlayer_48x48.png new file mode 100644 index 0000000..991e98d Binary files /dev/null and b/media/glyphs/mintlayer_48x48.png differ diff --git a/media/glyphs/mintlayer_64x64.gif b/media/glyphs/mintlayer_64x64.gif new file mode 100644 index 0000000..d5bdcef Binary files /dev/null and b/media/glyphs/mintlayer_64x64.gif differ diff --git a/media/icons/mask_14x14.gif b/media/icons/mask_14x14.gif new file mode 100644 index 0000000..1568a9f Binary files /dev/null and b/media/icons/mask_14x14.gif differ diff --git a/media/icons/mintlayer_14x14.gif b/media/icons/mintlayer_14x14.gif new file mode 100644 index 0000000..df802c2 Binary files /dev/null and b/media/icons/mintlayer_14x14.gif differ diff --git a/media/icons/mintlayer_32x32.gif b/media/icons/mintlayer_32x32.gif new file mode 100644 index 0000000..c1dbbf1 Binary files /dev/null and b/media/icons/mintlayer_32x32.gif differ diff --git a/media/icons/mintlayer_32x32.png b/media/icons/mintlayer_32x32.png new file mode 100644 index 0000000..790b9cd Binary files /dev/null and b/media/icons/mintlayer_32x32.png differ diff --git a/media/icons/mintlayer_40x40.gif b/media/icons/mintlayer_40x40.gif new file mode 100644 index 0000000..c2532c5 Binary files /dev/null and b/media/icons/mintlayer_40x40.gif differ diff --git a/run_extra_checks.sh b/run_extra_checks.sh new file mode 100755 index 0000000..681353e --- /dev/null +++ b/run_extra_checks.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Run some extra checks. + +set -e +set -o nounset + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +cd "$SCRIPT_DIR" + +echo "Running codecheck.py" +python3 "tools/codecheck.py" + +# Notes about clippy: +# 1. Ledger's guideline enforcer also runs it. But at the moment of writing this it doesn't check +# tests, see https://github.com/LedgerHQ/ledger-app-workflows/blob/master/scripts/check_all.sh. +# Besides, we want to enable some additional checks, similar to what we do in Mintlayer Core, +# so we do a separate clippy run here. +# 2. The guideline enforcer runs it for all existing device models, but in this additional run this +# is redundant, so we use one arbitrarily chosen model. +# 3. Unlike in Mintlayer Core, we can't disable certain annoying and mostly useless checks (such as +# let-and-return), because the guideline enforcer will run them anyway. + +echo "Running cargo fmt" +cargo fmt --check -- --config newline_style=Unix + +CLIPPY_TARGET_ARG=--target=apex_p + +echo "Running clippy (any code)" +cargo clippy "$CLIPPY_TARGET_ARG" --all-features --workspace --bins --lib --tests -- \ + -D warnings \ + -D clippy::implicit_saturating_sub \ + -D clippy::implicit_clone \ + -D clippy::map_unwrap_or \ + -D clippy::unnested_or_patterns \ + -D clippy::mut_mut \ + -D clippy::todo + +echo "Running clippy (production code)" +# TODO: consider also enabling `unwrap_used` and `items_after_statements`. +cargo clippy "$CLIPPY_TARGET_ARG" --all-features --workspace --bins --lib -- \ + -A clippy::all \ + -D clippy::float_arithmetic \ + -D clippy::dbg_macro \ + -D clippy::fallible_impl_from \ + -D clippy::string_slice + +echo "All checks passed" diff --git a/run_unit_tests.sh b/run_unit_tests.sh new file mode 100755 index 0000000..ce503e1 --- /dev/null +++ b/run_unit_tests.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e +set -o nounset + +# Run unit tests. +# The first argument must be the device model: nanox, nanosp, stax, flex or apex_p. +# The second argument is optional (defaults to all) and specifies the crate whose tests must be run. + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +cd "$SCRIPT_DIR" + +MODEL=$1 +PACKAGE=${2:-all} + +if [[ "$MODEL" == "nanosp" ]]; then + TARGET="nanosplus" +else + TARGET="$MODEL" +fi + +echo "*** Running unit tests on $MODEL ***" + +if [[ "$PACKAGE" == "all" ]]; then + PACKAGES=(mintlayer-app-core mintlayer-messages) +else + PACKAGES=("$PACKAGE") +fi + +for package in "${PACKAGES[@]}"; do + echo "*** Building unit tests for $package ***" + + # Build the test with normal output and without capturing it, so that build errors, if any, + # are visible. + cargo test -p "$package" --release --no-run --target="$TARGET" + + # Build the test again using `--message-format=json`, to capture the name of the test executable. + output=$(cargo test -p "$package" --release --no-run --message-format=json --target="$TARGET") + jq_selector='select(.reason == "compiler-artifact") | select(.profile.test == true) | select(.executable != null) | .executable' + test_exe_path=$(jq -r "$jq_selector" <<< "$output") + + echo "*** Running unit tests for $package ***" + speculos --display headless --model "$MODEL" "$test_exe_path" +done diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ffbbd17 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2025-12-05" + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..dcf0260 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,29 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025-2026 RBB S.r.l. + * + * 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. + *****************************************************************************/ + +#![no_std] +#![no_main] + +use mintlayer_app_core::mintlayer_main; + +ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); + +#[no_mangle] +extern "C" fn sample_main() { + mintlayer_main(); +} diff --git a/tests/application_client/__init__.py b/tests/application_client/__init__.py new file mode 100644 index 0000000..fbcf26c --- /dev/null +++ b/tests/application_client/__init__.py @@ -0,0 +1,421 @@ +# Copyright (c) 2025-2026 RBB S.r.l +# opensource@mintlayer.org +# SPDX-License-Identifier: MIT +# Licensed under the MIT License; +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +# +# 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. + +import scalecodec # type: ignore + +MAINNET = 0 +TESTNET = 1 +REGTEST = 2 +SIGNET = 3 + + +def init_mintlayer_types(): + custom_types = { + "types": { + "Bip32Path": "Vec", + "Amount": "Compact", + "H256": "[u8; 32]", + "BlockHeight": "Compact", + "BlockTimestamp": "Compact", + "OutputValue": { + "type": "enum", + "type_mapping": [ + ["Coin", "Amount"], + # Note: need to have this variant to make sure TokenV1's index is 2. + # pylint: disable=fixme + # TODO: the protocol should probably not use types from mintlayer core + # primitives. + # See https://github.com/mintlayer/mintlayer-ledger-app/issues/18. + ["DeprecatedTokenV0", ""], + ["TokenV1", "(TokenId, Amount)"], + ], + }, + "InputAddressPath": { + "type": "struct", + "type_mapping": [ + ["path", "Vec"], + ["multisig_idx", "Option"], + ], + }, + "Destination": { + "type": "enum", + "type_mapping": [ + ["AnyoneCanSpend", "()"], + ["PublicKeyHash", "PublicKeyHash"], + ["PublicKey", "PublicKey"], + ["ScriptHash", "ScriptId"], + ["ClassicMultisig", "PublicKeyHash"], + ], + }, + "PublicKeyHash": "[u8; 20]", + "PublicKey": { + "type": "struct", + "type_mapping": [ + ["key", "PublicKeyHolder"], + ], + }, + "PublicKeyHolder": { + "type": "enum", + "type_mapping": [ + ["Secp256k1Schnorr", "Secp256k1PublicKey"], + ], + }, + "Secp256k1PublicKey": { + "type": "struct", + "type_mapping": [ + ["pubkey_data", "[u8; 33]"], + ], + }, + "IsTokenFreezable": { + "type": "enum", + "type_mapping": [ + ["No", "()"], + ["Yes", "()"], + ], + }, + "TokenTotalSupply": { + "type": "enum", + "type_mapping": [ + ["Fixed", "Amount"], + ["Lockable", "()"], + ["Unlimited", "()"], + ], + }, + "TokenIssuanceV1": { + "type": "struct", + "type_mapping": [ + ["token_ticker", "Vec"], + ["number_of_decimals", "u8"], + ["metadata_uri", "Vec"], + ["total_supply", "TokenTotalSupply"], + ["authority", "Destination"], + ["is_freezable", "IsTokenFreezable"], + ], + }, + "TokenIssuance": { + "type": "enum", + # The Rust enum has an explicit codec index of 1 for the V1 variant. + # A placeholder is added for index 0 to ensure correct encoding. + "type_mapping": [ + ["_Unused", "()"], + ["V1", "TokenIssuanceV1"], + ], + }, + "TokenCreator": { + "type": "struct", + "type_mapping": [ + ["public_key", "PublicKey"], + ], + }, + "Metadata": { + "type": "struct", + "type_mapping": [ + ["creator", "Option"], + ["name", "Vec"], + ["description", "Vec"], + ["ticker", "Vec"], + ["icon_uri", "Vec"], + ["additional_metadata_uri", "Vec"], + ["media_uri", "Vec"], + ["media_hash", "Vec"], + ], + }, + "NftIssuanceV0": { + "type": "struct", + "type_mapping": [ + ["metadata", "Metadata"], + ], + }, + "NftIssuance": { + "type": "enum", + "type_mapping": [ + ["V0", "NftIssuanceV0"], + ], + }, + "OrderData": { + "type": "struct", + "type_mapping": [ + ["conclude_key", "Destination"], + ["ask", "OutputValue"], + ["give", "OutputValue"], + ], + }, + "TxOutput": { + "type": "enum", + "type_mapping": [ + ["Transfer", "(OutputValue, Destination)"], + ["LockThenTransfer", "(OutputValue, Destination, OutputTimeLock)"], + ["Burn", "OutputValue"], + ["CreateStakePool", "(PoolId, StakePoolData)"], + ["ProduceBlockFromStake", "(Destination, PoolId)"], + ["CreateDelegationId", "(Destination, PoolId)"], + ["DelegateStaking", "(Amount, DelegationId)"], + ["IssueFungibleToken", "TokenIssuance"], + ["IssueNft", "(TokenId, NftIssuance, Destination)"], + ["DataDeposit", "Vec"], + ["Htlc", "(OutputValue, HashedTimelockContract)"], + ["CreateOrder", "OrderData"], + ], + }, + "HashedTimelockContract": { + "type": "struct", + "type_mapping": [ + ["secret_hash", "[u8; 20]"], + ["spend_key", "Destination"], + ["refund_timelock", "OutputTimeLock"], + ["refund_key", "Destination"], + ], + }, + "OutputTimeLock": { + "type": "enum", + "type_mapping": [ + ["UntilHeight", "BlockHeight"], + ["UntilTime", "BlockTimestamp"], + ["ForBlockCount", "Compact"], + ["ForSeconds", "Compact"], + ], + }, + "PoolId": "H256", + "DelegationId": "H256", + "TokenId": "H256", + "OrderId": "H256", + "StakePoolData": { + "type": "struct", + "type_mapping": [ + ["pledge", "Amount"], + ["staker", "Destination"], + ["vrf_public_key", "VRFPublicKey"], + ["decommission_key", "Destination"], + ["margin_ratio_per_thousand", "u16"], + ["cost_per_block", "Amount"], + ], + }, + "VRFPublicKey": { + "type": "struct", + "type_mapping": [ + ["key", "VRFPublicKeyHolder"], + ], + }, + "VRFPublicKeyHolder": { + "type": "enum", + "type_mapping": [ + ["Schnorrkel", "SchnorrkelPublicKey"], + ], + }, + "SchnorrkelPublicKey": { + "type": "struct", + "type_mapping": [ + ["key", "[u8; 32]"], + ], + }, + "OutPointSourceId": { + "type": "enum", + "type_mapping": [ + ["Transaction", "H256"], + ["BlockReward", "H256"], + ], + }, + "OutPoint": { + "type": "struct", + "type_mapping": [ + ["id", "OutPointSourceId"], + ["index", "u32"], + ], + }, + "AccountNonce": "Compact", + "IsTokenUnfreezable": { + "type": "enum", + "type_mapping": [ + ["No", "()"], + ["Yes", "()"], + ], + }, + "AccountCommand": { + "type": "enum", + "type_mapping": [ + ["MintTokens", "(TokenId, Amount)"], + ["UnmintTokens", "TokenId"], + ["LockTokenSupply", "TokenId"], + ["FreezeToken", "(TokenId, IsTokenUnfreezable)"], + ["UnfreezeToken", "TokenId"], + ["ChangeTokenAuthority", "(TokenId, Destination)"], + ["ConcludeOrder", "OrderId"], + ["FillOrder", "(OrderId, Amount, Destination)"], + ["ChangeTokenMetadataUri", "(TokenId, Vec)"], + ], + }, + "OrderAccountCommand": { + "type": "enum", + "type_mapping": [ + ["FillOrder", "(OrderId, Amount)"], + ["FreezeOrder", "OrderId"], + ["ConcludeOrder", "OrderId"], + ], + }, + "TxInputWithAdditionalInfo": { + "type": "enum", + "type_mapping": [ + ["Utxo", "(OutPoint, AdditionalUtxoInfo)"], + ["Account", "AccountOutPoint"], + ["AccountCommand", "(AccountNonce, AccountCommand)"], + [ + "OrderAccountCommand", + "(OrderAccountCommand, AdditionalOrderInfo)", + ], + ], + }, + "AccountOutPoint": { + "type": "struct", + "type_mapping": [ + ["nonce", "Compact"], + ["spending", "AccountSpending"], + ], + }, + "AccountSpending": { + "type": "enum", + "type_mapping": [ + ["DelegationBalance", "(H256, Amount)"], + ], + }, + "SighashInputCommitment": { + "type": "enum", + "type_mapping": [ + ["None", "()"], + ["Utxo", "TxOutput"], + ["ProduceBlockFromStakeUtxo", "(TxOutput, Amount)"], + ["FillOrderAccountCommand", "(OutputValue, OutputValue)"], + [ + "ConcludeOrderAccountCommand", + "(OutputValue, OutputValue, Amount, Amount)", + ], + ], + }, + "SignTxStartReq": { + "type": "struct", + "type_mapping": [ + ["coin", "u8"], + ["version", "u8"], + ["num_inputs", "u32"], + ["num_outputs", "u32"], + ], + }, + "TxInputData": { + "type": "struct", + "type_mapping": [ + ["addresses", "Vec"], + ["input", "TxInputWithAdditionalInfo"], + ], + }, + "TxInputCommitmentData": { + "type": "struct", + "type_mapping": [ + ["commitment", "SighashInputCommitment"], + ], + }, + "UtxoWithPoolData": { + "type": "struct", + "type_mapping": [ + ["utxo", "TxOutput"], + ["staker_balance", "Amount"], + ], + }, + "AdditionalUtxoInfo": { + "type": "enum", + "type_mapping": [ + ["Utxo", "TxOutput"], + ["UtxoWithPoolData", "UtxoWithPoolData"], + ], + }, + "AdditionalOrderInfo": { + "type": "struct", + "type_mapping": [ + ["initially_asked", "OutputValue"], + ["initially_given", "OutputValue"], + ["ask_balance", "Amount"], + ["give_balance", "Amount"], + ], + }, + "TxOutputData": { + "type": "struct", + "type_mapping": [ + ["output", "TxOutput"], + ], + }, + "SignTxNextReq": { + "type": "enum", + "type_mapping": [ + ["ProcessInput", "TxInputData"], + ["ProcessInputCommitment", "TxInputCommitmentData"], + ["ProcessOutput", "TxOutputData"], + ["ReturnNextSignature", "()"], + ], + }, + "SignatureInResponse": "[u8; 64]", + "TxInputSignatureResponse": { + "type": "struct", + "type_mapping": [ + ["signature", "SignatureInResponse"], + ["input_idx", "u32"], + ["multisig_idx", "Option"], + ["has_next", "bool"], + ], + }, + "UncompressedSecp256k1PublicKey": "[u8; 65]", + "ChainCode": "[u8; 32]", + "PublicKeyResponse": { + "type": "struct", + "type_mapping": [ + ["public_key", "UncompressedSecp256k1PublicKey"], + ["chain_code", "ChainCode"], + ], + }, + "MsgSignatureResponse": { + "type": "struct", + "type_mapping": [ + ["signature", "SignatureInResponse"], + ], + }, + "Response": { + "type": "enum", + "type_mapping": [ + ["ExpectingNextChunk", "()"], + ["PublicKey", "PublicKeyResponse"], + ["TxSetup", "()"], + ["TxNext", "()"], + ["TxInputSignature", "TxInputSignatureResponse"], + ["MessageSetup", "()"], + ["MessageSignature", "MsgSignatureResponse"], + ["Pong", "()"], + ], + }, + "TxInput": { + "type": "enum", + "type_mapping": [ + ["Utxo", "OutPoint"], + ["Account", "AccountOutPoint"], + ["AccountCommand", "(AccountNonce, AccountCommand)"], + [ + "OrderAccountCommand", + "OrderAccountCommand", + ], + ], + }, + } + } + + scalecodec.base.RuntimeConfiguration().update_type_registry(custom_types) + + +init_mintlayer_types() diff --git a/tests/application_client/mintlayer_command_sender.py b/tests/application_client/mintlayer_command_sender.py new file mode 100644 index 0000000..529b330 --- /dev/null +++ b/tests/application_client/mintlayer_command_sender.py @@ -0,0 +1,534 @@ +from contextlib import contextmanager +from dataclasses import dataclass +from enum import IntEnum +from typing import Generator, List + +import scalecodec # type: ignore +from ragger.backend.interface import RAPDU, BackendInterface +from ragger.navigator import NavInsID +from ragger.navigator.navigation_scenario import NavigationScenarioData, UseCase + +from .mintlayer_response_unpacker import ( + unpack_get_public_key_response, +) +from .mintlayer_utils import ( + Transaction, + TxInputSignatureResponse, + TxInputSignature, + decode_response_variant, + sign_tx_start_req_obj, + sign_tx_next_req_obj, + verify_tx_signature, +) + +MAX_APDU_LEN: int = 255 + +CLA: int = 0xE1 + + +@dataclass +class ReviewTransaction: + transaction: Transaction + has_command_input: bool + review_custom_screen_text: str + + +@dataclass +class SignTxStep: + kind: str + + +class GetAppAndVersionP1(IntEnum): + # Parameter 1 for first APDU number. + P1_START = 0x00 + P1_NEXT = 0x01 + + +class SignTxP1(IntEnum): + # Parameter 1 for first APDU number. + P1_START = 0x00 + P1_NEXT = 0x01 + + +class SignMessageP1(IntEnum): + # Parameter 1 for first APDU number. + P1_START = 0x00 + P1_NEXT = 0x01 + + +class GetPublicKeyP1(IntEnum): + P1_DO_NOT_CONFIRM = 0x00 + # Parameter 1 for screen confirmation for GET_PUBLIC_KEY. + P1_CONFIRM = 0x01 + + +class P2(IntEnum): + # Parameter 2 for last APDU to receive. + P2_LAST = 0x00 + # Parameter 2 for more APDU to receive. + P2_MORE = 0x80 + + +class InsType(IntEnum): + GET_PUBLIC_KEY = 0x00 + SIGN_TX = 0x01 + SIGN_MESSAGE = 0x02 + PING = 0x03 + + +class Errors(IntEnum): + SW_DENY = 0x6985 + SW_CLA_NOT_SUPPORTED = 0x6E00 + SW_INS_NOT_SUPPORTED = 0x6E01 + SW_WRONG_P1P2 = 0x6E02 + SW_WRONG_APDU_LENGTH = 0x6E03 + + SW_TX_DISPLAY_FAIL = 0xB000 + SW_TX_LOCK_TIME_INVALID = 0xB001 + SW_WRONG_TX_LENGTH = 0xB002 + SW_WRONG_CONTEXT = 0xB008 + SW_DESERIALIZE_FAIL = 0xB009 + SW_MAX_BUFFER_LEN_EXCEEDED = 0xB012 + + +def split_message(message: bytes, max_size: int) -> List[bytes]: + if len(message) == 0: + return [b""] + + return [message[x : x + max_size] for x in range(0, len(message), max_size)] + + +class MintlayerCommandSender: + def __init__(self, backend: BackendInterface) -> None: + self.backend = backend + + def get_app_and_version(self) -> RAPDU: + return self.backend.exchange( + cla=0xB0, # specific CLA for BOLOS + ins=0x01, # specific INS for get_app_and_version + p1=GetAppAndVersionP1.P1_START, + p2=P2.P2_LAST, + data=b"", + ) + + def get_public_key(self, coin: int, path: str) -> RAPDU: + data = coin.to_bytes(1, "little") + pack_derivation_path_from_str(path) + + return self.backend.exchange( + cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=GetPublicKeyP1.P1_DO_NOT_CONFIRM, + p2=P2.P2_LAST, + data=data, + ) + + def get_public_key_by_ints_path(self, coin: int, path: list[int]) -> RAPDU: + data = coin.to_bytes(1, "little") + pack_derivation_path_from_ints(path) + + return self.backend.exchange( + cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=GetPublicKeyP1.P1_DO_NOT_CONFIRM, + p2=P2.P2_LAST, + data=data, + ) + + @contextmanager + def get_public_key_with_confirmation( + self, coin: int, path: str + ) -> Generator[None, None, None]: + data = coin.to_bytes(1, "little") + pack_derivation_path_from_str(path) + + with self.backend.exchange_async( + cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=GetPublicKeyP1.P1_CONFIRM, + p2=P2.P2_LAST, + data=data, + ): + yield + + @contextmanager + def sign_message( + self, coin: int, addr_type: int, path: str, message: bytes + ) -> Generator[None, None, None]: + data = ( + coin.to_bytes(1, "little") + + addr_type.to_bytes(1, "little") + + pack_derivation_path_from_str(path) + ) + + response = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_MESSAGE, + p1=SignMessageP1.P1_START, + p2=P2.P2_LAST, + data=data, + ) + decode_response_variant(response.data, "MessageSetup") + + chunks = split_message(message, MAX_APDU_LEN) + + for chunk in chunks[:-1]: + response = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_MESSAGE, + p1=SignMessageP1.P1_NEXT, + p2=P2.P2_MORE, + data=chunk, + ) + decode_response_variant(response.data, "ExpectingNextChunk") + + with self.backend.exchange_async( + cla=CLA, + ins=InsType.SIGN_MESSAGE, + p1=SignMessageP1.P1_NEXT, + p2=P2.P2_LAST, + data=chunks[-1], + ): + yield + + # pylint: disable-next=too-many-locals + def sign_tx(self, transaction: Transaction) -> Generator[SignTxStep, None, None]: + # ---- Start req ---- + start_req = sign_tx_start_req_obj.encode( + { + "coin": transaction.coin, + "version": 0, + "num_inputs": len(transaction.inputs), + "num_outputs": len(transaction.outputs), + } + ).data + + response = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_START, + p2=P2.P2_LAST, + data=bytes(start_req), + ) + decode_response_variant(response.data, "TxSetup") + + # ---- INPUTS ---- + print("sending inputs", len(transaction.inputs)) + + def encode_input(inp): + return sign_tx_next_req_obj.encode({"ProcessInput": inp}).data + + def encode_input_comm(comm): + return sign_tx_next_req_obj.encode({"ProcessInputCommitment": comm}).data + + def encode_output(outp): + return sign_tx_next_req_obj.encode({"ProcessOutput": outp}).data + + for inp in transaction.inputs: + encoded_inp = encode_input(inp) + self._send_chunked_sync(encoded_inp, "TxNext") + + # ---- INPUT COMMITMENTS ---- + print("sending input commitments") + + for comm in transaction.input_commitments[:-1]: + encoded_comm = encode_input_comm(comm) + self._send_chunked_sync(encoded_comm, "TxNext") + + encoded_comm = encode_input_comm(transaction.input_commitments[-1]) + chunks = split_message(encoded_comm, MAX_APDU_LEN) + + # all but last chunk sync + for chunk in chunks[:-1]: + response = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_MORE, + data=chunk, + ) + decode_response_variant(response.data, "ExpectingNextChunk") + + # last chunk async -> UI review + with self.backend.exchange_async( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_LAST, + data=chunks[-1], + ): + yield SignTxStep(kind="start") + + if len(transaction.outputs) == 0: + yield SignTxStep(kind="sign") + + response = self.get_async_response() + decode_response_variant(response.data, "TxNext") + + # ---- OUTPUTS ---- + print("streaming outputs") + + for idx, out in enumerate(transaction.outputs): + print(f"sending output {idx}") + + encoded_out = encode_output(out) + chunks = split_message(encoded_out, MAX_APDU_LEN) + + # all but last chunk sync + for chunk in chunks[:-1]: + response = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_MORE, + data=chunk, + ) + decode_response_variant(response.data, "ExpectingNextChunk") + + # last chunk async -> UI review + with self.backend.exchange_async( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_LAST, + data=chunks[-1], + ): + kind = "sign" if idx == len(transaction.outputs) - 1 else "output" + yield SignTxStep(kind=kind) + + response = self.get_async_response() + decode_response_variant(response.data, "TxNext") + + def _send_chunked_sync(self, data: bytes, expected_last_response_variant: str): + chunks = split_message(data, MAX_APDU_LEN) + + for chunk in chunks[:-1]: + response = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_MORE, + data=chunk, + ) + decode_response_variant(response.data, "ExpectingNextChunk") + + response = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_LAST, + data=chunks[-1], + ) + return decode_response_variant(response.data, expected_last_response_variant) + + def get_async_response(self) -> RAPDU: + response = self.backend.last_async_response + assert response is not None + return response + + def get_all_signatures(self, transaction: Transaction) -> List[TxInputSignature]: + next_sig = sign_tx_next_req_obj.encode({"ReturnNextSignature": None}).data + sigs = [] + expected_sigs_count = len(transaction.expected_sig_indices()) + + while True: + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_LAST, + data=next_sig, + ) + res = TxInputSignatureResponse.from_data(res.data) + + sigs.append(TxInputSignature.from_response(res)) + + if not res.has_next: + break + + assert len(sigs) < expected_sigs_count, ( + "has_next is still true after the expected number of signatures " + f"have been received (sigs = {sigs!r})" + ) + return sigs + + +def hardened_index(index: int) -> int: + return index | 1 << 31 + + +def parse_derivation_path(derivation_path: str) -> list[int]: + split = derivation_path.split("/") + + if split[0] != "m": + raise ValueError("Error master expected") + + result = [] + for value in split[1:]: + if value == "": + raise ValueError(f'Error missing value in split list "{split}"') + if value.endswith("'"): + result.append(hardened_index(int(value[:-1]))) + else: + result.append(int(value)) + + return result + + +def pack_derivation_path_from_str(path: str) -> bytes: + parsed_path = parse_derivation_path(path) + return pack_derivation_path_from_ints(parsed_path) + + +def pack_derivation_path_from_ints(path: list[int]) -> bytes: + path_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("Bip32Path") + return path_obj.encode(path).data + + +# pylint: disable-next=too-many-locals,too-many-branches,too-many-statements +def sign_tx_review( + client, + device, + navigator, + scenario_navigator, + review_transaction: ReviewTransaction, +): + transaction = review_transaction.transaction + has_command_input = review_transaction.has_command_input + review_custom_screen_text = review_transaction.review_custom_screen_text + + addr_paths_by_indices = transaction.addr_paths_by_indices() + pubkeys_by_indices = {} + for indices, addr_path in addr_paths_by_indices.items(): + pubkey_rapdu = client.get_public_key_by_ints_path(transaction.coin, addr_path) + _, pubkey, _, _ = unpack_get_public_key_response(pubkey_rapdu.data) + + pubkeys_by_indices[indices] = pubkey + + # The snapshot index (used to make its name) and the amount by which it should be increased + # after each step. The increase should be large enough, so that snapshots from later steps + # don't overwrite the previous ones (10 is not enough). + start_idx = 0 + idx_inc = 100 + + if not device.is_nano: + instruction = NavInsID.SWIPE_CENTER_TO_LEFT + else: + instruction = NavInsID.RIGHT_CLICK + + last_page_pattern = r".*\((\d+)/\1\)$" + + for step in client.sign_tx(transaction): + print("step kind: ", step.kind) + if step.kind == "start": + navigator.navigate_and_compare( + path=scenario_navigator.screenshot_path, + test_case_name=scenario_navigator.test_name, + instructions=[instruction], + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + snap_start_idx=start_idx, + ) + start_idx += idx_inc + + if has_command_input: + if device.is_nano: + navigator.navigate_until_text_and_compare( + navigate_instruction=instruction, + validation_instructions=[instruction], + text=last_page_pattern, + path=scenario_navigator.screenshot_path, + test_case_name=scenario_navigator.test_name, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + snap_start_idx=start_idx, + ) + else: + navigator.navigate_and_compare( + path=scenario_navigator.screenshot_path, + test_case_name=scenario_navigator.test_name, + instructions=[instruction] * 2, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + snap_start_idx=start_idx, + ) + start_idx += idx_inc + + if step.kind == "output": + if device.is_nano: + navigator.navigate_until_text_and_compare( + navigate_instruction=instruction, + validation_instructions=[instruction], + text=last_page_pattern, + path=scenario_navigator.screenshot_path, + test_case_name=scenario_navigator.test_name, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + snap_start_idx=start_idx, + ) + else: + navigator.navigate_and_compare( + path=scenario_navigator.screenshot_path, + test_case_name=scenario_navigator.test_name, + instructions=[instruction] * 2, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + snap_start_idx=start_idx, + ) + start_idx += idx_inc + + elif step.kind == "sign": + scenario = NavigationScenarioData( + scenario_navigator.device, + scenario_navigator.backend, + UseCase.TX_REVIEW, + True, + ) + + if device.is_nano: + validation_instructions = scenario.validation + else: + # On touch devices `UseCase.TX_REVIEW` sets `scenario.validation` to + # `[USE_CASE_REVIEW_CONFIRM, USE_CASE_STATUS_DISMISS]`. But the status screen + # appears only after the last `ReturnNextSignature`. + validation_instructions = [NavInsID.USE_CASE_REVIEW_CONFIRM] + + navigator.navigate_until_text_and_compare( + navigate_instruction=scenario.navigation, + validation_instructions=validation_instructions, + text=review_custom_screen_text, + path=scenario_navigator.screenshot_path, + test_case_name=scenario_navigator.test_name, + screen_change_after_last_instruction=False, + snap_start_idx=start_idx, + ) + start_idx += idx_inc + + # After review approval, explicitly request every signature. + signatures = client.get_all_signatures(transaction) + + # The last ReturnNextSignature is what makes the tx Finished, so the "Transaction signed" + # status screen is expected here. + validation_instructions = ( + [] if device.is_nano else [NavInsID.USE_CASE_STATUS_DISMISS] + ) + navigator.navigate_until_text_and_compare( + navigate_instruction=NavInsID.WAIT_FOR_SCREEN_CHANGE, + validation_instructions=validation_instructions, + text=r"^Transaction signed$", + path=scenario_navigator.screenshot_path, + test_case_name=scenario_navigator.test_name, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + snap_start_idx=start_idx, + ) + + sig_indices = {sig.indices() for sig in signatures} + expected_sig_indices = transaction.expected_sig_indices() + assert ( + sig_indices == expected_sig_indices + ), f"Sig indices don't match, expected: {expected_sig_indices}, actual: {sig_indices}" + + addr_paths_by_indices = transaction.addr_paths_by_indices() + + for sig in signatures: + pubkey = pubkeys_by_indices[sig.indices()] + sig_valid = verify_tx_signature(transaction, pubkey, sig.signature) + assert sig_valid, f"Signature verification failed for {sig.indices()}" diff --git a/tests/application_client/mintlayer_response_unpacker.py b/tests/application_client/mintlayer_response_unpacker.py new file mode 100644 index 0000000..2f30f24 --- /dev/null +++ b/tests/application_client/mintlayer_response_unpacker.py @@ -0,0 +1,76 @@ +from struct import unpack +from typing import Tuple + +from .mintlayer_utils import decode_response_variant + + +# remainder, data_len, data +def pop_sized_buf_from_buffer(buffer: bytes, size: int) -> Tuple[bytes, bytes]: + return buffer[size:], buffer[0:size] + + +# remainder, data_len, data +def pop_size_prefixed_buf_from_buf(buffer: bytes) -> Tuple[bytes, int, bytes]: + data_len = buffer[0] + return buffer[1 + data_len :], data_len, buffer[1 : data_len + 1] + + +# Unpack from response: +# response = app_name (var) +def unpack_get_app_name_response(response: bytes) -> str: + return response.decode("ascii") + + +# Unpack from response: +# response = MAJOR (1) +# MINOR (1) +# PATCH (1) +def unpack_get_version_response(response: bytes) -> Tuple[int, int, int]: + assert len(response) == 3 + major, minor, patch = unpack("BBB", response) + return (major, minor, patch) + + +# Unpack from response: +# response = format_id (1) +# app_name_raw_len (1) +# app_name_raw (var) +# version_raw_len (1) +# version_raw (var) +# unused_len (1) +# unused (var) +def unpack_get_app_and_version_response(response: bytes) -> Tuple[str, str]: + response, _ = pop_sized_buf_from_buffer(response, 1) + response, _, app_name_raw = pop_size_prefixed_buf_from_buf(response) + response, _, version_raw = pop_size_prefixed_buf_from_buf(response) + response, _, _ = pop_size_prefixed_buf_from_buf(response) + + assert len(response) == 0 + + return app_name_raw.decode("ascii"), version_raw.decode("ascii") + + +# Unpack from response: +def unpack_get_public_key_response(response: bytes) -> Tuple[int, bytes, int, bytes]: + msg = decode_response_variant(response, "PublicKey") + print(msg) + + pub_key = bytes.fromhex(msg["public_key"][2:]) + pub_key_len = len(pub_key) + chain_code = bytes.fromhex(msg["chain_code"][2:]) + chain_code_len = len(chain_code) + + print(pub_key_len, pub_key) + print(chain_code_len, chain_code) + + assert pub_key_len == 65 + assert chain_code_len == 32 + return pub_key_len, pub_key, chain_code_len, chain_code + + +# Unpack from response: +def unpack_sign_message_response(response: bytes) -> bytes: + resp = decode_response_variant(response, "MessageSignature") + signature = bytes.fromhex(resp["signature"][2:]) + assert len(signature) == 64 + return signature diff --git a/tests/application_client/mintlayer_utils.py b/tests/application_client/mintlayer_utils.py new file mode 100644 index 0000000..ebd1220 --- /dev/null +++ b/tests/application_client/mintlayer_utils.py @@ -0,0 +1,201 @@ +from dataclasses import dataclass +from hashlib import blake2b +from typing import List, Optional + +from coincurve import PublicKeyXOnly +import scalecodec # type: ignore + +sign_tx_start_req_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "SignTxStartReq" +) +sign_tx_next_req_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "SignTxNextReq" +) +compact_u32_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "Compact" +) +tx_input_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("TxInput") +tx_input_commitment_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "SighashInputCommitment" +) +tx_output_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("TxOutput") + + +@dataclass +class TxInputSignatureResponse: + signature: bytes + input_idx: int + multisig_idx: Optional[int] + has_next: bool + + @staticmethod + def from_data(response: bytes): + decoded_response = decode_response_variant(response, "TxInputSignature") + + signature = bytes.fromhex(decoded_response["signature"][2:]) + assert len(signature) == 64 + + return TxInputSignatureResponse( + signature=signature, + input_idx=decoded_response["input_idx"], + multisig_idx=decoded_response["multisig_idx"], + has_next=decoded_response["has_next"], + ) + + +@dataclass(frozen=True) +class TxInputSignatureIndices: + input_idx: int + multisig_idx: Optional[int] + + +@dataclass +class TxInputSignature: + signature: bytes + input_idx: int + multisig_idx: Optional[int] + + @staticmethod + def from_response(response: TxInputSignatureResponse): + return TxInputSignature( + signature=response.signature, + input_idx=response.input_idx, + multisig_idx=response.multisig_idx, + ) + + def indices(self) -> TxInputSignatureIndices: + return TxInputSignatureIndices( + input_idx=self.input_idx, + multisig_idx=self.multisig_idx, + ) + + +@dataclass +class Transaction: + coin: int + # A list of TxInputData objects. + inputs: List[dict] + # A list of TxInputCommitmentData objects. + input_commitments: List[dict] + # A list of TxOutputData objects. + outputs: List[dict] + + # Extract key derivation path for each input_idx/multisig_idx combination + # and return it as a dict. + def addr_paths_by_indices(self) -> dict[TxInputSignatureIndices, list[int]]: + result = {} + for input_idx, inp in enumerate(self.inputs): + for addr in inp["addresses"]: + multisig_idx = addr["multisig_idx"] + indices = TxInputSignatureIndices( + input_idx=input_idx, multisig_idx=multisig_idx + ) + + result[indices] = addr["path"] + + return result + + def expected_sig_indices(self) -> set[TxInputSignatureIndices]: + return set(self.addr_paths_by_indices()) + + def digest_for_signing(self) -> bytes: + encoded_inputs = b"" + for inp_data in self.inputs: + inp_with_info = inp_data["input"] + if "Utxo" in inp_with_info: + inp = {"Utxo": inp_with_info["Utxo"][0]} + elif "Account" in inp_with_info: + inp = {"Account": inp_with_info["Account"]} + elif "AccountCommand" in inp_with_info: + inp = {"AccountCommand": inp_with_info["AccountCommand"]} + elif "OrderAccountCommand" in inp_with_info: + inp = {"OrderAccountCommand": inp_with_info["OrderAccountCommand"][0]} + else: + raise ValueError("Unexpected input") + + encoded_inputs += tx_input_obj.encode(inp).data + + encoded_input_commitments = b"" + for inp_comm in self.input_commitments: + comm = inp_comm["commitment"] + encoded_input_commitments += tx_input_commitment_obj.encode(comm).data + + encoded_outputs = b"" + for outp_data in self.outputs: + outp = outp_data["output"] + encoded_outputs += tx_output_obj.encode(outp).data + + assert len(self.input_commitments) == len(self.inputs) + + num_inputs_as_le_bytes = len(self.inputs).to_bytes(4, "little") + compact_encoded_num_outputs = compact_u32_obj.encode(len(self.outputs)).data + + preimage = ( + b"\x01" # SigHashType::ALL + + b"\x01" # version + + bytes(16) # zero flags + + num_inputs_as_le_bytes + + encoded_inputs + + num_inputs_as_le_bytes + + encoded_input_commitments + + compact_encoded_num_outputs + + encoded_outputs + ) + + tx_hash = mintlayer_hash(mintlayer_hash(preimage)) + return tx_hash + + +def decode_response(response: bytes) -> dict: + response_bytes = scalecodec.base.ScaleBytes(response) + response_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "Response", data=response_bytes + ) + return response_obj.decode() + + +def decode_response_variant(response: bytes, expected_variant: str) -> dict: + decoded_response = decode_response(response) + + assert ( + isinstance(decoded_response, dict) + and len(decoded_response) == 1 + and expected_variant in decoded_response + ), f"Expecting a dict with a single key '{expected_variant}', but got: {decoded_response!r}" + + return decoded_response[expected_variant] + + +def mintlayer_hash(data: bytes) -> bytes: + return blake2b(data, digest_size=64).digest()[:32] + + +MESSAGE_MAGIC_PREFIX = b"===MINTLAYER MESSAGE BEGIN===\n" +MESSAGE_MAGIC_SUFFIX = b"\n===MINTLAYER MESSAGE END===" + + +def mintlayer_message_digest(message: bytes) -> bytes: + framed = MESSAGE_MAGIC_PREFIX + message + MESSAGE_MAGIC_SUFFIX + return mintlayer_hash(mintlayer_hash(framed)) + + +def xonly_pubkey(public_key: bytes) -> bytes: + if len(public_key) == 32: + return public_key + if len(public_key) == 33 and public_key[0] in (0x02, 0x03): + return public_key[1:33] + if len(public_key) == 65 and public_key[0] == 0x04: + return public_key[1:33] + raise ValueError( + "Expected x-only, compressed, or uncompressed secp256k1 public key" + ) + + +def verify_message_signature(msg: bytes, pubkey: bytes, sig: bytes) -> bool: + digest = mintlayer_message_digest(msg) + return PublicKeyXOnly(xonly_pubkey(pubkey)).verify(sig, digest) + + +def verify_tx_signature(tx: Transaction, pubkey: bytes, sig: bytes) -> bool: + digest = tx.digest_for_signing() + return PublicKeyXOnly(xonly_pubkey(pubkey)).verify(sig, digest) diff --git a/tests/application_client/py.typed b/tests/application_client/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0315a04 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +import pytest +from ragger.conftest import configuration +from ragger.navigator import NavInsID + +########################### +### 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 + +configuration.OPTIONAL.CUSTOM_SEED = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + +######################### +### CONFIGURATION END ### +######################### + +# Pull all features from the base ragger conftest using the overridden configuration +pytest_plugins = ("ragger.conftest.base_conftest",) diff --git a/tests/regenerate_snapshots.sh b/tests/regenerate_snapshots.sh new file mode 100755 index 0000000..fce36e4 --- /dev/null +++ b/tests/regenerate_snapshots.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e +set -o nounset + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +SNAPSHOTS_DIR=$SCRIPT_DIR/snapshots +ROOT_DIR=$(cd "$SCRIPT_DIR/.." && pwd) + +MODELS=(nanox nanosp stax flex apex_p) + +cd "$ROOT_DIR" + +echo "*** Removing old snapshots ***" +rm -rf "$SNAPSHOTS_DIR" + +for model in "${MODELS[@]}"; do + if [[ "$model" == "nanosp" ]]; then + TARGET="nanosplus" + else + TARGET="$model" + fi + + # Clean the build directory. Note that most of the time this is redundant, but after + # the app version has been bumped this is actually needed, otherwise the old version + # may be picked up from the existing artifacts of the sdk's build script. + echo "*** Cleaning the build directory for $model ***" + cargo clean --target "$TARGET" + + echo "*** Building the app for $model ***" + cargo ledger build "$TARGET" + + echo "*** Regenerating snapshots for $model ***" + pytest tests/ --tb=short -v --device "$model" --golden_run +done diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..1c7e6e3 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,4 @@ +coincurve>=20.0.0 +pytest +ragger[speculos,ledgerwallet]>=1.21.1 +scalecodec>=1.2.11 diff --git a/tests/setup.cfg b/tests/setup.cfg new file mode 100644 index 0000000..7d0d7e3 --- /dev/null +++ b/tests/setup.cfg @@ -0,0 +1,21 @@ +[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 +max-line-length=100 +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/snapshots/apex_p/test_app_mainmenu/00000.png b/tests/snapshots/apex_p/test_app_mainmenu/00000.png new file mode 100644 index 0000000..5784e46 Binary files /dev/null and b/tests/snapshots/apex_p/test_app_mainmenu/00000.png differ diff --git a/tests/snapshots/apex_p/test_app_mainmenu/00001.png b/tests/snapshots/apex_p/test_app_mainmenu/00001.png new file mode 100644 index 0000000..650255f Binary files /dev/null and b/tests/snapshots/apex_p/test_app_mainmenu/00001.png differ diff --git a/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00000.png b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00000.png new file mode 100644 index 0000000..d405490 Binary files /dev/null and b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00000.png differ diff --git a/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000..0285ec5 Binary files /dev/null and b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00001.png differ diff --git a/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000..6ce358d Binary files /dev/null and b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00002.png differ diff --git a/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00003.png b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00003.png new file mode 100644 index 0000000..5784e46 Binary files /dev/null and b/tests/snapshots/apex_p/test_get_public_key_confirm_accepted/00003.png differ diff --git a/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00000.png b/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00000.png new file mode 100644 index 0000000..d405490 Binary files /dev/null and b/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00000.png differ diff --git a/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000..0285ec5 Binary files /dev/null and b/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00001.png differ diff --git a/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000..0f7f5fe Binary files /dev/null and b/tests/snapshots/apex_p/test_get_public_key_confirm_refused/00002.png differ diff --git a/tests/snapshots/apex_p/test_sign_large_message/00000.png b/tests/snapshots/apex_p/test_sign_large_message/00000.png new file mode 100644 index 0000000..b5959c6 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_large_message/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_large_message/00001.png b/tests/snapshots/apex_p/test_sign_large_message/00001.png new file mode 100644 index 0000000..6c4faa0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_large_message/00001.png differ diff --git a/tests/snapshots/apex_p/test_sign_large_message/00002.png b/tests/snapshots/apex_p/test_sign_large_message/00002.png new file mode 100644 index 0000000..4b0c35c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_large_message/00002.png differ diff --git a/tests/snapshots/apex_p/test_sign_large_message/00003.png b/tests/snapshots/apex_p/test_sign_large_message/00003.png new file mode 100644 index 0000000..857f887 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_large_message/00003.png differ diff --git a/tests/snapshots/apex_p/test_sign_large_message/00004.png b/tests/snapshots/apex_p/test_sign_large_message/00004.png new file mode 100644 index 0000000..d0ceff0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_large_message/00004.png differ diff --git a/tests/snapshots/apex_p/test_sign_large_message/00005.png b/tests/snapshots/apex_p/test_sign_large_message/00005.png new file mode 100644 index 0000000..5784e46 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_large_message/00005.png differ diff --git a/tests/snapshots/apex_p/test_sign_message/00000.png b/tests/snapshots/apex_p/test_sign_message/00000.png new file mode 100644 index 0000000..be2479e Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_message/00001.png b/tests/snapshots/apex_p/test_sign_message/00001.png new file mode 100644 index 0000000..333d923 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message/00001.png differ diff --git a/tests/snapshots/apex_p/test_sign_message/00002.png b/tests/snapshots/apex_p/test_sign_message/00002.png new file mode 100644 index 0000000..fc21acc Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message/00002.png differ diff --git a/tests/snapshots/apex_p/test_sign_message/00003.png b/tests/snapshots/apex_p/test_sign_message/00003.png new file mode 100644 index 0000000..d0ceff0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message/00003.png differ diff --git a/tests/snapshots/apex_p/test_sign_message/00004.png b/tests/snapshots/apex_p/test_sign_message/00004.png new file mode 100644 index 0000000..5784e46 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message/00004.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_pkh/00000.png b/tests/snapshots/apex_p/test_sign_message_pkh/00000.png new file mode 100644 index 0000000..be2479e Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_pkh/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_pkh/00001.png b/tests/snapshots/apex_p/test_sign_message_pkh/00001.png new file mode 100644 index 0000000..a585a87 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_pkh/00001.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_pkh/00002.png b/tests/snapshots/apex_p/test_sign_message_pkh/00002.png new file mode 100644 index 0000000..fc21acc Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_pkh/00002.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_pkh/00003.png b/tests/snapshots/apex_p/test_sign_message_pkh/00003.png new file mode 100644 index 0000000..d0ceff0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_pkh/00003.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_pkh/00004.png b/tests/snapshots/apex_p/test_sign_message_pkh/00004.png new file mode 100644 index 0000000..5784e46 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_pkh/00004.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_refused/00000.png b/tests/snapshots/apex_p/test_sign_message_refused/00000.png new file mode 100644 index 0000000..be2479e Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_refused/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_refused/00001.png b/tests/snapshots/apex_p/test_sign_message_refused/00001.png new file mode 100644 index 0000000..333d923 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_refused/00001.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_refused/00002.png b/tests/snapshots/apex_p/test_sign_message_refused/00002.png new file mode 100644 index 0000000..fc21acc Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_refused/00002.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_refused/00003.png b/tests/snapshots/apex_p/test_sign_message_refused/00003.png new file mode 100644 index 0000000..419b616 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_refused/00003.png differ diff --git a/tests/snapshots/apex_p/test_sign_message_refused/00004.png b/tests/snapshots/apex_p/test_sign_message_refused/00004.png new file mode 100644 index 0000000..f411cb4 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_message_refused/00004.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00000.png b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00100.png b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00100.png new file mode 100644 index 0000000..534147d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00101.png b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00101.png new file mode 100644 index 0000000..534147d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00200.png b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00200.png new file mode 100644 index 0000000..d019bb5 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00201.png b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00201.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00202.png b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00202.png new file mode 100644 index 0000000..3f03cbc Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00300.png b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_authority/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00000.png b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00100.png b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00100.png new file mode 100644 index 0000000..8ba60e7 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00101.png b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00101.png new file mode 100644 index 0000000..8ba60e7 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00200.png b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00200.png new file mode 100644 index 0000000..d019bb5 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00201.png b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00201.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00202.png b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00202.png new file mode 100644 index 0000000..61b4e75 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00300.png b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_change_token_metadata_uri/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_delegation/00000.png b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_delegation/00100.png b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00100.png new file mode 100644 index 0000000..a3a5a41 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_delegation/00101.png b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00101.png new file mode 100644 index 0000000..fd7c3fd Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_delegation/00102.png b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00102.png new file mode 100644 index 0000000..e9c3e68 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_delegation/00200.png b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_delegation/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00000.png b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00100.png b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00100.png new file mode 100644 index 0000000..a94a8b0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00101.png b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00101.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00102.png b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00102.png new file mode 100644 index 0000000..8afd400 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00200.png b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_create_stake_pool/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00000.png b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00100.png b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00100.png new file mode 100644 index 0000000..5a6372a Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00101.png b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00101.png new file mode 100644 index 0000000..8a3ce9b Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00102.png b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00102.png new file mode 100644 index 0000000..f471ae0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00200.png b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_delegate_staking/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00000.png b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00100.png b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00100.png new file mode 100644 index 0000000..cd93b66 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00101.png b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00101.png new file mode 100644 index 0000000..cd93b66 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00200.png b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00200.png new file mode 100644 index 0000000..d019bb5 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00201.png b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00201.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00202.png b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00202.png new file mode 100644 index 0000000..9b7a3e8 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00300.png b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_freeze_tokens/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_htlc/00000.png b/tests/snapshots/apex_p/test_sign_tx_htlc/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_htlc/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_htlc/00100.png b/tests/snapshots/apex_p/test_sign_tx_htlc/00100.png new file mode 100644 index 0000000..c2df644 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_htlc/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_htlc/00101.png b/tests/snapshots/apex_p/test_sign_tx_htlc/00101.png new file mode 100644 index 0000000..c2df644 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_htlc/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_htlc/00200.png b/tests/snapshots/apex_p/test_sign_tx_htlc/00200.png new file mode 100644 index 0000000..e3945ee Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_htlc/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_htlc/00201.png b/tests/snapshots/apex_p/test_sign_tx_htlc/00201.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_htlc/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_htlc/00202.png b/tests/snapshots/apex_p/test_sign_tx_htlc/00202.png new file mode 100644 index 0000000..41f624c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_htlc/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_htlc/00300.png b/tests/snapshots/apex_p/test_sign_tx_htlc/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_htlc/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00000.png b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00100.png b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00100.png new file mode 100644 index 0000000..5b427d9 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00101.png b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00101.png new file mode 100644 index 0000000..fd7c3fd Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00102.png b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00102.png new file mode 100644 index 0000000..039058c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00200.png b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_fungible_token/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_nft/00000.png b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_nft/00100.png b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00100.png new file mode 100644 index 0000000..4172c1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_nft/00101.png b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00101.png new file mode 100644 index 0000000..3eda950 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_nft/00102.png b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00102.png new file mode 100644 index 0000000..6fa38da Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_issue_nft/00200.png b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_issue_nft/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00000.png b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00100.png b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00100.png new file mode 100644 index 0000000..020d4bb Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00101.png b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00101.png new file mode 100644 index 0000000..020d4bb Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00200.png b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00200.png new file mode 100644 index 0000000..bd21edc Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00201.png b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00201.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00202.png b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00202.png new file mode 100644 index 0000000..59494e0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00300.png b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_lock_then_transfer/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00000.png b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00100.png b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00100.png new file mode 100644 index 0000000..64925c0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00101.png b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00101.png new file mode 100644 index 0000000..64925c0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00200.png b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00200.png new file mode 100644 index 0000000..3d58ef7 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00201.png b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00201.png new file mode 100644 index 0000000..5974993 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00202.png b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00202.png new file mode 100644 index 0000000..923e3f2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00300.png b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_mint_tokens/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00000.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00100.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00100.png new file mode 100644 index 0000000..cabc5a0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00101.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00101.png new file mode 100644 index 0000000..cabc5a0 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00200.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00200.png new file mode 100644 index 0000000..d6ba17f Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00201.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00201.png new file mode 100644 index 0000000..d6ba17f Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00300.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00300.png new file mode 100644 index 0000000..1b27f40 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00301.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00301.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00301.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00302.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00302.png new file mode 100644 index 0000000..931b256 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00302.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_conclude/00400.png b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00400.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_conclude/00400.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00000.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00100.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00100.png new file mode 100644 index 0000000..532cadb Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00101.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00101.png new file mode 100644 index 0000000..532cadb Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00200.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00200.png new file mode 100644 index 0000000..e3945ee Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00201.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00201.png new file mode 100644 index 0000000..e3945ee Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00300.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00300.png new file mode 100644 index 0000000..5ced57b Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00301.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00301.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00301.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00302.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00302.png new file mode 100644 index 0000000..700876b Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00302.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_order_fill/00400.png b/tests/snapshots/apex_p/test_sign_tx_order_fill/00400.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_order_fill/00400.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_transfer/00000.png b/tests/snapshots/apex_p/test_sign_tx_transfer/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_transfer/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_transfer/00100.png b/tests/snapshots/apex_p/test_sign_tx_transfer/00100.png new file mode 100644 index 0000000..a7f38f9 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_transfer/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_transfer/00101.png b/tests/snapshots/apex_p/test_sign_tx_transfer/00101.png new file mode 100644 index 0000000..a9958ff Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_transfer/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_transfer/00102.png b/tests/snapshots/apex_p/test_sign_tx_transfer/00102.png new file mode 100644 index 0000000..4b44940 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_transfer/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_transfer/00200.png b/tests/snapshots/apex_p/test_sign_tx_transfer/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_transfer/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00000.png b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00100.png b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00100.png new file mode 100644 index 0000000..5325934 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00101.png b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00101.png new file mode 100644 index 0000000..5325934 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00200.png b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00200.png new file mode 100644 index 0000000..d019bb5 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00201.png b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00201.png new file mode 100644 index 0000000..113692d Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00202.png b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00202.png new file mode 100644 index 0000000..01174e8 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00300.png b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unfreeze_tokens/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00000.png b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00100.png b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00100.png new file mode 100644 index 0000000..f2b28cb Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00101.png b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00101.png new file mode 100644 index 0000000..f2b28cb Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00200.png b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00200.png new file mode 100644 index 0000000..d019bb5 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00201.png b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00201.png new file mode 100644 index 0000000..c74205e Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00201.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00202.png b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00202.png new file mode 100644 index 0000000..f737ad3 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00202.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00300.png b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_unmint_tokens/00300.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00000.png b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00100.png b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00100.png new file mode 100644 index 0000000..886bbaf Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00101.png b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00101.png new file mode 100644 index 0000000..cd92a0f Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00102.png b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00102.png new file mode 100644 index 0000000..4b44940 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00200.png b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_input_and_commitment/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_output/00000.png b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_output/00100.png b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00100.png new file mode 100644 index 0000000..4172c1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_output/00101.png b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00101.png new file mode 100644 index 0000000..3eda950 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_output/00102.png b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00102.png new file mode 100644 index 0000000..6fa38da Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00102.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_with_large_output/00200.png b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00200.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_with_large_output/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_without_outputs/00000.png b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00000.png new file mode 100644 index 0000000..f89ff1c Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00000.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_without_outputs/00100.png b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00100.png new file mode 100644 index 0000000..cd93b66 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00100.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_without_outputs/00101.png b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00101.png new file mode 100644 index 0000000..cd93b66 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00101.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_without_outputs/00200.png b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00200.png new file mode 100644 index 0000000..9b7a3e8 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00200.png differ diff --git a/tests/snapshots/apex_p/test_sign_tx_without_outputs/00300.png b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00300.png new file mode 100644 index 0000000..4aba0a2 Binary files /dev/null and b/tests/snapshots/apex_p/test_sign_tx_without_outputs/00300.png differ diff --git a/tests/snapshots/flex/test_app_mainmenu/00000.png b/tests/snapshots/flex/test_app_mainmenu/00000.png new file mode 100644 index 0000000..f2fa972 Binary files /dev/null and b/tests/snapshots/flex/test_app_mainmenu/00000.png differ diff --git a/tests/snapshots/flex/test_app_mainmenu/00001.png b/tests/snapshots/flex/test_app_mainmenu/00001.png new file mode 100644 index 0000000..a628159 Binary files /dev/null and b/tests/snapshots/flex/test_app_mainmenu/00001.png differ diff --git a/tests/snapshots/flex/test_get_public_key_confirm_accepted/00000.png b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00000.png new file mode 100644 index 0000000..22e90c6 Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00000.png differ diff --git a/tests/snapshots/flex/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000..cf019b4 Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00001.png differ diff --git a/tests/snapshots/flex/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000..8d07f2f Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00002.png differ diff --git a/tests/snapshots/flex/test_get_public_key_confirm_accepted/00003.png b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00003.png new file mode 100644 index 0000000..f2fa972 Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_accepted/00003.png differ diff --git a/tests/snapshots/flex/test_get_public_key_confirm_refused/00000.png b/tests/snapshots/flex/test_get_public_key_confirm_refused/00000.png new file mode 100644 index 0000000..22e90c6 Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_refused/00000.png differ diff --git a/tests/snapshots/flex/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/flex/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000..cf019b4 Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_refused/00001.png differ diff --git a/tests/snapshots/flex/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/flex/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000..7c8c42e Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_refused/00002.png differ diff --git a/tests/snapshots/flex/test_sign_large_message/00000.png b/tests/snapshots/flex/test_sign_large_message/00000.png new file mode 100644 index 0000000..44e3d67 Binary files /dev/null and b/tests/snapshots/flex/test_sign_large_message/00000.png differ diff --git a/tests/snapshots/flex/test_sign_large_message/00001.png b/tests/snapshots/flex/test_sign_large_message/00001.png new file mode 100644 index 0000000..571fe59 Binary files /dev/null and b/tests/snapshots/flex/test_sign_large_message/00001.png differ diff --git a/tests/snapshots/flex/test_sign_large_message/00002.png b/tests/snapshots/flex/test_sign_large_message/00002.png new file mode 100644 index 0000000..64ddce2 Binary files /dev/null and b/tests/snapshots/flex/test_sign_large_message/00002.png differ diff --git a/tests/snapshots/flex/test_sign_large_message/00003.png b/tests/snapshots/flex/test_sign_large_message/00003.png new file mode 100644 index 0000000..2675d2d Binary files /dev/null and b/tests/snapshots/flex/test_sign_large_message/00003.png differ diff --git a/tests/snapshots/flex/test_sign_large_message/00004.png b/tests/snapshots/flex/test_sign_large_message/00004.png new file mode 100644 index 0000000..f8e68c8 Binary files /dev/null and b/tests/snapshots/flex/test_sign_large_message/00004.png differ diff --git a/tests/snapshots/flex/test_sign_large_message/00005.png b/tests/snapshots/flex/test_sign_large_message/00005.png new file mode 100644 index 0000000..f2fa972 Binary files /dev/null and b/tests/snapshots/flex/test_sign_large_message/00005.png differ diff --git a/tests/snapshots/flex/test_sign_message/00000.png b/tests/snapshots/flex/test_sign_message/00000.png new file mode 100644 index 0000000..a2a9b36 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message/00000.png differ diff --git a/tests/snapshots/flex/test_sign_message/00001.png b/tests/snapshots/flex/test_sign_message/00001.png new file mode 100644 index 0000000..9abda3f Binary files /dev/null and b/tests/snapshots/flex/test_sign_message/00001.png differ diff --git a/tests/snapshots/flex/test_sign_message/00002.png b/tests/snapshots/flex/test_sign_message/00002.png new file mode 100644 index 0000000..3849275 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message/00002.png differ diff --git a/tests/snapshots/flex/test_sign_message/00003.png b/tests/snapshots/flex/test_sign_message/00003.png new file mode 100644 index 0000000..f8e68c8 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message/00003.png differ diff --git a/tests/snapshots/flex/test_sign_message/00004.png b/tests/snapshots/flex/test_sign_message/00004.png new file mode 100644 index 0000000..f2fa972 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message/00004.png differ diff --git a/tests/snapshots/flex/test_sign_message_pkh/00000.png b/tests/snapshots/flex/test_sign_message_pkh/00000.png new file mode 100644 index 0000000..a2a9b36 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_pkh/00000.png differ diff --git a/tests/snapshots/flex/test_sign_message_pkh/00001.png b/tests/snapshots/flex/test_sign_message_pkh/00001.png new file mode 100644 index 0000000..d63bb87 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_pkh/00001.png differ diff --git a/tests/snapshots/flex/test_sign_message_pkh/00002.png b/tests/snapshots/flex/test_sign_message_pkh/00002.png new file mode 100644 index 0000000..3849275 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_pkh/00002.png differ diff --git a/tests/snapshots/flex/test_sign_message_pkh/00003.png b/tests/snapshots/flex/test_sign_message_pkh/00003.png new file mode 100644 index 0000000..f8e68c8 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_pkh/00003.png differ diff --git a/tests/snapshots/flex/test_sign_message_pkh/00004.png b/tests/snapshots/flex/test_sign_message_pkh/00004.png new file mode 100644 index 0000000..f2fa972 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_pkh/00004.png differ diff --git a/tests/snapshots/flex/test_sign_message_refused/00000.png b/tests/snapshots/flex/test_sign_message_refused/00000.png new file mode 100644 index 0000000..a2a9b36 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_refused/00000.png differ diff --git a/tests/snapshots/flex/test_sign_message_refused/00001.png b/tests/snapshots/flex/test_sign_message_refused/00001.png new file mode 100644 index 0000000..9abda3f Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_refused/00001.png differ diff --git a/tests/snapshots/flex/test_sign_message_refused/00002.png b/tests/snapshots/flex/test_sign_message_refused/00002.png new file mode 100644 index 0000000..3849275 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_refused/00002.png differ diff --git a/tests/snapshots/flex/test_sign_message_refused/00003.png b/tests/snapshots/flex/test_sign_message_refused/00003.png new file mode 100644 index 0000000..19e8366 Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_refused/00003.png differ diff --git a/tests/snapshots/flex/test_sign_message_refused/00004.png b/tests/snapshots/flex/test_sign_message_refused/00004.png new file mode 100644 index 0000000..40eeaba Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_refused/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00000.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00100.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00100.png new file mode 100644 index 0000000..31ddc9e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00101.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00101.png new file mode 100644 index 0000000..31ddc9e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00200.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00200.png new file mode 100644 index 0000000..ada16a3 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00201.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00201.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00202.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00202.png new file mode 100644 index 0000000..68f59e1 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00300.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00000.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00100.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00100.png new file mode 100644 index 0000000..51ab425 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00101.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00101.png new file mode 100644 index 0000000..51ab425 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00200.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00200.png new file mode 100644 index 0000000..ada16a3 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00201.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00201.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00202.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00202.png new file mode 100644 index 0000000..bcf4b40 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00300.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00000.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00100.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00100.png new file mode 100644 index 0000000..428cd7e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00101.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00101.png new file mode 100644 index 0000000..bdfc1af Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00102.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00102.png new file mode 100644 index 0000000..48c4ef2 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00200.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00000.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00100.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00100.png new file mode 100644 index 0000000..d5b69b4 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00101.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00101.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00102.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00102.png new file mode 100644 index 0000000..c6b89cb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00200.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegate_staking/00000.png b/tests/snapshots/flex/test_sign_tx_delegate_staking/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegate_staking/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegate_staking/00100.png b/tests/snapshots/flex/test_sign_tx_delegate_staking/00100.png new file mode 100644 index 0000000..d3234b0 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegate_staking/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegate_staking/00101.png b/tests/snapshots/flex/test_sign_tx_delegate_staking/00101.png new file mode 100644 index 0000000..51e3005 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegate_staking/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegate_staking/00102.png b/tests/snapshots/flex/test_sign_tx_delegate_staking/00102.png new file mode 100644 index 0000000..824c2d0 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegate_staking/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegate_staking/00200.png b/tests/snapshots/flex/test_sign_tx_delegate_staking/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegate_staking/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00000.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00100.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00100.png new file mode 100644 index 0000000..f156168 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00101.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00101.png new file mode 100644 index 0000000..f156168 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00200.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00200.png new file mode 100644 index 0000000..ada16a3 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00201.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00201.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00202.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00202.png new file mode 100644 index 0000000..aa0f0aa Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00300.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00000.png b/tests/snapshots/flex/test_sign_tx_htlc/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00100.png b/tests/snapshots/flex/test_sign_tx_htlc/00100.png new file mode 100644 index 0000000..26ad42d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00101.png b/tests/snapshots/flex/test_sign_tx_htlc/00101.png new file mode 100644 index 0000000..26ad42d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00200.png b/tests/snapshots/flex/test_sign_tx_htlc/00200.png new file mode 100644 index 0000000..72c7dec Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00201.png b/tests/snapshots/flex/test_sign_tx_htlc/00201.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00202.png b/tests/snapshots/flex/test_sign_tx_htlc/00202.png new file mode 100644 index 0000000..77f2381 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00300.png b/tests/snapshots/flex/test_sign_tx_htlc/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00000.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00100.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00100.png new file mode 100644 index 0000000..959e4d1 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00101.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00101.png new file mode 100644 index 0000000..bdfc1af Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00102.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00102.png new file mode 100644 index 0000000..55dce88 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00200.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00000.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00100.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00100.png new file mode 100644 index 0000000..49a16a2 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00101.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00101.png new file mode 100644 index 0000000..c77916a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00102.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00102.png new file mode 100644 index 0000000..b1edb3e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00200.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00000.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00100.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00100.png new file mode 100644 index 0000000..bd5df4f Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00101.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00101.png new file mode 100644 index 0000000..bd5df4f Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00200.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00200.png new file mode 100644 index 0000000..ea07f63 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00201.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00201.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00202.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00202.png new file mode 100644 index 0000000..c8eb2cf Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00300.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00000.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00100.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00100.png new file mode 100644 index 0000000..16bf59a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00101.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00101.png new file mode 100644 index 0000000..16bf59a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00200.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00200.png new file mode 100644 index 0000000..7f084db Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00201.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00201.png new file mode 100644 index 0000000..9882d41 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00202.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00202.png new file mode 100644 index 0000000..ad35bf1 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00300.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00000.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00100.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00100.png new file mode 100644 index 0000000..29b3fad Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00101.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00101.png new file mode 100644 index 0000000..29b3fad Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00200.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00200.png new file mode 100644 index 0000000..eddbba3 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00201.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00201.png new file mode 100644 index 0000000..eddbba3 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00300.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00300.png new file mode 100644 index 0000000..a05b1c4 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00301.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00301.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00301.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00302.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00302.png new file mode 100644 index 0000000..f4f3a10 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00302.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00400.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00400.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00400.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00000.png b/tests/snapshots/flex/test_sign_tx_order_fill/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00100.png b/tests/snapshots/flex/test_sign_tx_order_fill/00100.png new file mode 100644 index 0000000..3b5a1b7 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00101.png b/tests/snapshots/flex/test_sign_tx_order_fill/00101.png new file mode 100644 index 0000000..3b5a1b7 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00200.png b/tests/snapshots/flex/test_sign_tx_order_fill/00200.png new file mode 100644 index 0000000..72c7dec Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00201.png b/tests/snapshots/flex/test_sign_tx_order_fill/00201.png new file mode 100644 index 0000000..72c7dec Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00300.png b/tests/snapshots/flex/test_sign_tx_order_fill/00300.png new file mode 100644 index 0000000..e312050 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00301.png b/tests/snapshots/flex/test_sign_tx_order_fill/00301.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00301.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00302.png b/tests/snapshots/flex/test_sign_tx_order_fill/00302.png new file mode 100644 index 0000000..5b7bcb1 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00302.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00400.png b/tests/snapshots/flex/test_sign_tx_order_fill/00400.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00400.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00000.png b/tests/snapshots/flex/test_sign_tx_transfer/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00100.png b/tests/snapshots/flex/test_sign_tx_transfer/00100.png new file mode 100644 index 0000000..a6e4d55 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00101.png b/tests/snapshots/flex/test_sign_tx_transfer/00101.png new file mode 100644 index 0000000..67a9b5a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00102.png b/tests/snapshots/flex/test_sign_tx_transfer/00102.png new file mode 100644 index 0000000..77025cd Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00200.png b/tests/snapshots/flex/test_sign_tx_transfer/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00000.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00100.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00100.png new file mode 100644 index 0000000..92acded Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00101.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00101.png new file mode 100644 index 0000000..92acded Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00200.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00200.png new file mode 100644 index 0000000..ada16a3 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00201.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00201.png new file mode 100644 index 0000000..8a8445e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00202.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00202.png new file mode 100644 index 0000000..5a46298 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00300.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00000.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00100.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00100.png new file mode 100644 index 0000000..a0aa228 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00101.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00101.png new file mode 100644 index 0000000..a0aa228 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00200.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00200.png new file mode 100644 index 0000000..ada16a3 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00201.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00201.png new file mode 100644 index 0000000..bf6752a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00201.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00202.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00202.png new file mode 100644 index 0000000..62478ed Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00202.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00300.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00300.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00000.png b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00100.png b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00100.png new file mode 100644 index 0000000..b8e8fc2 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00101.png b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00101.png new file mode 100644 index 0000000..5880f40 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00102.png b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00102.png new file mode 100644 index 0000000..77025cd Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00200.png b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_input_and_commitment/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_output/00000.png b/tests/snapshots/flex/test_sign_tx_with_large_output/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_output/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_output/00100.png b/tests/snapshots/flex/test_sign_tx_with_large_output/00100.png new file mode 100644 index 0000000..49a16a2 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_output/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_output/00101.png b/tests/snapshots/flex/test_sign_tx_with_large_output/00101.png new file mode 100644 index 0000000..c77916a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_output/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_output/00102.png b/tests/snapshots/flex/test_sign_tx_with_large_output/00102.png new file mode 100644 index 0000000..b1edb3e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_output/00102.png differ diff --git a/tests/snapshots/flex/test_sign_tx_with_large_output/00200.png b/tests/snapshots/flex/test_sign_tx_with_large_output/00200.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_with_large_output/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_without_outputs/00000.png b/tests/snapshots/flex/test_sign_tx_without_outputs/00000.png new file mode 100644 index 0000000..5d42a9a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_without_outputs/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_without_outputs/00100.png b/tests/snapshots/flex/test_sign_tx_without_outputs/00100.png new file mode 100644 index 0000000..f156168 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_without_outputs/00100.png differ diff --git a/tests/snapshots/flex/test_sign_tx_without_outputs/00101.png b/tests/snapshots/flex/test_sign_tx_without_outputs/00101.png new file mode 100644 index 0000000..f156168 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_without_outputs/00101.png differ diff --git a/tests/snapshots/flex/test_sign_tx_without_outputs/00200.png b/tests/snapshots/flex/test_sign_tx_without_outputs/00200.png new file mode 100644 index 0000000..aa0f0aa Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_without_outputs/00200.png differ diff --git a/tests/snapshots/flex/test_sign_tx_without_outputs/00300.png b/tests/snapshots/flex/test_sign_tx_without_outputs/00300.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_without_outputs/00300.png differ diff --git a/tests/snapshots/nanosp/test_app_mainmenu/00000.png b/tests/snapshots/nanosp/test_app_mainmenu/00000.png new file mode 100644 index 0000000..4073303 Binary files /dev/null and b/tests/snapshots/nanosp/test_app_mainmenu/00000.png differ diff --git a/tests/snapshots/nanosp/test_app_mainmenu/00001.png b/tests/snapshots/nanosp/test_app_mainmenu/00001.png new file mode 100644 index 0000000..77bcafd Binary files /dev/null and b/tests/snapshots/nanosp/test_app_mainmenu/00001.png differ diff --git a/tests/snapshots/nanosp/test_app_mainmenu/00002.png b/tests/snapshots/nanosp/test_app_mainmenu/00002.png new file mode 100644 index 0000000..f265d69 Binary files /dev/null and b/tests/snapshots/nanosp/test_app_mainmenu/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00000.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00000.png new file mode 100644 index 0000000..e2ead08 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00003.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00003.png new file mode 100644 index 0000000..7dfa0c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00004.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00004.png new file mode 100644 index 0000000..84caa59 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00000.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00000.png new file mode 100644 index 0000000..e2ead08 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00000.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00001.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00002.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00003.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00003.png new file mode 100644 index 0000000..7dfa0c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00003.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00004.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00004.png new file mode 100644 index 0000000..7b38f99 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00004.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00005.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00005.png new file mode 100644 index 0000000..03e5ee2 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00000.png b/tests/snapshots/nanosp/test_sign_large_message/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00001.png b/tests/snapshots/nanosp/test_sign_large_message/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00002.png b/tests/snapshots/nanosp/test_sign_large_message/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00003.png b/tests/snapshots/nanosp/test_sign_large_message/00003.png new file mode 100644 index 0000000..7802e92 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00004.png b/tests/snapshots/nanosp/test_sign_large_message/00004.png new file mode 100644 index 0000000..40bdb70 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00005.png b/tests/snapshots/nanosp/test_sign_large_message/00005.png new file mode 100644 index 0000000..850225e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00006.png b/tests/snapshots/nanosp/test_sign_large_message/00006.png new file mode 100644 index 0000000..cec141b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00007.png b/tests/snapshots/nanosp/test_sign_large_message/00007.png new file mode 100644 index 0000000..36eb31d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00008.png b/tests/snapshots/nanosp/test_sign_large_message/00008.png new file mode 100644 index 0000000..df753a1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00009.png b/tests/snapshots/nanosp/test_sign_large_message/00009.png new file mode 100644 index 0000000..d5b841e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00009.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00010.png b/tests/snapshots/nanosp/test_sign_large_message/00010.png new file mode 100644 index 0000000..f5e85c3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00010.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00011.png b/tests/snapshots/nanosp/test_sign_large_message/00011.png new file mode 100644 index 0000000..d22ac2d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00011.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00012.png b/tests/snapshots/nanosp/test_sign_large_message/00012.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00012.png differ diff --git a/tests/snapshots/nanosp/test_sign_large_message/00013.png b/tests/snapshots/nanosp/test_sign_large_message/00013.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_large_message/00013.png differ diff --git a/tests/snapshots/nanosp/test_sign_message/00000.png b/tests/snapshots/nanosp/test_sign_message/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message/00001.png b/tests/snapshots/nanosp/test_sign_message/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message/00002.png b/tests/snapshots/nanosp/test_sign_message/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message/00003.png b/tests/snapshots/nanosp/test_sign_message/00003.png new file mode 100644 index 0000000..3617b8d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message/00004.png b/tests/snapshots/nanosp/test_sign_message/00004.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_message/00005.png b/tests/snapshots/nanosp/test_sign_message/00005.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_pkh/00000.png b/tests/snapshots/nanosp/test_sign_message_pkh/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_pkh/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_pkh/00001.png b/tests/snapshots/nanosp/test_sign_message_pkh/00001.png new file mode 100644 index 0000000..208f5d8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_pkh/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_pkh/00002.png b/tests/snapshots/nanosp/test_sign_message_pkh/00002.png new file mode 100644 index 0000000..3617b8d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_pkh/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_pkh/00003.png b/tests/snapshots/nanosp/test_sign_message_pkh/00003.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_pkh/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_pkh/00004.png b/tests/snapshots/nanosp/test_sign_message_pkh/00004.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_pkh/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00000.png b/tests/snapshots/nanosp/test_sign_message_refused/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00001.png b/tests/snapshots/nanosp/test_sign_message_refused/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00002.png b/tests/snapshots/nanosp/test_sign_message_refused/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00003.png b/tests/snapshots/nanosp/test_sign_message_refused/00003.png new file mode 100644 index 0000000..3617b8d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00004.png b/tests/snapshots/nanosp/test_sign_message_refused/00004.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00005.png b/tests/snapshots/nanosp/test_sign_message_refused/00005.png new file mode 100644 index 0000000..12caa58 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00006.png b/tests/snapshots/nanosp/test_sign_message_refused/00006.png new file mode 100644 index 0000000..d917d19 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00000.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00100.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00100.png new file mode 100644 index 0000000..15fea9d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00101.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00101.png new file mode 100644 index 0000000..bc691a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00102.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00102.png new file mode 100644 index 0000000..28ee9c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00103.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00103.png new file mode 100644 index 0000000..9832913 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00200.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00201.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00202.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00203.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00204.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00205.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00205.png new file mode 100644 index 0000000..c81329e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00300.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00000.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00100.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00100.png new file mode 100644 index 0000000..196af3c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00101.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00101.png new file mode 100644 index 0000000..08f05af Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00102.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00102.png new file mode 100644 index 0000000..5057eb0 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00200.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00201.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00202.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00203.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00204.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00205.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00205.png new file mode 100644 index 0000000..d3f5cd6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00300.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00000.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00100.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00100.png new file mode 100644 index 0000000..7c23f06 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00101.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00101.png new file mode 100644 index 0000000..59d0e80 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00102.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00102.png new file mode 100644 index 0000000..3d01046 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00103.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00103.png new file mode 100644 index 0000000..f62cce2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00104.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00104.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00105.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00105.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00106.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00106.png new file mode 100644 index 0000000..17cf916 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00106.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00200.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00000.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00100.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00100.png new file mode 100644 index 0000000..ad24691 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00101.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00101.png new file mode 100644 index 0000000..bd7a27e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00102.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00102.png new file mode 100644 index 0000000..cb8a713 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00103.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00103.png new file mode 100644 index 0000000..af79c8c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00104.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00104.png new file mode 100644 index 0000000..38c565f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00105.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00105.png new file mode 100644 index 0000000..bd5c339 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00106.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00106.png new file mode 100644 index 0000000..b1cd9fe Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00106.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00107.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00107.png new file mode 100644 index 0000000..e35ffb0 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00107.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00108.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00108.png new file mode 100644 index 0000000..42713e3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00108.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00109.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00109.png new file mode 100644 index 0000000..5418fd7 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00109.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00110.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00110.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00110.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00111.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00111.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00111.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00112.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00112.png new file mode 100644 index 0000000..b3afad9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00112.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00200.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00000.png b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00100.png b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00100.png new file mode 100644 index 0000000..15001c0 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00101.png b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00101.png new file mode 100644 index 0000000..722145c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00102.png b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00102.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00103.png b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00103.png new file mode 100644 index 0000000..0623b1b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00104.png b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00104.png new file mode 100644 index 0000000..e0ad1f1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00200.png b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegate_staking/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00000.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00100.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00100.png new file mode 100644 index 0000000..eaa64e5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00101.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00101.png new file mode 100644 index 0000000..351a884 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00200.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00201.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00202.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00203.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00204.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00205.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00205.png new file mode 100644 index 0000000..567675f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00300.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00000.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00100.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00100.png new file mode 100644 index 0000000..40b1181 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00101.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00101.png new file mode 100644 index 0000000..f113d41 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00102.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00102.png new file mode 100644 index 0000000..195e6f3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00103.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00103.png new file mode 100644 index 0000000..778f66f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00104.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00104.png new file mode 100644 index 0000000..0d5d509 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00105.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00105.png new file mode 100644 index 0000000..9949b8d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00200.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00201.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00202.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00202.png new file mode 100644 index 0000000..607670c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00203.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00204.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00205.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00205.png new file mode 100644 index 0000000..32f349e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00300.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00000.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00100.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00100.png new file mode 100644 index 0000000..7c86f79 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00101.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00101.png new file mode 100644 index 0000000..7f8fb9a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00102.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00102.png new file mode 100644 index 0000000..7cd6829 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00103.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00103.png new file mode 100644 index 0000000..77eedd9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00104.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00104.png new file mode 100644 index 0000000..f589bbf Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00105.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00105.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00106.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00106.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00106.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00107.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00107.png new file mode 100644 index 0000000..caaaca0 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00107.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00200.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00000.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00100.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00100.png new file mode 100644 index 0000000..8b7b71f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00101.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00101.png new file mode 100644 index 0000000..9d40b0b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00102.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00102.png new file mode 100644 index 0000000..64c4618 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00103.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00103.png new file mode 100644 index 0000000..2400405 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00104.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00104.png new file mode 100644 index 0000000..2f750b3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00105.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00105.png new file mode 100644 index 0000000..e3ef4c1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00106.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00106.png new file mode 100644 index 0000000..bc9c4a5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00106.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00107.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00107.png new file mode 100644 index 0000000..77dec68 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00107.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00108.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00108.png new file mode 100644 index 0000000..6d7bef4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00108.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00109.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00109.png new file mode 100644 index 0000000..8bf8d9b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00109.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00110.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00110.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00110.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00111.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00111.png new file mode 100644 index 0000000..af149c9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00111.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00112.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00112.png new file mode 100644 index 0000000..8a6f6ed Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00112.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00200.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00000.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00100.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00100.png new file mode 100644 index 0000000..216a4ef Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00101.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00101.png new file mode 100644 index 0000000..bcebde4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00102.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00102.png new file mode 100644 index 0000000..58a376f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00200.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00201.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00201.png new file mode 100644 index 0000000..7a9b805 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00202.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00202.png new file mode 100644 index 0000000..342974f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00203.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00203.png new file mode 100644 index 0000000..53e8899 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00204.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00204.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00205.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00205.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00206.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00206.png new file mode 100644 index 0000000..f7d545b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00206.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00300.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00000.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00100.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00100.png new file mode 100644 index 0000000..27249d7 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00101.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00101.png new file mode 100644 index 0000000..7199003 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00200.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00201.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00201.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00202.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00202.png new file mode 100644 index 0000000..b1ff363 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00203.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00203.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00204.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00204.png new file mode 100644 index 0000000..1a58b12 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00205.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00205.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00206.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00206.png new file mode 100644 index 0000000..eeaad30 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00206.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00207.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00207.png new file mode 100644 index 0000000..f2eaa31 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00207.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00300.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00000.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00100.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00100.png new file mode 100644 index 0000000..05efc73 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00101.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00101.png new file mode 100644 index 0000000..7060383 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00200.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00201.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00202.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00202.png new file mode 100644 index 0000000..d596bdf Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00300.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00300.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00301.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00301.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00301.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00302.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00302.png new file mode 100644 index 0000000..e820252 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00302.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00303.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00303.png new file mode 100644 index 0000000..376ff24 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00303.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00304.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00304.png new file mode 100644 index 0000000..3d1d652 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00304.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00305.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00305.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00305.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00306.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00306.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00306.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00307.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00307.png new file mode 100644 index 0000000..1611a57 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00307.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00400.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00400.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00400.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00000.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00100.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00100.png new file mode 100644 index 0000000..a6a6827 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00101.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00101.png new file mode 100644 index 0000000..315c395 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00102.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00102.png new file mode 100644 index 0000000..45ccfa9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00200.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00201.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00202.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00202.png new file mode 100644 index 0000000..607670c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00300.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00300.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00301.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00301.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00301.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00302.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00302.png new file mode 100644 index 0000000..d48537e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00302.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00303.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00303.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00303.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00304.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00304.png new file mode 100644 index 0000000..1a58b12 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00304.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00305.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00305.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00305.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00306.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00306.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00306.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00307.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00307.png new file mode 100644 index 0000000..6e341c1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00307.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00400.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00400.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00400.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00000.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00100.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00100.png new file mode 100644 index 0000000..e2e6efc Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00101.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00101.png new file mode 100644 index 0000000..75337cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00102.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00102.png new file mode 100644 index 0000000..c759d9b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00103.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00103.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00104.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00104.png new file mode 100644 index 0000000..639af51 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00105.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00105.png new file mode 100644 index 0000000..7898c99 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00200.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00000.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00100.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00100.png new file mode 100644 index 0000000..6336f24 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00101.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00101.png new file mode 100644 index 0000000..1853ea2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00200.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00201.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00202.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00203.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00204.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00205.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00205.png new file mode 100644 index 0000000..ddae9bc Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00300.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00000.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00100.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00100.png new file mode 100644 index 0000000..57c605e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00101.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00101.png new file mode 100644 index 0000000..129fb85 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00200.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00201.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00202.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00203.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00203.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00204.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00204.png new file mode 100644 index 0000000..10fa236 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00204.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00205.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00205.png new file mode 100644 index 0000000..8ed052d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00205.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00206.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00206.png new file mode 100644 index 0000000..39ef4a2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00206.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00300.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00300.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00000.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00100.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00100.png new file mode 100644 index 0000000..e49b5a9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00101.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00101.png new file mode 100644 index 0000000..eeca286 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00102.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00102.png new file mode 100644 index 0000000..d3d4399 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00103.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00103.png new file mode 100644 index 0000000..e4ce494 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00104.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00104.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00105.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00105.png new file mode 100644 index 0000000..ca25279 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00106.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00106.png new file mode 100644 index 0000000..7898c99 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00106.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00200.png b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_input_and_commitment/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00000.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00100.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00100.png new file mode 100644 index 0000000..8eb7eca Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00101.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00101.png new file mode 100644 index 0000000..94cbe16 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00102.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00102.png new file mode 100644 index 0000000..57f8edc Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00102.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00103.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00103.png new file mode 100644 index 0000000..adb48cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00103.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00104.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00104.png new file mode 100644 index 0000000..bac888b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00104.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00105.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00105.png new file mode 100644 index 0000000..ff2c2a8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00105.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00106.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00106.png new file mode 100644 index 0000000..9f6dc8d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00106.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00107.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00107.png new file mode 100644 index 0000000..8a490a4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00107.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00108.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00108.png new file mode 100644 index 0000000..18123a5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00108.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00109.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00109.png new file mode 100644 index 0000000..615928e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00109.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00110.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00110.png new file mode 100644 index 0000000..214e45d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00110.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00111.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00111.png new file mode 100644 index 0000000..1da0b67 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00111.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00112.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00112.png new file mode 100644 index 0000000..43ae4fb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00112.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00113.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00113.png new file mode 100644 index 0000000..06534a7 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00113.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00114.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00114.png new file mode 100644 index 0000000..3cfd3b1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00114.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00115.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00115.png new file mode 100644 index 0000000..93af868 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00115.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00116.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00116.png new file mode 100644 index 0000000..f4ec1f4 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00116.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00117.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00117.png new file mode 100644 index 0000000..3f4b57f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00117.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00118.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00118.png new file mode 100644 index 0000000..175dcbd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00118.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00119.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00119.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00119.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00120.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00120.png new file mode 100644 index 0000000..af149c9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00120.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00121.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00121.png new file mode 100644 index 0000000..8a6f6ed Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00121.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_with_large_output/00200.png b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_with_large_output/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_without_outputs/00000.png b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_without_outputs/00100.png b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00100.png new file mode 100644 index 0000000..eaa64e5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00100.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_without_outputs/00101.png b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00101.png new file mode 100644 index 0000000..351a884 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00101.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_without_outputs/00200.png b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00200.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_without_outputs/00201.png b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00201.png new file mode 100644 index 0000000..eeaad30 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00201.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_without_outputs/00202.png b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00202.png new file mode 100644 index 0000000..567675f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00202.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_without_outputs/00300.png b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_without_outputs/00300.png differ diff --git a/tests/snapshots/nanox/test_app_mainmenu/00000.png b/tests/snapshots/nanox/test_app_mainmenu/00000.png new file mode 100644 index 0000000..4073303 Binary files /dev/null and b/tests/snapshots/nanox/test_app_mainmenu/00000.png differ diff --git a/tests/snapshots/nanox/test_app_mainmenu/00001.png b/tests/snapshots/nanox/test_app_mainmenu/00001.png new file mode 100644 index 0000000..77bcafd Binary files /dev/null and b/tests/snapshots/nanox/test_app_mainmenu/00001.png differ diff --git a/tests/snapshots/nanox/test_app_mainmenu/00002.png b/tests/snapshots/nanox/test_app_mainmenu/00002.png new file mode 100644 index 0000000..f265d69 Binary files /dev/null and b/tests/snapshots/nanox/test_app_mainmenu/00002.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00000.png b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00000.png new file mode 100644 index 0000000..e2ead08 Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00000.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00001.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00002.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00003.png b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00003.png new file mode 100644 index 0000000..7dfa0c5 Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00003.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00004.png b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00004.png new file mode 100644 index 0000000..84caa59 Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_accepted/00004.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00000.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00000.png new file mode 100644 index 0000000..e2ead08 Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00000.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00001.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00002.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00003.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00003.png new file mode 100644 index 0000000..7dfa0c5 Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00003.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00004.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00004.png new file mode 100644 index 0000000..7b38f99 Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00004.png differ diff --git a/tests/snapshots/nanox/test_get_public_key_confirm_refused/00005.png b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00005.png new file mode 100644 index 0000000..03e5ee2 Binary files /dev/null and b/tests/snapshots/nanox/test_get_public_key_confirm_refused/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00000.png b/tests/snapshots/nanox/test_sign_large_message/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00001.png b/tests/snapshots/nanox/test_sign_large_message/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00002.png b/tests/snapshots/nanox/test_sign_large_message/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00003.png b/tests/snapshots/nanox/test_sign_large_message/00003.png new file mode 100644 index 0000000..7802e92 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00004.png b/tests/snapshots/nanox/test_sign_large_message/00004.png new file mode 100644 index 0000000..40bdb70 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00005.png b/tests/snapshots/nanox/test_sign_large_message/00005.png new file mode 100644 index 0000000..850225e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00006.png b/tests/snapshots/nanox/test_sign_large_message/00006.png new file mode 100644 index 0000000..cec141b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00007.png b/tests/snapshots/nanox/test_sign_large_message/00007.png new file mode 100644 index 0000000..36eb31d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00007.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00008.png b/tests/snapshots/nanox/test_sign_large_message/00008.png new file mode 100644 index 0000000..df753a1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00008.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00009.png b/tests/snapshots/nanox/test_sign_large_message/00009.png new file mode 100644 index 0000000..d5b841e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00009.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00010.png b/tests/snapshots/nanox/test_sign_large_message/00010.png new file mode 100644 index 0000000..f5e85c3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00010.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00011.png b/tests/snapshots/nanox/test_sign_large_message/00011.png new file mode 100644 index 0000000..d22ac2d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00011.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00012.png b/tests/snapshots/nanox/test_sign_large_message/00012.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00012.png differ diff --git a/tests/snapshots/nanox/test_sign_large_message/00013.png b/tests/snapshots/nanox/test_sign_large_message/00013.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_large_message/00013.png differ diff --git a/tests/snapshots/nanox/test_sign_message/00000.png b/tests/snapshots/nanox/test_sign_message/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message/00001.png b/tests/snapshots/nanox/test_sign_message/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message/00002.png b/tests/snapshots/nanox/test_sign_message/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message/00003.png b/tests/snapshots/nanox/test_sign_message/00003.png new file mode 100644 index 0000000..3617b8d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message/00004.png b/tests/snapshots/nanox/test_sign_message/00004.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_message/00005.png b/tests/snapshots/nanox/test_sign_message/00005.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_message_pkh/00000.png b/tests/snapshots/nanox/test_sign_message_pkh/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_pkh/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_pkh/00001.png b/tests/snapshots/nanox/test_sign_message_pkh/00001.png new file mode 100644 index 0000000..208f5d8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_pkh/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_pkh/00002.png b/tests/snapshots/nanox/test_sign_message_pkh/00002.png new file mode 100644 index 0000000..3617b8d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_pkh/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_pkh/00003.png b/tests/snapshots/nanox/test_sign_message_pkh/00003.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_pkh/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_pkh/00004.png b/tests/snapshots/nanox/test_sign_message_pkh/00004.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_pkh/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00000.png b/tests/snapshots/nanox/test_sign_message_refused/00000.png new file mode 100644 index 0000000..2e4d511 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00001.png b/tests/snapshots/nanox/test_sign_message_refused/00001.png new file mode 100644 index 0000000..602c22a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00002.png b/tests/snapshots/nanox/test_sign_message_refused/00002.png new file mode 100644 index 0000000..305308f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00003.png b/tests/snapshots/nanox/test_sign_message_refused/00003.png new file mode 100644 index 0000000..3617b8d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00004.png b/tests/snapshots/nanox/test_sign_message_refused/00004.png new file mode 100644 index 0000000..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00005.png b/tests/snapshots/nanox/test_sign_message_refused/00005.png new file mode 100644 index 0000000..12caa58 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00006.png b/tests/snapshots/nanox/test_sign_message_refused/00006.png new file mode 100644 index 0000000..d917d19 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00000.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00100.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00100.png new file mode 100644 index 0000000..15fea9d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00101.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00101.png new file mode 100644 index 0000000..bc691a7 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00102.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00102.png new file mode 100644 index 0000000..28ee9c5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00103.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00103.png new file mode 100644 index 0000000..9832913 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00200.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00201.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00202.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00203.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00204.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00205.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00205.png new file mode 100644 index 0000000..c81329e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00300.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00000.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00100.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00100.png new file mode 100644 index 0000000..196af3c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00101.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00101.png new file mode 100644 index 0000000..08f05af Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00102.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00102.png new file mode 100644 index 0000000..5057eb0 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00200.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00201.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00202.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00203.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00204.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00205.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00205.png new file mode 100644 index 0000000..d3f5cd6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00300.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00000.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00100.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00100.png new file mode 100644 index 0000000..7c23f06 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00101.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00101.png new file mode 100644 index 0000000..59d0e80 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00102.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00102.png new file mode 100644 index 0000000..3d01046 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00103.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00103.png new file mode 100644 index 0000000..f62cce2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00104.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00104.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00105.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00105.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00106.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00106.png new file mode 100644 index 0000000..17cf916 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00106.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00200.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00000.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00100.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00100.png new file mode 100644 index 0000000..ad24691 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00101.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00101.png new file mode 100644 index 0000000..bd7a27e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00102.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00102.png new file mode 100644 index 0000000..cb8a713 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00103.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00103.png new file mode 100644 index 0000000..af79c8c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00104.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00104.png new file mode 100644 index 0000000..38c565f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00105.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00105.png new file mode 100644 index 0000000..bd5c339 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00106.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00106.png new file mode 100644 index 0000000..b1cd9fe Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00106.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00107.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00107.png new file mode 100644 index 0000000..e35ffb0 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00107.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00108.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00108.png new file mode 100644 index 0000000..42713e3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00108.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00109.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00109.png new file mode 100644 index 0000000..5418fd7 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00109.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00110.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00110.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00110.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00111.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00111.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00111.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00112.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00112.png new file mode 100644 index 0000000..b3afad9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00112.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00200.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegate_staking/00000.png b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegate_staking/00100.png b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00100.png new file mode 100644 index 0000000..15001c0 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegate_staking/00101.png b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00101.png new file mode 100644 index 0000000..722145c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegate_staking/00102.png b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00102.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegate_staking/00103.png b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00103.png new file mode 100644 index 0000000..0623b1b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegate_staking/00104.png b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00104.png new file mode 100644 index 0000000..e0ad1f1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegate_staking/00200.png b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegate_staking/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00000.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00100.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00100.png new file mode 100644 index 0000000..eaa64e5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00101.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00101.png new file mode 100644 index 0000000..351a884 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00200.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00201.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00202.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00203.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00204.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00205.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00205.png new file mode 100644 index 0000000..567675f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00300.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00000.png b/tests/snapshots/nanox/test_sign_tx_htlc/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00100.png b/tests/snapshots/nanox/test_sign_tx_htlc/00100.png new file mode 100644 index 0000000..40b1181 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00101.png b/tests/snapshots/nanox/test_sign_tx_htlc/00101.png new file mode 100644 index 0000000..f113d41 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00102.png b/tests/snapshots/nanox/test_sign_tx_htlc/00102.png new file mode 100644 index 0000000..195e6f3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00103.png b/tests/snapshots/nanox/test_sign_tx_htlc/00103.png new file mode 100644 index 0000000..778f66f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00104.png b/tests/snapshots/nanox/test_sign_tx_htlc/00104.png new file mode 100644 index 0000000..0d5d509 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00105.png b/tests/snapshots/nanox/test_sign_tx_htlc/00105.png new file mode 100644 index 0000000..9949b8d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00200.png b/tests/snapshots/nanox/test_sign_tx_htlc/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00201.png b/tests/snapshots/nanox/test_sign_tx_htlc/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00202.png b/tests/snapshots/nanox/test_sign_tx_htlc/00202.png new file mode 100644 index 0000000..607670c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00203.png b/tests/snapshots/nanox/test_sign_tx_htlc/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00204.png b/tests/snapshots/nanox/test_sign_tx_htlc/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00205.png b/tests/snapshots/nanox/test_sign_tx_htlc/00205.png new file mode 100644 index 0000000..32f349e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00300.png b/tests/snapshots/nanox/test_sign_tx_htlc/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00000.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00100.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00100.png new file mode 100644 index 0000000..7c86f79 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00101.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00101.png new file mode 100644 index 0000000..7f8fb9a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00102.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00102.png new file mode 100644 index 0000000..7cd6829 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00103.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00103.png new file mode 100644 index 0000000..77eedd9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00104.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00104.png new file mode 100644 index 0000000..f589bbf Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00105.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00105.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00106.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00106.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00106.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00107.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00107.png new file mode 100644 index 0000000..caaaca0 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00107.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00200.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00000.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00100.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00100.png new file mode 100644 index 0000000..8b7b71f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00101.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00101.png new file mode 100644 index 0000000..9d40b0b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00102.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00102.png new file mode 100644 index 0000000..64c4618 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00103.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00103.png new file mode 100644 index 0000000..2400405 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00104.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00104.png new file mode 100644 index 0000000..2f750b3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00105.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00105.png new file mode 100644 index 0000000..e3ef4c1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00106.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00106.png new file mode 100644 index 0000000..bc9c4a5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00106.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00107.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00107.png new file mode 100644 index 0000000..77dec68 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00107.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00108.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00108.png new file mode 100644 index 0000000..6d7bef4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00108.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00109.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00109.png new file mode 100644 index 0000000..8bf8d9b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00109.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00110.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00110.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00110.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00111.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00111.png new file mode 100644 index 0000000..af149c9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00111.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00112.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00112.png new file mode 100644 index 0000000..8a6f6ed Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00112.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00200.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00000.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00100.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00100.png new file mode 100644 index 0000000..216a4ef Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00101.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00101.png new file mode 100644 index 0000000..bcebde4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00102.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00102.png new file mode 100644 index 0000000..58a376f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00200.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00201.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00201.png new file mode 100644 index 0000000..7a9b805 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00202.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00202.png new file mode 100644 index 0000000..342974f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00203.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00203.png new file mode 100644 index 0000000..53e8899 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00204.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00204.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00205.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00205.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00206.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00206.png new file mode 100644 index 0000000..f7d545b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00206.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00300.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00000.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00100.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00100.png new file mode 100644 index 0000000..27249d7 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00101.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00101.png new file mode 100644 index 0000000..7199003 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00200.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00201.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00201.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00202.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00202.png new file mode 100644 index 0000000..b1ff363 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00203.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00203.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00204.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00204.png new file mode 100644 index 0000000..1a58b12 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00205.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00205.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00206.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00206.png new file mode 100644 index 0000000..eeaad30 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00206.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00207.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00207.png new file mode 100644 index 0000000..f2eaa31 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00207.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00300.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00000.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00100.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00100.png new file mode 100644 index 0000000..05efc73 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00101.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00101.png new file mode 100644 index 0000000..7060383 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00200.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00201.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00202.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00202.png new file mode 100644 index 0000000..d596bdf Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00300.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00300.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00301.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00301.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00301.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00302.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00302.png new file mode 100644 index 0000000..e820252 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00302.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00303.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00303.png new file mode 100644 index 0000000..376ff24 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00303.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00304.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00304.png new file mode 100644 index 0000000..3d1d652 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00304.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00305.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00305.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00305.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00306.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00306.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00306.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00307.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00307.png new file mode 100644 index 0000000..1611a57 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00307.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00400.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00400.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00400.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00000.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00100.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00100.png new file mode 100644 index 0000000..a6a6827 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00101.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00101.png new file mode 100644 index 0000000..315c395 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00102.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00102.png new file mode 100644 index 0000000..45ccfa9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00200.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00201.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00202.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00202.png new file mode 100644 index 0000000..607670c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00300.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00300.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00301.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00301.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00301.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00302.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00302.png new file mode 100644 index 0000000..d48537e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00302.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00303.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00303.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00303.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00304.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00304.png new file mode 100644 index 0000000..1a58b12 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00304.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00305.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00305.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00305.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00306.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00306.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00306.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00307.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00307.png new file mode 100644 index 0000000..6e341c1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00307.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00400.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00400.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00400.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00000.png b/tests/snapshots/nanox/test_sign_tx_transfer/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00100.png b/tests/snapshots/nanox/test_sign_tx_transfer/00100.png new file mode 100644 index 0000000..e2e6efc Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00101.png b/tests/snapshots/nanox/test_sign_tx_transfer/00101.png new file mode 100644 index 0000000..75337cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00102.png b/tests/snapshots/nanox/test_sign_tx_transfer/00102.png new file mode 100644 index 0000000..c759d9b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00103.png b/tests/snapshots/nanox/test_sign_tx_transfer/00103.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00104.png b/tests/snapshots/nanox/test_sign_tx_transfer/00104.png new file mode 100644 index 0000000..639af51 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00105.png b/tests/snapshots/nanox/test_sign_tx_transfer/00105.png new file mode 100644 index 0000000..7898c99 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00200.png b/tests/snapshots/nanox/test_sign_tx_transfer/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00000.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00100.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00100.png new file mode 100644 index 0000000..6336f24 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00101.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00101.png new file mode 100644 index 0000000..1853ea2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00200.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00201.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00202.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00203.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00204.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00204.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00205.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00205.png new file mode 100644 index 0000000..ddae9bc Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00300.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00000.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00100.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00100.png new file mode 100644 index 0000000..57c605e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00101.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00101.png new file mode 100644 index 0000000..129fb85 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00200.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00201.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00201.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00202.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00202.png new file mode 100644 index 0000000..85319c5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00203.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00203.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00203.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00204.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00204.png new file mode 100644 index 0000000..10fa236 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00204.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00205.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00205.png new file mode 100644 index 0000000..8ed052d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00205.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00206.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00206.png new file mode 100644 index 0000000..39ef4a2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00206.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00300.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00300.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00000.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00100.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00100.png new file mode 100644 index 0000000..e49b5a9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00101.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00101.png new file mode 100644 index 0000000..eeca286 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00102.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00102.png new file mode 100644 index 0000000..d3d4399 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00103.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00103.png new file mode 100644 index 0000000..e4ce494 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00104.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00104.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00105.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00105.png new file mode 100644 index 0000000..ca25279 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00106.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00106.png new file mode 100644 index 0000000..7898c99 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00106.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00200.png b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_input_and_commitment/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00000.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00100.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00100.png new file mode 100644 index 0000000..8eb7eca Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00101.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00101.png new file mode 100644 index 0000000..94cbe16 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00102.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00102.png new file mode 100644 index 0000000..57f8edc Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00102.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00103.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00103.png new file mode 100644 index 0000000..adb48cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00103.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00104.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00104.png new file mode 100644 index 0000000..bac888b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00104.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00105.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00105.png new file mode 100644 index 0000000..ff2c2a8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00105.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00106.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00106.png new file mode 100644 index 0000000..9f6dc8d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00106.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00107.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00107.png new file mode 100644 index 0000000..8a490a4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00107.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00108.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00108.png new file mode 100644 index 0000000..18123a5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00108.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00109.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00109.png new file mode 100644 index 0000000..615928e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00109.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00110.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00110.png new file mode 100644 index 0000000..214e45d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00110.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00111.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00111.png new file mode 100644 index 0000000..1da0b67 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00111.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00112.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00112.png new file mode 100644 index 0000000..43ae4fb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00112.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00113.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00113.png new file mode 100644 index 0000000..06534a7 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00113.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00114.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00114.png new file mode 100644 index 0000000..3cfd3b1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00114.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00115.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00115.png new file mode 100644 index 0000000..93af868 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00115.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00116.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00116.png new file mode 100644 index 0000000..f4ec1f4 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00116.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00117.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00117.png new file mode 100644 index 0000000..3f4b57f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00117.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00118.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00118.png new file mode 100644 index 0000000..175dcbd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00118.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00119.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00119.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00119.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00120.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00120.png new file mode 100644 index 0000000..af149c9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00120.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00121.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00121.png new file mode 100644 index 0000000..8a6f6ed Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00121.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_with_large_output/00200.png b/tests/snapshots/nanox/test_sign_tx_with_large_output/00200.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_with_large_output/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_without_outputs/00000.png b/tests/snapshots/nanox/test_sign_tx_without_outputs/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_without_outputs/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_without_outputs/00100.png b/tests/snapshots/nanox/test_sign_tx_without_outputs/00100.png new file mode 100644 index 0000000..eaa64e5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_without_outputs/00100.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_without_outputs/00101.png b/tests/snapshots/nanox/test_sign_tx_without_outputs/00101.png new file mode 100644 index 0000000..351a884 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_without_outputs/00101.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_without_outputs/00200.png b/tests/snapshots/nanox/test_sign_tx_without_outputs/00200.png new file mode 100644 index 0000000..11e1d3f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_without_outputs/00200.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_without_outputs/00201.png b/tests/snapshots/nanox/test_sign_tx_without_outputs/00201.png new file mode 100644 index 0000000..eeaad30 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_without_outputs/00201.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_without_outputs/00202.png b/tests/snapshots/nanox/test_sign_tx_without_outputs/00202.png new file mode 100644 index 0000000..567675f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_without_outputs/00202.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_without_outputs/00300.png b/tests/snapshots/nanox/test_sign_tx_without_outputs/00300.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_without_outputs/00300.png differ diff --git a/tests/snapshots/stax/test_app_mainmenu/00000.png b/tests/snapshots/stax/test_app_mainmenu/00000.png new file mode 100644 index 0000000..a08dbb1 Binary files /dev/null and b/tests/snapshots/stax/test_app_mainmenu/00000.png differ diff --git a/tests/snapshots/stax/test_app_mainmenu/00001.png b/tests/snapshots/stax/test_app_mainmenu/00001.png new file mode 100644 index 0000000..10a8392 Binary files /dev/null and b/tests/snapshots/stax/test_app_mainmenu/00001.png differ diff --git a/tests/snapshots/stax/test_get_public_key_confirm_accepted/00000.png b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00000.png new file mode 100644 index 0000000..bbae86a Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00000.png differ diff --git a/tests/snapshots/stax/test_get_public_key_confirm_accepted/00001.png b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00001.png new file mode 100644 index 0000000..d3e68dd Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00001.png differ diff --git a/tests/snapshots/stax/test_get_public_key_confirm_accepted/00002.png b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00002.png new file mode 100644 index 0000000..ef04425 Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00002.png differ diff --git a/tests/snapshots/stax/test_get_public_key_confirm_accepted/00003.png b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00003.png new file mode 100644 index 0000000..a08dbb1 Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_accepted/00003.png differ diff --git a/tests/snapshots/stax/test_get_public_key_confirm_refused/00000.png b/tests/snapshots/stax/test_get_public_key_confirm_refused/00000.png new file mode 100644 index 0000000..bbae86a Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_refused/00000.png differ diff --git a/tests/snapshots/stax/test_get_public_key_confirm_refused/00001.png b/tests/snapshots/stax/test_get_public_key_confirm_refused/00001.png new file mode 100644 index 0000000..d3e68dd Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_refused/00001.png differ diff --git a/tests/snapshots/stax/test_get_public_key_confirm_refused/00002.png b/tests/snapshots/stax/test_get_public_key_confirm_refused/00002.png new file mode 100644 index 0000000..94c91bb Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_refused/00002.png differ diff --git a/tests/snapshots/stax/test_sign_large_message/00000.png b/tests/snapshots/stax/test_sign_large_message/00000.png new file mode 100644 index 0000000..9beaf55 Binary files /dev/null and b/tests/snapshots/stax/test_sign_large_message/00000.png differ diff --git a/tests/snapshots/stax/test_sign_large_message/00001.png b/tests/snapshots/stax/test_sign_large_message/00001.png new file mode 100644 index 0000000..f94393b Binary files /dev/null and b/tests/snapshots/stax/test_sign_large_message/00001.png differ diff --git a/tests/snapshots/stax/test_sign_large_message/00002.png b/tests/snapshots/stax/test_sign_large_message/00002.png new file mode 100644 index 0000000..43a2b81 Binary files /dev/null and b/tests/snapshots/stax/test_sign_large_message/00002.png differ diff --git a/tests/snapshots/stax/test_sign_large_message/00003.png b/tests/snapshots/stax/test_sign_large_message/00003.png new file mode 100644 index 0000000..9645708 Binary files /dev/null and b/tests/snapshots/stax/test_sign_large_message/00003.png differ diff --git a/tests/snapshots/stax/test_sign_large_message/00004.png b/tests/snapshots/stax/test_sign_large_message/00004.png new file mode 100644 index 0000000..650a6f3 Binary files /dev/null and b/tests/snapshots/stax/test_sign_large_message/00004.png differ diff --git a/tests/snapshots/stax/test_sign_large_message/00005.png b/tests/snapshots/stax/test_sign_large_message/00005.png new file mode 100644 index 0000000..a08dbb1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_large_message/00005.png differ diff --git a/tests/snapshots/stax/test_sign_message/00000.png b/tests/snapshots/stax/test_sign_message/00000.png new file mode 100644 index 0000000..c75b6df Binary files /dev/null and b/tests/snapshots/stax/test_sign_message/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message/00001.png b/tests/snapshots/stax/test_sign_message/00001.png new file mode 100644 index 0000000..ede4fcc Binary files /dev/null and b/tests/snapshots/stax/test_sign_message/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message/00002.png b/tests/snapshots/stax/test_sign_message/00002.png new file mode 100644 index 0000000..6b6fc92 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message/00002.png differ diff --git a/tests/snapshots/stax/test_sign_message/00003.png b/tests/snapshots/stax/test_sign_message/00003.png new file mode 100644 index 0000000..650a6f3 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message/00003.png differ diff --git a/tests/snapshots/stax/test_sign_message/00004.png b/tests/snapshots/stax/test_sign_message/00004.png new file mode 100644 index 0000000..a08dbb1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message/00004.png differ diff --git a/tests/snapshots/stax/test_sign_message_pkh/00000.png b/tests/snapshots/stax/test_sign_message_pkh/00000.png new file mode 100644 index 0000000..c75b6df Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_pkh/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_pkh/00001.png b/tests/snapshots/stax/test_sign_message_pkh/00001.png new file mode 100644 index 0000000..bd48506 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_pkh/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_pkh/00002.png b/tests/snapshots/stax/test_sign_message_pkh/00002.png new file mode 100644 index 0000000..6b6fc92 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_pkh/00002.png differ diff --git a/tests/snapshots/stax/test_sign_message_pkh/00003.png b/tests/snapshots/stax/test_sign_message_pkh/00003.png new file mode 100644 index 0000000..650a6f3 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_pkh/00003.png differ diff --git a/tests/snapshots/stax/test_sign_message_pkh/00004.png b/tests/snapshots/stax/test_sign_message_pkh/00004.png new file mode 100644 index 0000000..a08dbb1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_pkh/00004.png differ diff --git a/tests/snapshots/stax/test_sign_message_refused/00000.png b/tests/snapshots/stax/test_sign_message_refused/00000.png new file mode 100644 index 0000000..c75b6df Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_refused/00000.png differ diff --git a/tests/snapshots/stax/test_sign_message_refused/00001.png b/tests/snapshots/stax/test_sign_message_refused/00001.png new file mode 100644 index 0000000..ede4fcc Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_refused/00001.png differ diff --git a/tests/snapshots/stax/test_sign_message_refused/00002.png b/tests/snapshots/stax/test_sign_message_refused/00002.png new file mode 100644 index 0000000..6b6fc92 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_refused/00002.png differ diff --git a/tests/snapshots/stax/test_sign_message_refused/00003.png b/tests/snapshots/stax/test_sign_message_refused/00003.png new file mode 100644 index 0000000..45414d5 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_refused/00003.png differ diff --git a/tests/snapshots/stax/test_sign_message_refused/00004.png b/tests/snapshots/stax/test_sign_message_refused/00004.png new file mode 100644 index 0000000..fecace7 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_refused/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00000.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00100.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00100.png new file mode 100644 index 0000000..acda3e2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00101.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00101.png new file mode 100644 index 0000000..acda3e2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00200.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00200.png new file mode 100644 index 0000000..14a4dca Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00201.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00201.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00202.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00202.png new file mode 100644 index 0000000..9f62ac4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00300.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00000.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00100.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00100.png new file mode 100644 index 0000000..eadaf06 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00101.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00101.png new file mode 100644 index 0000000..eadaf06 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00200.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00200.png new file mode 100644 index 0000000..14a4dca Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00201.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00201.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00202.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00202.png new file mode 100644 index 0000000..5154b60 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00300.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00000.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00100.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00100.png new file mode 100644 index 0000000..8c29405 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00101.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00101.png new file mode 100644 index 0000000..1ba7dbc Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00102.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00102.png new file mode 100644 index 0000000..db55f1e Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00200.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00000.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00100.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00100.png new file mode 100644 index 0000000..9481bf6 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00101.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00101.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00102.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00102.png new file mode 100644 index 0000000..c05cf3b Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00200.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegate_staking/00000.png b/tests/snapshots/stax/test_sign_tx_delegate_staking/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegate_staking/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegate_staking/00100.png b/tests/snapshots/stax/test_sign_tx_delegate_staking/00100.png new file mode 100644 index 0000000..ec8dd5b Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegate_staking/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegate_staking/00101.png b/tests/snapshots/stax/test_sign_tx_delegate_staking/00101.png new file mode 100644 index 0000000..171985a Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegate_staking/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegate_staking/00102.png b/tests/snapshots/stax/test_sign_tx_delegate_staking/00102.png new file mode 100644 index 0000000..2bc14dc Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegate_staking/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegate_staking/00200.png b/tests/snapshots/stax/test_sign_tx_delegate_staking/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegate_staking/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00000.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00100.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00100.png new file mode 100644 index 0000000..0923fc4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00101.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00101.png new file mode 100644 index 0000000..0923fc4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00200.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00200.png new file mode 100644 index 0000000..14a4dca Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00201.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00201.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00202.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00202.png new file mode 100644 index 0000000..c40ddea Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00300.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00000.png b/tests/snapshots/stax/test_sign_tx_htlc/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00100.png b/tests/snapshots/stax/test_sign_tx_htlc/00100.png new file mode 100644 index 0000000..8572fd8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00101.png b/tests/snapshots/stax/test_sign_tx_htlc/00101.png new file mode 100644 index 0000000..8572fd8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00200.png b/tests/snapshots/stax/test_sign_tx_htlc/00200.png new file mode 100644 index 0000000..c1443b8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00201.png b/tests/snapshots/stax/test_sign_tx_htlc/00201.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00202.png b/tests/snapshots/stax/test_sign_tx_htlc/00202.png new file mode 100644 index 0000000..f316ee9 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00300.png b/tests/snapshots/stax/test_sign_tx_htlc/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00000.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00100.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00100.png new file mode 100644 index 0000000..109b5f9 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00101.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00101.png new file mode 100644 index 0000000..1ba7dbc Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00102.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00102.png new file mode 100644 index 0000000..efce194 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00200.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00000.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00100.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00100.png new file mode 100644 index 0000000..f8545e4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00101.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00101.png new file mode 100644 index 0000000..82af017 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00102.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00102.png new file mode 100644 index 0000000..63885c4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00200.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00000.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00100.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00100.png new file mode 100644 index 0000000..d535786 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00101.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00101.png new file mode 100644 index 0000000..d535786 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00200.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00200.png new file mode 100644 index 0000000..42cc985 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00201.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00201.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00202.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00202.png new file mode 100644 index 0000000..e4a5bc4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00300.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00000.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00100.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00100.png new file mode 100644 index 0000000..ab5f855 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00101.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00101.png new file mode 100644 index 0000000..ab5f855 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00200.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00200.png new file mode 100644 index 0000000..0ae4a95 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00201.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00201.png new file mode 100644 index 0000000..bf4012c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00202.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00202.png new file mode 100644 index 0000000..6aa859c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00300.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00000.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00100.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00100.png new file mode 100644 index 0000000..5376024 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00101.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00101.png new file mode 100644 index 0000000..5376024 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00200.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00200.png new file mode 100644 index 0000000..87f9c29 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00201.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00201.png new file mode 100644 index 0000000..87f9c29 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00300.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00300.png new file mode 100644 index 0000000..8da0054 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00301.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00301.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00301.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00302.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00302.png new file mode 100644 index 0000000..49dbd18 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00302.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00400.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00400.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00400.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00000.png b/tests/snapshots/stax/test_sign_tx_order_fill/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00100.png b/tests/snapshots/stax/test_sign_tx_order_fill/00100.png new file mode 100644 index 0000000..a617a80 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00101.png b/tests/snapshots/stax/test_sign_tx_order_fill/00101.png new file mode 100644 index 0000000..a617a80 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00200.png b/tests/snapshots/stax/test_sign_tx_order_fill/00200.png new file mode 100644 index 0000000..c1443b8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00201.png b/tests/snapshots/stax/test_sign_tx_order_fill/00201.png new file mode 100644 index 0000000..c1443b8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00300.png b/tests/snapshots/stax/test_sign_tx_order_fill/00300.png new file mode 100644 index 0000000..b74dcaa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00301.png b/tests/snapshots/stax/test_sign_tx_order_fill/00301.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00301.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00302.png b/tests/snapshots/stax/test_sign_tx_order_fill/00302.png new file mode 100644 index 0000000..6abc393 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00302.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00400.png b/tests/snapshots/stax/test_sign_tx_order_fill/00400.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00400.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00000.png b/tests/snapshots/stax/test_sign_tx_transfer/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00100.png b/tests/snapshots/stax/test_sign_tx_transfer/00100.png new file mode 100644 index 0000000..3dfb266 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00101.png b/tests/snapshots/stax/test_sign_tx_transfer/00101.png new file mode 100644 index 0000000..7f25a0d Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00102.png b/tests/snapshots/stax/test_sign_tx_transfer/00102.png new file mode 100644 index 0000000..fad9bf9 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00200.png b/tests/snapshots/stax/test_sign_tx_transfer/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00000.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00100.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00100.png new file mode 100644 index 0000000..0ec4027 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00101.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00101.png new file mode 100644 index 0000000..0ec4027 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00200.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00200.png new file mode 100644 index 0000000..14a4dca Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00201.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00201.png new file mode 100644 index 0000000..33b0672 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00202.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00202.png new file mode 100644 index 0000000..a94fb9e Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00300.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00000.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00100.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00100.png new file mode 100644 index 0000000..5584da1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00101.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00101.png new file mode 100644 index 0000000..5584da1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00200.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00200.png new file mode 100644 index 0000000..14a4dca Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00201.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00201.png new file mode 100644 index 0000000..595823c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00201.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00202.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00202.png new file mode 100644 index 0000000..b6ae52c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00202.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00300.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00300.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00000.png b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00100.png b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00100.png new file mode 100644 index 0000000..93dbdfd Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00101.png b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00101.png new file mode 100644 index 0000000..38c3471 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00102.png b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00102.png new file mode 100644 index 0000000..fad9bf9 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00200.png b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_input_and_commitment/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_output/00000.png b/tests/snapshots/stax/test_sign_tx_with_large_output/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_output/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_output/00100.png b/tests/snapshots/stax/test_sign_tx_with_large_output/00100.png new file mode 100644 index 0000000..f8545e4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_output/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_output/00101.png b/tests/snapshots/stax/test_sign_tx_with_large_output/00101.png new file mode 100644 index 0000000..82af017 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_output/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_output/00102.png b/tests/snapshots/stax/test_sign_tx_with_large_output/00102.png new file mode 100644 index 0000000..63885c4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_output/00102.png differ diff --git a/tests/snapshots/stax/test_sign_tx_with_large_output/00200.png b/tests/snapshots/stax/test_sign_tx_with_large_output/00200.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_with_large_output/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_without_outputs/00000.png b/tests/snapshots/stax/test_sign_tx_without_outputs/00000.png new file mode 100644 index 0000000..a46449f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_without_outputs/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_without_outputs/00100.png b/tests/snapshots/stax/test_sign_tx_without_outputs/00100.png new file mode 100644 index 0000000..0923fc4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_without_outputs/00100.png differ diff --git a/tests/snapshots/stax/test_sign_tx_without_outputs/00101.png b/tests/snapshots/stax/test_sign_tx_without_outputs/00101.png new file mode 100644 index 0000000..0923fc4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_without_outputs/00101.png differ diff --git a/tests/snapshots/stax/test_sign_tx_without_outputs/00200.png b/tests/snapshots/stax/test_sign_tx_without_outputs/00200.png new file mode 100644 index 0000000..c40ddea Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_without_outputs/00200.png differ diff --git a/tests/snapshots/stax/test_sign_tx_without_outputs/00300.png b/tests/snapshots/stax/test_sign_tx_without_outputs/00300.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_without_outputs/00300.png differ diff --git a/tests/test_app_mainmenu.py b/tests/test_app_mainmenu.py new file mode 100644 index 0000000..dfb9e71 --- /dev/null +++ b/tests/test_app_mainmenu.py @@ -0,0 +1,20 @@ +from ragger.navigator import NavInsID + +from utils import ROOT_SCREENSHOT_PATH + + +# In this test we check the behavior of the device main menu +def test_app_mainmenu(device, navigator, test_name): + # Navigate in the main menu + if device.is_nano: + instructions = [NavInsID.RIGHT_CLICK, NavInsID.RIGHT_CLICK] + else: + instructions = [ + NavInsID.USE_CASE_HOME_SETTINGS, + ] + navigator.navigate_and_compare( + ROOT_SCREENSHOT_PATH, + test_name, + instructions, + screen_change_before_first_instruction=False, + ) diff --git a/tests/test_error_cmd.py b/tests/test_error_cmd.py new file mode 100644 index 0000000..88c6b2b --- /dev/null +++ b/tests/test_error_cmd.py @@ -0,0 +1,232 @@ +import pytest +from ragger.error import ExceptionRAPDU + +from application_client import MAINNET +from application_client.mintlayer_command_sender import ( + CLA, + GetAppAndVersionP1, + SignTxP1, + P2, + Errors, + InsType, +) +from application_client.mintlayer_utils import ( + sign_tx_start_req_obj, + sign_tx_next_req_obj, +) + + +# Ensure the app returns an error when a bad CLA is used +def test_bad_cla(backend): + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA + 1, ins=InsType.GET_PUBLIC_KEY) + assert e.value.status == Errors.SW_CLA_NOT_SUPPORTED + + +# Ensure the app returns an error when a bad INS is used +def test_bad_ins(backend): + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange(cla=CLA, ins=0xFF) + assert e.value.status == Errors.SW_INS_NOT_SUPPORTED + + +# Ensure the app returns an error when a bad P1 or P2 is used +def test_wrong_p1p2(backend): + # Wrong P2 + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange( + cla=CLA, ins=InsType.GET_PUBLIC_KEY, p1=GetAppAndVersionP1.P1_START, p2=123 + ) + assert e.value.status == Errors.SW_WRONG_P1P2 + + backend.exchange( + cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=GetAppAndVersionP1.P1_START, + p2=P2.P2_MORE, + ) + + # Wrong P1 after sending MORE + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange( + cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=GetAppAndVersionP1.P1_START + 1, + p2=P2.P2_MORE, + ) + assert e.value.status == Errors.SW_WRONG_P1P2 + + +# Ensure the app returns an error when a bad data length is used +def test_wrong_data_length(backend): + # APDUs must be at least 4 bytes: CLA, INS, P1, P2. + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange_raw(bytes.fromhex("E00300")) + assert e.value.status == Errors.SW_WRONG_APDU_LENGTH + # APDUs advertises a too long length + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange_raw(bytes.fromhex("E003000005")) + assert e.value.status == Errors.SW_WRONG_APDU_LENGTH + + +# Ensure there is no state confusion when trying wrong APDU sequences +def test_invalid_state(backend): + with pytest.raises(ExceptionRAPDU) as e: + backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, # Try to continue a flow instead of start a new one + p2=P2.P2_LAST, + ) + assert e.value.status == Errors.SW_WRONG_CONTEXT + + +def test_sign_tx_invalid_coin(backend, scenario_navigator, device, navigator): + invalid_coin = 255 + num_inputs = 1 + num_outputs = 1 + start_req = sign_tx_start_req_obj.encode( + { + "coin": invalid_coin, + "version": 0, + "num_inputs": num_inputs, + "num_outputs": num_outputs, + } + ).data + + with pytest.raises(ExceptionRAPDU) as e: + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_START, + p2=P2.P2_LAST, + data=bytes(start_req), + ) + + assert e.value.status == Errors.SW_DESERIALIZE_FAIL + + +def test_sign_tx_invalid_context(backend, scenario_navigator, device, navigator): + """ + After the start request try to pass an output instead of the input + expect an error for wrong context + """ + num_inputs = 2 + num_outputs = 2 + start_req = sign_tx_start_req_obj.encode( + { + "coin": MAINNET, + "version": 0, + "num_inputs": num_inputs, + "num_outputs": num_outputs, + } + ).data + + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_START, + p2=P2.P2_LAST, + data=bytes(start_req), + ) + + assert res.status == 0x9000 + + with pytest.raises(ExceptionRAPDU) as e: + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_LAST, + data=sign_tx_next_req_obj.encode( + { + "ProcessOutput": { + "output": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": { + "Secp256k1Schnorr": { + "pubkey_data": bytes([0] * 33) + } + } + } + }, + ], + } + } + } + ).data, + ) + assert e.value.status == Errors.SW_WRONG_CONTEXT + + +def test_sign_tx_invalid_input(backend, scenario_navigator, device, navigator): + num_inputs = 2 + num_outputs = 2 + start_req = sign_tx_start_req_obj.encode( + { + "coin": MAINNET, + "version": 0, + "num_inputs": num_inputs, + "num_outputs": num_outputs, + } + ).data + + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_START, + p2=P2.P2_LAST, + data=bytes(start_req), + ) + + print("res, ", res.status) + assert res.status == 0x9000 + + with pytest.raises(ExceptionRAPDU) as e: + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_LAST, + data=bytes([0] * 10), + ) + + assert e.value.status == Errors.SW_DESERIALIZE_FAIL + + +def test_sign_tx_too_large_data(backend, scenario_navigator, device, navigator): + num_inputs = 2 + num_outputs = 2 + start_req = sign_tx_start_req_obj.encode( + { + "coin": MAINNET, + "version": 0, + "num_inputs": num_inputs, + "num_outputs": num_outputs, + } + ).data + + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_START, + p2=P2.P2_LAST, + data=bytes(start_req), + ) + + assert res.status == 0x9000 + + with pytest.raises(ExceptionRAPDU) as e: + for _ in range(1000): + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=SignTxP1.P1_NEXT, + p2=P2.P2_MORE, + data=b"input_data", + ) + + assert e.value.status == Errors.SW_MAX_BUFFER_LEN_EXCEEDED diff --git a/tests/test_name_version.py b/tests/test_name_version.py new file mode 100644 index 0000000..f326ff7 --- /dev/null +++ b/tests/test_name_version.py @@ -0,0 +1,17 @@ +from application_client.mintlayer_command_sender import MintlayerCommandSender +from application_client.mintlayer_response_unpacker import ( + unpack_get_app_and_version_response, +) + + +# Test a specific APDU asking BOLOS (and not the app) the name and version of the current app +def test_get_app_and_version(backend, backend_name): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + # Send the special instruction to BOLOS + response = client.get_app_and_version() + # Use an helper to parse the response, assert the values + app_name, version = unpack_get_app_and_version_response(response.data) + print(app_name, version) + assert app_name == "Mintlayer" + assert version == "1.0.0" diff --git a/tests/test_pubkey_cmd.py b/tests/test_pubkey_cmd.py new file mode 100644 index 0000000..8f3ef23 --- /dev/null +++ b/tests/test_pubkey_cmd.py @@ -0,0 +1,79 @@ +import pytest +from ragger.bip import CurveChoice, calculate_public_key_and_chaincode +from ragger.error import ExceptionRAPDU + +from application_client import MAINNET, TESTNET +from application_client.mintlayer_command_sender import Errors, MintlayerCommandSender +from application_client.mintlayer_response_unpacker import ( + unpack_get_public_key_response, +) + +MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + + +# In this test we check that the GET_PUBLIC_KEY works in non-confirmation mode +def test_get_public_key_no_confirm(backend): + for path in [ + "m/44'/19788'/0'/0/0", + "m/44'/19788'/0/0/0", + "m/44'/19788'/911'/0/0", + "m/44'/19788'/255/255/255", + "m/44'/19788'/2147483647/0/0/0/0/0/0/0", + ]: + client = MintlayerCommandSender(backend) + response = client.get_public_key(coin=MAINNET, path=path).data + _, public_key, _, _ = unpack_get_public_key_response(response) + + ref_public_key, _ = calculate_public_key_and_chaincode( + CurveChoice.Secp256k1, path=path, mnemonic=MNEMONIC + ) + assert public_key.hex() == ref_public_key + + +def test_get_public_key_no_confirm_testnet(backend): + for path in [ + "m/44'/1'/0'/0/0", + "m/44'/1'/0/0/0", + "m/44'/1'/911'/0/0", + "m/44'/1'/255/255/255", + "m/44'/1'/2147483647/0/0/0/0/0/0/0", + ]: + client = MintlayerCommandSender(backend) + response = client.get_public_key(coin=TESTNET, path=path).data + _, public_key, _, _ = unpack_get_public_key_response(response) + + ref_public_key, _ = calculate_public_key_and_chaincode( + CurveChoice.Secp256k1, path=path, mnemonic=MNEMONIC + ) + assert public_key.hex() == ref_public_key + + +# In this test we check that the GET_PUBLIC_KEY works in confirmation mode +def test_get_public_key_confirm_accepted(backend, scenario_navigator): + client = MintlayerCommandSender(backend) + path = "m/44'/19788'/0'/0/0" + + with client.get_public_key_with_confirmation(coin=MAINNET, path=path): + scenario_navigator.address_review_approve() + + response = client.get_async_response().data + _, public_key, _, _ = unpack_get_public_key_response(response) + + ref_public_key, _ = calculate_public_key_and_chaincode( + CurveChoice.Secp256k1, path=path, mnemonic=MNEMONIC + ) + assert public_key.hex() == ref_public_key + + +# In this test we check that the GET_PUBLIC_KEY in confirmation mode replies an error if the user refuses +def test_get_public_key_confirm_refused(backend, scenario_navigator): + client = MintlayerCommandSender(backend) + path = "m/44'/19788'/0'/0/0" + + with pytest.raises(ExceptionRAPDU) as e: + with client.get_public_key_with_confirmation(coin=MAINNET, path=path): + scenario_navigator.address_review_reject() + + # Assert that we have received a refusal + assert e.value.status == Errors.SW_DENY + assert len(e.value.data) == 0 diff --git a/tests/test_sign_message.py b/tests/test_sign_message.py new file mode 100644 index 0000000..b7b7dd0 --- /dev/null +++ b/tests/test_sign_message.py @@ -0,0 +1,90 @@ +import pytest +from ragger.error import ExceptionRAPDU + +from application_client import MAINNET +from application_client.mintlayer_command_sender import Errors, MintlayerCommandSender +from application_client.mintlayer_response_unpacker import ( + unpack_sign_message_response, + unpack_get_public_key_response, +) +from application_client.mintlayer_utils import ( + verify_message_signature, +) + + +# In this test we check that the message signing works +def test_sign_message(backend, scenario_navigator): + path = "m/44'/19788'/0'/0/0" + message = b"Hello" + coin = MAINNET + client = MintlayerCommandSender(backend) + + pubkey = get_pub_key(client, coin=coin, path=path) + + with client.sign_message(coin=coin, addr_type=0, path=path, message=message): + scenario_navigator.review_approve() + + sig = unpack_sign_message_response(client.get_async_response().data) + + sig_valid = verify_message_signature(message, pubkey, sig) + assert sig_valid, "Signature verification failed" + + +# Same as test_sign_message, but the message is large enough to require chunking +def test_sign_large_message(backend, scenario_navigator): + path = "m/44'/19788'/0'/0/0" + message = b"Hello" * 100 + coin = MAINNET + client = MintlayerCommandSender(backend) + + pubkey = get_pub_key(client, coin=coin, path=path) + + with client.sign_message(coin=coin, addr_type=0, path=path, message=message): + scenario_navigator.review_approve() + + sig = unpack_sign_message_response(client.get_async_response().data) + assert len(sig) == 64 + + sig_valid = verify_message_signature(message, pubkey, sig) + assert sig_valid, "Signature verification failed" + + +def test_sign_message_pkh(backend, scenario_navigator): + path = "m/44'/19788'/0'/0/0" + message = b"Hello" + coin = MAINNET + client = MintlayerCommandSender(backend) + + pubkey = get_pub_key(client, coin=coin, path=path) + + with client.sign_message(coin=coin, addr_type=1, path=path, message=message): + scenario_navigator.review_approve() + + sig = unpack_sign_message_response(client.get_async_response().data) + + sig_valid = verify_message_signature(message, pubkey, sig) + assert sig_valid, "Signature verification failed" + + +# Message signing refused test +# The test will ask for a message signature that will be refused on screen +def test_sign_message_refused(backend, scenario_navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + path: str = "m/44'/19788'/0'/0/0" + message: bytes = b"Hello" + coin = MAINNET + + with pytest.raises(ExceptionRAPDU) as e: + with client.sign_message(coin=coin, addr_type=0, path=path, message=message): + scenario_navigator.review_reject() + + # Assert that we have received a refusal + assert e.value.status == Errors.SW_DENY + assert len(e.value.data) == 0 + + +def get_pub_key(client: MintlayerCommandSender, coin: int, path: str) -> bytes: + rapdu = client.get_public_key(coin, path) + _, pubkey, _, _ = unpack_get_public_key_response(rapdu.data) + return pubkey diff --git a/tests/test_sign_tx.py b/tests/test_sign_tx.py new file mode 100644 index 0000000..029ba00 --- /dev/null +++ b/tests/test_sign_tx.py @@ -0,0 +1,1591 @@ +from application_client import MAINNET +from application_client.mintlayer_command_sender import ( + MintlayerCommandSender, + sign_tx_review, + ReviewTransaction, +) +from application_client.mintlayer_response_unpacker import ( + unpack_get_public_key_response, +) +from application_client.mintlayer_utils import Transaction, sign_tx_next_req_obj + +# TODO: implement missing tests: +# * CreateOrder, DataDeposit, Burn outputs; +# * pool decommissioning; +# * LockTokenSupply, FreezeOrder commands; +# * maybe something else. +# See https://github.com/mintlayer/mintlayer-ledger-app/issues/19. + + +def test_sign_tx_transfer(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + # The path used for this entire test + path: str = "m/44'/19788'/0'/0/0" + + # First we need to get the public key of the device in order to build the transaction + rapdu = client.get_public_key(coin=MAINNET, path=path) + _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) + + print("pk", len(public_key)) + + h = 1 << 31 + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + output = { + "output": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\stransfer", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_lock_then_transfer(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + # The path used for this entire test + path: str = "m/44'/19788'/0'/0/0" + + h = 1 << 31 + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Account": { + "nonce": 1, + "spending": {"DelegationBalance": [[0] * 32, 11]}, + } + }, + } + + inp_commitment = {"commitment": {"None": None}} + + output = { + "output": { + "LockThenTransfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + {"UntilHeight": 10}, + ], + } + } + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\sdelegation", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_create_delegation(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + h = 1 << 31 + + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + output = { + "output": { + "CreateDelegationId": [ + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + [0] * 32, + ], + } + } + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\screate", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_delegate_staking(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + h = 1 << 31 + + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + output = { + "output": { + "DelegateStaking": [5, [0] * 32], + } + } + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\sdelegate", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_create_stake_pool(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + h = 1 << 31 + + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 40001}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + output = { + "output": { + "CreateStakePool": [ + [0] * 32, + { + "pledge": 40000, + "staker": { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)} + } + } + }, + "vrf_public_key": {"key": {"Schnorrkel": {"key": bytes([0] * 32)}}}, + "decommission_key": { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)} + } + } + }, + "margin_ratio_per_thousand": 10, + "cost_per_block": 5, + }, + ], + } + } + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\screate\sstake", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_issue_fungible_token(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + h = 1 << 31 + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + output = { + "output": { + "IssueFungibleToken": { + "V1": { + "token_ticker": b"MYTKN", + "number_of_decimals": 8, + "metadata_uri": b"https://my.token.uri", + "total_supply": {"Fixed": 1000000000}, + "authority": { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)} + } + } + }, + "is_freezable": {"Yes": None}, + } + }, + } + } + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\screate\stoken", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_issue_nft(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + h = 1 << 31 + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 2000}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + # This is the new output for issuing an NFT. + # The structure is (TokenId, NftIssuance, Destination) + output = { + "output": { + "IssueNft": [ + bytes([0] * 32), + { + "V0": { + "metadata": { + "creator": { + "public_key": { + "key": { + "Secp256k1Schnorr": { + "pubkey_data": bytes([0] * 33) + } + } + } + }, + "name": b"MyAwesomeNFT", + "description": b"FirstNFT", + "ticker": b"MNFT1", + "icon_uri": b"https://my.nft/icon.png", + "additional_metadata_uri": b"https://my.nft/meta.json", + "media_uri": b"https://my.nft/media.jpg", + "media_hash": bytes([0] * 32), + } + } + }, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\screate\sNFT", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_mint_tokens(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input. + 2. An AccountCommand input to mint new tokens. + And one output to transfer the newly minted tokens. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The utxo (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + account_nonce = 1 + token_id = bytes([0] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "AccountCommand": [ + account_nonce, + { + "MintTokens": [ + token_id, + 1000, # Amount to mint + ] + }, + ] + }, + } + + acc_inp_commitment = {"commitment": {"None": None}} + + mint_output = { + "output": { + "Transfer": [ + {"TokenV1": [token_id, 1000]}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[mint_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\smint\stokens", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_unmint_tokens(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input to pay tx fees. + 2. An AccountCommand input to unmint new tokens. + 3. A standard UTXO input with tokens to unmint + And one output to transfer the newly minted tokens. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional data (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + additional_info2 = { + "Utxo": { + "Transfer": [ + {"TokenV1": [bytes([0] * 32), 1000]}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + utxo_input2 = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 2, + }, + additional_info2, + ] + }, + } + + inp_commitment2 = {"commitment": additional_info2} + + account_nonce = 1 + token_id = bytes([0] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "AccountCommand": [ + account_nonce, + { + "UnmintTokens": token_id, + }, + ] + }, + } + + acc_inp_commitment = {"commitment": {"None": None}} + + change_output = { + "output": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input, utxo_input2], + input_commitments=[inp_commitment, acc_inp_commitment, inp_commitment2], + outputs=[change_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\sunmint\stokens", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_freeze_tokens(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input to pay for tx fees. + 2. An AccountCommand input to freeze the tokens. + And one output to transfer the change coins. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional info (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + account_nonce = 1 + token_id = bytes([0] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "AccountCommand": [ + account_nonce, + {"FreezeToken": [token_id, {"No": None}]}, + ] + }, + } + + acc_inp_commitment = {"commitment": {"None": None}} + + change_output = { + "output": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\sfreeze\stokens", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_unfreeze_tokens(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input to pay for tx fees. + 2. An AccountCommand input to unfreeze the tokens. + And one output to transfer the change coins. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional data (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + account_nonce = 1 + token_id = bytes([0] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "AccountCommand": [ + account_nonce, + { + "UnfreezeToken": token_id, + }, + ] + }, + } + + acc_inp_commitment = {"commitment": {"None": None}} + + change_output = { + "output": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\sunfreeze", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_change_token_authority(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input to pay for tx fees. + 2. An AccountCommand input to change the token's authority. + And one output to transfer the change coins. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional data (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + account_nonce = 1 + token_id = bytes([0] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "AccountCommand": [ + account_nonce, + { + "ChangeTokenAuthority": [ + token_id, + { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)} + } + } + }, + ] + }, + ] + }, + } + + acc_inp_commitment = {"commitment": {"None": None}} + + change_output = { + "output": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\schange\stoken", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_change_token_metadata_uri( + backend, scenario_navigator, device, navigator +): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input to pay for tx fees. + 2. An AccountCommand input to change the token's metadata uri. + And one output to transfer the change coins. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional info (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + account_nonce = 1 + token_id = bytes([0] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "AccountCommand": [ + account_nonce, + { + "ChangeTokenMetadataUri": [ + token_id, + "uri.com".encode(), + ] + }, + ] + }, + } + + acc_inp_commitment = {"commitment": {"None": None}} + + change_output = { + "output": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\schange\stoken", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_order_fill(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with 3 inputs: + 1. A standard UTXO input to pay for tx fees. + 2. An account command input to fill an order. + 3. A standard UTXO input to be used for the fill. + And one output to transfer the change coins. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional info (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + fill_amount = 10 + fill_ask = 100 + fill_give = 1000 + + additional_order_info = { + "initially_asked": {"Coin": fill_ask}, + "initially_given": {"TokenV1": [bytes([0] * 32), fill_give]}, + "ask_balance": 0, + "give_balance": 0, + } + order_id = bytes([0] * 32) + account_input = { + "addresses": [], # FillOrder input must not be signed + "input": { + "OrderAccountCommand": [ + { + "FillOrder": [ + order_id, + fill_amount, + ] + }, + additional_order_info, + ] + }, + } + + fill_order_inp_commitment = { + "commitment": { + "FillOrderAccountCommand": [ + additional_order_info["initially_asked"], + additional_order_info["initially_given"], + ] + }, + } + + change_output = { + "output": { + "Transfer": [ + {"Coin": 100 - 1 - fill_amount}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + fill_output = { + "output": { + "Transfer": [ + { + "TokenV1": [ + bytes([0] * 32), + fill_amount * fill_give // fill_ask, + ] + }, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, fill_order_inp_commitment], + outputs=[change_output, fill_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\sfill\sorder", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_order_conclude(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input to pay for tx fees. + 2. An OrderAccountCommand input to conclude an order. + And one output to transfer the change coins + ask balance and another output for the give balance. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional_data (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + initial_ask = 100 + initial_give = 1000 + ask_balance = 10 + give_balance = 900 + + token_id = bytes([1] * 32) + additional_order_info = { + "initially_asked": {"Coin": initial_ask}, + "initially_given": {"TokenV1": [token_id, initial_give]}, + "ask_balance": ask_balance, + "give_balance": give_balance, + } + order_id = bytes([1] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "OrderAccountCommand": [ + { + "ConcludeOrder": order_id, + }, + additional_order_info, + ] + }, + } + + conclude_order_inp_commitment = { + "commitment": { + "ConcludeOrderAccountCommand": [ + additional_order_info["initially_asked"], + additional_order_info["initially_given"], + additional_order_info["ask_balance"], + additional_order_info["give_balance"], + ] + }, + } + + change_output = { + "output": { + "Transfer": [ + {"Coin": 100 - 1 + ask_balance}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + conclude_output = { + "output": { + "Transfer": [ + {"TokenV1": [token_id, give_balance]}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, conclude_order_inp_commitment], + outputs=[change_output, conclude_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\sconclude\sorder", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_htlc(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with one input: + 1. The standard UTXO input to pay for tx fees. + And one output to transfer the change coins and the HTLC output. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional info (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + change_output = { + "output": { + "Transfer": [ + {"Coin": 89}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + htlc_output = { + "output": { + "Htlc": [ + {"Coin": 10}, + { + "secret_hash": [0] * 20, + "spend_key": { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)} + } + } + }, + "refund_timelock": {"UntilHeight": 100}, + "refund_key": { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([3] * 33)} + } + } + }, + }, + ], + } + } + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input], + input_commitments=[inp_commitment], + outputs=[htlc_output, change_output], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\screate\sHTLC", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +def test_sign_tx_without_outputs(backend, scenario_navigator, device, navigator): + """ + Test signing a transaction with two inputs: + 1. A standard UTXO input to pay for tx fees. + 2. An AccountCommand input to freeze the tokens. + And no outputs. + """ + client = MintlayerCommandSender(backend) + h = 1 << 31 + bip44_path = [44 + h, 19788 + h, 0 + h, 0, 0] + + # The additional info (the previous TxOutput that this UTXO input spends) + # This represents an output of 100 coins owned by our key + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 100}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + utxo_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([1] * 32)}, + "index": 0, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + account_nonce = 1 + token_id = bytes([0] * 32) + account_input = { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "input": { + "AccountCommand": [ + account_nonce, + {"FreezeToken": [token_id, {"No": None}]}, + ] + }, + } + + acc_inp_commitment = {"commitment": {"None": None}} + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[], + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=True, + review_custom_screen_text=r"Sign\sfreeze\stokens", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +# Sign a tx with an output large enough to require chunking +def test_sign_tx_with_large_output(backend, scenario_navigator, device, navigator): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + h = 1 << 31 + additional_info = { + "Utxo": { + "Transfer": [ + {"Coin": 2000}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + # Make the output big + metadata_uri = b"abcef" * 100 + + # This is an output for issuing an NFT. + # The structure is (TokenId, NftIssuance, Destination) + output = { + "output": { + "IssueNft": [ + bytes([0] * 32), + { + "V0": { + "metadata": { + "creator": { + "public_key": { + "key": { + "Secp256k1Schnorr": { + "pubkey_data": bytes([0] * 33) + } + } + } + }, + "name": b"MyAwesomeNFT", + "description": b"FirstNFT", + "ticker": b"MNFT1", + "icon_uri": b"https://my.nft/icon.png", + "additional_metadata_uri": metadata_uri, + "media_uri": b"https://my.nft/media.jpg", + "media_hash": bytes([0] * 32), + } + } + }, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + + # Sanity check + assert len(sign_tx_next_req_obj.encode({"ProcessOutput": output}).data) > 500 + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\screate\sNFT", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) + + +# Sign a tx with an input and input commitment large enough to require chunking +def test_sign_tx_with_large_input_and_commitment( + backend, scenario_navigator, device, navigator +): + # Use the app interface instead of raw interface + client = MintlayerCommandSender(backend) + h = 1 << 31 + + # Make the additional info big + metadata_uri = b"abcef" * 100 + + # The utxo is for issuing an NFT. + # The structure is (TokenId, NftIssuance, Destination) + token_id = bytes([11] * 32) + additional_info = { + "Utxo": { + "IssueNft": [ + token_id, + { + "V0": { + "metadata": { + "creator": { + "public_key": { + "key": { + "Secp256k1Schnorr": { + "pubkey_data": bytes([0] * 33) + } + } + } + }, + "name": b"MyAwesomeNFT", + "description": b"FirstNFT", + "ticker": b"MNFT1", + "icon_uri": b"https://my.nft/icon.png", + "additional_metadata_uri": metadata_uri, + "media_uri": b"https://my.nft/media.jpg", + "media_hash": bytes([0] * 32), + } + } + }, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + inp = { + "addresses": [{"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None}], + "input": { + "Utxo": [ + { + "id": {"Transaction": bytes([0] * 32)}, + "index": 1, + }, + additional_info, + ] + }, + } + + inp_commitment = {"commitment": additional_info} + + output = { + "output": { + "Transfer": [ + {"TokenV1": [token_id, 1]}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + + # Sanity checks + assert len(sign_tx_next_req_obj.encode({"ProcessInput": inp}).data) > 500 + assert ( + len( + sign_tx_next_req_obj.encode({"ProcessInputCommitment": inp_commitment}).data + ) + > 500 + ) + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + review_tx = ReviewTransaction( + transaction=transaction, + has_command_input=False, + review_custom_screen_text=r"Sign\stransfer", + ) + sign_tx_review(client, device, navigator, scenario_navigator, review_tx) diff --git a/tests/usage.md b/tests/usage.md new file mode 100644 index 0000000..fcde929 --- /dev/null +++ b/tests/usage.md @@ -0,0 +1,91 @@ +# How to use the Ragger test framework + +This document describes how to run the Mintlayer Ledger app functional tests with the Ragger test +framework, either on the Speculos emulator or on a physical Ledger device. + +## Quickly get started with Ragger and Speculos + +### Install Ragger and dependencies + +In this document we'll be running tests outside of a Docker container, in which case additional +dependencies must be installed: + +```bash +pip install -r tests/requirements.txt +sudo apt-get update && sudo apt-get install qemu-user-static +``` + +### Build the application + +Build the app for the device model that will be used by the tests; you can use the image +`ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder` for this: + +```bash +docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest +docker run --user "$(id -u)":"$(id -g)" --rm -ti -v "$(realpath .):/app" -w /app ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest +``` + +Then, inside the container: + +```bash +cargo ledger build nanox +``` + +ℹ️ `cargo ledger build` accepts `nanox`, `nanosplus`, `stax`, `flex`, and `apex_p`. + +ℹ️ Alternatively, the `ledger-app-dev-tools` image can be used. It already contains tools required to run tests +and must be used if you intend to run them inside the Docker container. + +### Run tests using the Speculos emulator + +Run the functional tests from the repository root: + +```bash +pytest tests/ --tb=short -v --device nanox +``` + +To see the emulator display while tests run, add `--display`: + +```bash +pytest tests/ --tb=short -v --device nanox --display +``` + +⚠️ `--device` specifies the Speculos device model and it should match the binary you've built. +Normally, it will be the same as the `cargo` target that you've passed to `cargo ledger build`, +except for `nanosplus`, whose corresponding device model name is `nanosp`. + +### Run tests using a physical Ledger device + +The Mintlayer app must be built, loaded on the device, and opened before running the tests. +See [Loading on device](../README.md#loading-on-device) for platform-specific loading instructions. + +After the app has been installed on the device, run the tests via: +```bash +pytest tests/ --tb=short -v --device nanox --backend ledgerwallet +``` + +⚠️ Functional tests expect the device to be seeded with the seed phrase "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about". +If this is not the case, most of the tests will fail. + +## Available pytest options + +Standard useful pytest options: + +```text + -v formats the test summary in a readable way; + -s enables logs for successful tests; on Speculos it enables app logs if compiled with DEBUG=1; + -k only runs the tests which match ; the expression may be a single substring to match + against test names and their parent classes, or a combination of them, e.g. '-k foo' or + '-k "(foo or bar) and not baz"'; + --tb=short formats tracebacks in a compact way; +``` + +Custom pytest options: + +```text + --device run tests on the specified device [nanox,nanosp,stax,flex,apex_p,all]; + --backend run tests against [speculos, ledgercomm, ledgerwallet]; Speculos is the default; + --display on Speculos, show the app screen using QT; + --golden_run save current screens instead of comparing them; + --log_apdu_file log all APDU exchanges to the given file; previous file content is erased; +``` diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..1d79e50 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,3 @@ +from pathlib import Path + +ROOT_SCREENSHOT_PATH = Path(__file__).parent.resolve() diff --git a/tools/codecheck.py b/tools/codecheck.py new file mode 100755 index 0000000..847d8ed --- /dev/null +++ b/tools/codecheck.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 + +import itertools +import os +import pathlib +import re +import sys + +PROJECT_ROOT_DIR = pathlib.Path(__file__).resolve().parent.parent + +LICENSE_TEMPLATE1 = [ + r"/\*{77}", + r" \* Mintlayer Ledger App\.", + r" \* \(c\) 202[0-9](-202[0-9])? RBB S\.r\.l\.", + r" \*", + r' \* Licensed under the Apache License, Version 2\.0 \(the "License"\);', + r" \* you may not use this file except in compliance with the License\.", + r" \* You may obtain a copy of the License at", + r" \*", + r" \* http://www\.apache\.org/licenses/LICENSE-2\.0", + r" \*", + r" \* Unless required by applicable law or agreed to in writing, software", + r' \* distributed under the License is distributed on an "AS IS" BASIS,', + r" \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.", + r" \* See the License for the specific language governing permissions and", + r" \* limitations under the License\.", + r" \*{77}/", +] + +LICENSE_TEMPLATE2 = [ + r"/\*{77}", + r" \* Mintlayer Ledger App\.", + r" \* \(c\) 202[0-9](-202[0-9])? Ledger SAS\.", + r" \* \(c\) 202[0-9](-202[0-9])? RBB S\.r\.l\.", + r" \*", + r' \* Licensed under the Apache License, Version 2\.0 \(the "License"\);', + r" \* you may not use this file except in compliance with the License\.", + r" \* You may obtain a copy of the License at", + r" \*", + r" \* http://www\.apache\.org/licenses/LICENSE-2\.0", + r" \*", + r" \* Unless required by applicable law or agreed to in writing, software", + r' \* distributed under the License is distributed on an "AS IS" BASIS,', + r" \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.", + r" \* See the License for the specific language governing permissions and", + r" \* limitations under the License\.", + r" \*{77}/", +] + +COMMON_EXCLUDE_DIRS = ["target", ".git", ".mypy_cache"] + + +# List source files with the given extension. If the extension is None, list all files. +def sources_with_extension(ext: str | None, top_dir=".", exclude=[]): + exclude_full_paths = [ + os.path.normpath(rel_path) for rel_path in COMMON_EXCLUDE_DIRS + exclude + ] + exclude_names = ["__pycache__"] + + def is_excluded(dirpath, entry_name): + return ( + entry_name in exclude_names + or os.path.normpath(os.path.join(dirpath, entry_name).lower()) + in exclude_full_paths + ) + + for dirpath, dirnames, filenames in os.walk(top_dir, topdown=True): + dirnames[:] = [d for d in dirnames if not is_excluded(dirpath, d)] + for filename in filenames: + if not is_excluded(dirpath, filename): + if ext is None or os.path.splitext(filename)[1].lower() == ext: + yield os.path.join(dirpath, filename) + + +# List all files +def all_files(exclude=[]): + return sources_with_extension(None, exclude=exclude) + + +# List Rust source files +def rs_sources(exclude=[]): + return sources_with_extension(".rs", exclude=exclude) + + +# List Cargo config files +def cargo_config_files(exclude=[]): + return sources_with_extension(".toml", exclude=exclude) + + +# List Python source files +def py_sources(exclude=[]): + return sources_with_extension(".py", exclude=exclude) + + +# List GitHub workflow files +def github_workflows(exclude=[]): + return sources_with_extension(".yml", top_dir=".github", exclude=exclude) + + +# Check license header in Rust source files +def check_local_licenses(): + print("==== Checking local license headers:") + + # List of files/dirs excluded from the check + exclude = [] + + template1 = re.compile("(?:" + r")\n(?:".join(LICENSE_TEMPLATE1) + ")") + template2 = re.compile("(?:" + r")\n(?:".join(LICENSE_TEMPLATE2) + ")") + + ok = True + for path in rs_sources(exclude): + with open(path, "r", encoding="utf-8") as file: + file_contents = file.read() + if not (template1.search(file_contents) or template2.search(file_contents)): + ok = False + print("{}: license missing or incorrect".format(path)) + + print() + return ok + + +# Check TODO(PR) and FIXME instances +def check_todos(): + print("==== Checking TODO(PR) and FIXME instances:") + + # List of files/dirs excluded from the check + exclude = [ + # Exclude itself + "tools/codecheck.py", + ] + + ok = True + for path in itertools.chain( + rs_sources(exclude), + cargo_config_files(exclude), + py_sources(exclude), + github_workflows(exclude), + ): + with open(path, "r", encoding="utf-8") as file: + file_data = file.read() + if "TODO(PR)" in file_data or "FIXME" in file_data: + ok = False + print("{}: found TODO(PR) or FIXME instances".format(path)) + + print() + return ok + + +# Check for trailing whitespaces +def check_trailing_whitespaces(): + print("==== Checking for trailing whitespaces:") + + # List of files/dirs excluded from the check + exclude = ["media", "tests/snapshots"] + + ok = True + for path in all_files(exclude): + with open(path, "r", encoding="utf-8") as file: + try: + for line_idx, line in enumerate(file, start=1): + line = line.rstrip("\n\r") + if line != line.rstrip(): + ok = False + print(f"{path}: trailing whitespaces at line {line_idx}") + except: + ok = False + print( + f"{path}: can't check for trailing whitespaces, " + "perhaps the file should be in the 'exclude' list?" + ) + + print() + return ok + + +def run_checks(): + return all( + [ + check_local_licenses(), + check_todos(), + check_trailing_whitespaces(), + ] + ) + + +if __name__ == "__main__": + # Note: this script expects the current directory to be the project root. + os.chdir(PROJECT_ROOT_DIR) + + if not run_checks(): + sys.exit(1) diff --git a/tools/get_target_id.py b/tools/get_target_id.py new file mode 100755 index 0000000..364fc80 --- /dev/null +++ b/tools/get_target_id.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Extract the `ledger.target_id` section from a Ledger app ELF.""" + +import argparse +import sys + +try: + from elftools.elf.elffile import ELFFile +except ModuleNotFoundError as e: + raise SystemExit( + "Missing dependency 'pyelftools'. Install it with: python3 -m pip install pyelftools" + ) from e + + +def get_target_id(elf_path: str) -> str: + with open(elf_path, "rb") as f: + elf = ELFFile(f) + section = elf.get_section_by_name("ledger.target_id") + if section is None: + raise SystemExit(f"Section 'ledger.target_id' not found in {elf_path}") + return section.data().decode("ascii").strip() + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("elf", help="Path to the Ledger app ELF file") + args = parser.parse_args() + + print(get_target_id(args.elf)) + return 0 + + +if __name__ == "__main__": + sys.exit(main())