diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..cd2f521 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,20 @@ +[target.apex_p] +runner = "speculos -m apex_p" + +[build] +target = "apex_p" + +[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..014f22f --- /dev/null +++ b/.github/workflows/build_and_functional_tests.yml @@ -0,0 +1,44 @@ +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 + +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..7f91930 --- /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/guidelines_enforcer.yml b/.github/workflows/guidelines_enforcer.yml new file mode 100644 index 0000000..fdaf9f2 --- /dev/null +++ b/.github/workflows/guidelines_enforcer.yml @@ -0,0 +1,23 @@ +name: Ensure compliance with Ledger guidelines + +# This workflow is mandatory in all applications +# It calls a reusable workflow guidelines_enforcer developed by Ledger's internal developer team. +# The successful completion of the reusable workflow is a mandatory step for an app to be available on the Ledger +# application store. +# +# More information on the guidelines can be found in the repository: +# LedgerHQ/ledger-app-workflows/ + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + guidelines_enforcer: + name: Call Ledger guidelines_enforcer + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 diff --git a/.github/workflows/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/.gitignore b/.gitignore new file mode 100644 index 0000000..814b669 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +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/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5b46788 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1659 @@ +# 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.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +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 2.9.1", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.104", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[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" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[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", +] + +[[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-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 = "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.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "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.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", +] + +[[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.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[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 = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png 0.17.16", + "qoi", + "tiff 0.9.1", +] + +[[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 0.18.0", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff 0.10.3", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[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.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed329eea6ba198654ceab2c4dd41637d4890dc3c2bba845a57b5f81dac6d6a6" +dependencies = [ + "flate2", + "image 0.24.9", + "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 = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +dependencies = [ + "rayon", +] + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "ledger_device_sdk" +version = "1.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c1588f1d6e99ed03534edb661977f5853e4af14ca3397aa2f69447d25f093d" +dependencies = [ + "const-zero", + "include_gif", + "ledger_secure_sdk_sys", + "num-traits", + "numtoa", + "rand_core", + "zeroize", +] + +[[package]] +name = "ledger_secure_sdk_sys" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38075ebc426861a7768328783e0dc4c4bac1026b1aafca052c6795b8cf398a91" +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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +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 = "messages" +version = "0.1.0" +dependencies = [ + "mintlayer-core-primitives", + "num_enum", + "parity-scale-codec", +] + +[[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 = "0.1.0" +dependencies = [ + "bech32", + "chrono", + "hex", + "image 0.25.8", + "ledger_device_sdk", + "ledger_secure_sdk_sys", + "messages", + "mintlayer-core-primitives", +] + +[[package]] +name = "mintlayer-core-primitives" +version = "1.0.0" +source = "git+https://github.com/mintlayer/mintlayer-core-primitives?rev=13b10dbc88efdf3b5aa31ece8e34278bc69a5a9b#13b10dbc88efdf3b5aa31ece8e34278bc69a5a9b" +dependencies = [ + "derive_more", + "fixed-hash", + "parity-scale-codec", + "strum", +] + +[[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 = "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.1", + "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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.104", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[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", + "rand_chacha", + "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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +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.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[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_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 = "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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[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 = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +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.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +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 = "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..edca7a1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "mintlayer-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +messages = { path = "./messages" } +ledger_device_sdk = "1.30.0" +ledger_secure_sdk_sys = "1.13.0" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +bech32 = { version = "0.11", default-features = false, features = ["alloc"] } +chrono = { version = "0.4", default-features = false, features = ["alloc"] } + +mintlayer-core-primitives = { git = "https://github.com/mintlayer/mintlayer-core-primitives", rev = "13b10dbc88efdf3b5aa31ece8e34278bc69a5a9b", package = "mintlayer-core-primitives" } + +[build-dependencies] +image = "0.25.8" + +[features] +default = ["ledger_device_sdk/nano_nbgl"] +debug = ["ledger_device_sdk/debug"] + +[package.metadata.ledger] +curve = ["secp256k1"] +flags = "0" +path = ["44'/19788'", "44'/1'"] +name = "Mintlayer" + +[package.metadata.ledger.nanox] +icon = "icons/mintlayer_14x14.gif" + +[package.metadata.ledger.nanosplus] +icon = "icons/mintlayer_14x14.gif" + +[package.metadata.ledger.stax] +icon = "icons/mintlayer_32x32.gif" + +[package.metadata.ledger.flex] +icon = "icons/mintlayer_40x40.gif" + +[package.metadata.ledger.apex_p] +icon = "icons/mintlayer_32x32.png" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(target_os, values("apex_p", "stax", "flex", "nanos", "nanox", "nanosplus"))', +] } 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..ee3d99f --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# 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, S+, Stax and Flex devices. + +:warning: Nano S is not supported + +## Quick start guide + +### With VS Code + +You can quickly setup 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 [VScode](https://code.visualstudio.com/download) and add [Ledger's extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools). +- Open a terminal and clone `app-boilerplate-rust` with `git clone git@github.com:LedgerHQ/app-boilerplate-rust.git`. +- Open the `app-boilerplate-rust` folder with VSCode. +- Use Ledger extension's sidebar menu or open the tasks menu with `ctrl + shift + b` (`command + shift + b` on a Mac) to conveniently execute actions : + - **Build** the app for the device model of your choice with `Build`. + - **Test** your binary on 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 VSCode 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 setup 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: + +```shell +sudo 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): + ```shell + sudo docker run --rm -ti --privileged -v "/dev/bus/usb:/dev/bus/usb" -v "$(realpath .):/app" --publish 5001:5001 --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: + ```shell + sudo docker run --rm -ti --privileged -v "$(pwd -P):/app" --publish 5001:5001 --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): + ```shell + docker run --rm -ti --privileged -v "$(Get-Location):/app" -e DISPLAY='host.docker.internal:0' --publish 5001:5001 --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`) + +### 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 : + +```shell +pytest tests/ --tb=short -v --device {nanosp | nanox | stax | flex} +``` + +#### 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 +``` + +:warning: UI is displayed on `localhost:5000` + +#### Stax or Flex + +```bash +speculos --apdu-port 9999 --api-port 5000 --model stax target/stax/release/mintlayer-app +``` + +You can also specify the seed phrase with -s "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + +:warning: UI is displayed by your X server + +You can then send APDU using `ledgercomm` (`pip install ledgercomm`): + +``` +ledgercomm-send file test.apdu +``` + +### Loading on device + +:warning: Loading the built application on a device shall be performed out of the Docker container, by using [ledgerctl](https://github.com/LedgerHQ/ledgerctl): + +```shell +pip3 install ledgerwallet +``` + +ℹ️ Your device must be connected, unlocked and the screen showing the dashboard (not inside an application). + +For instance, for Flex: + +```bash +ledgerctl install -f target/flex/release/app_flex.json +``` + +## 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 : + - Source code lint checks with `cargo fmt` + - Python functional test code lint checks with `pylint` and `mypy` diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..89a3680 --- /dev/null +++ b/build.rs @@ -0,0 +1,49 @@ +use std::process::Command; + +use image::{ImageFormat, ImageReader, Pixel}; + +fn main() { + println!("cargo:rerun-if-changed=script.ld"); + println!("cargo:rerun-if-changed=icons/mintlayer_14x14.gif"); + println!("cargo:rerun-if-changed=icons/mask_14x14.gif"); + + let path = std::path::PathBuf::from("icons"); + let reader = ImageReader::open(path.join("mintlayer_14x14.gif")).unwrap(); + let img = reader.decode().unwrap(); + let mut gray = img.into_luma8(); + + // Apply mask + let mask = ImageReader::open(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("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 + 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/glyphs/home_nano_nbgl.png b/glyphs/home_nano_nbgl.png new file mode 100644 index 0000000..4fa2028 Binary files /dev/null and b/glyphs/home_nano_nbgl.png differ diff --git a/glyphs/mintlayer_48x48.png b/glyphs/mintlayer_48x48.png new file mode 100644 index 0000000..991e98d Binary files /dev/null and b/glyphs/mintlayer_48x48.png differ diff --git a/glyphs/mintlayer_64x64.gif b/glyphs/mintlayer_64x64.gif new file mode 100644 index 0000000..d5bdcef Binary files /dev/null and b/glyphs/mintlayer_64x64.gif differ diff --git a/icons/mask_14x14.gif b/icons/mask_14x14.gif new file mode 100644 index 0000000..1568a9f Binary files /dev/null and b/icons/mask_14x14.gif differ diff --git a/icons/mintlayer_14x14.gif b/icons/mintlayer_14x14.gif new file mode 100644 index 0000000..df802c2 Binary files /dev/null and b/icons/mintlayer_14x14.gif differ diff --git a/icons/mintlayer_16x16.gif b/icons/mintlayer_16x16.gif new file mode 100644 index 0000000..1e53099 Binary files /dev/null and b/icons/mintlayer_16x16.gif differ diff --git a/icons/mintlayer_32x32.gif b/icons/mintlayer_32x32.gif new file mode 100644 index 0000000..c1dbbf1 Binary files /dev/null and b/icons/mintlayer_32x32.gif differ diff --git a/icons/mintlayer_32x32.png b/icons/mintlayer_32x32.png new file mode 100644 index 0000000..790b9cd Binary files /dev/null and b/icons/mintlayer_32x32.png differ diff --git a/icons/mintlayer_40x40.gif b/icons/mintlayer_40x40.gif new file mode 100644 index 0000000..c2532c5 Binary files /dev/null and b/icons/mintlayer_40x40.gif differ diff --git a/icons/mintlayer_64x64.gif b/icons/mintlayer_64x64.gif new file mode 100644 index 0000000..d5bdcef Binary files /dev/null and b/icons/mintlayer_64x64.gif differ 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/messages/Cargo.toml b/messages/Cargo.toml new file mode 100644 index 0000000..a4ecbd3 --- /dev/null +++ b/messages/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "messages" +version = "0.1.0" +edition = "2024" + +[dependencies] +# 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. +parity-scale-codec = { git = "https://github.com/paritytech/parity-scale-codec.git", rev = "5021525697edc0661591ebc71392c48d950a10b0", default-features = false, features = [ + "derive", + "chain-error", +] } +mintlayer-core-primitives = { git = "https://github.com/mintlayer/mintlayer-core-primitives", rev = "13b10dbc88efdf3b5aa31ece8e34278bc69a5a9b", package = "mintlayer-core-primitives" } +num_enum = { version = "0.7.5", default-features = false } diff --git a/messages/src/lib.rs b/messages/src/lib.rs new file mode 100644 index 0000000..9cd8433 --- /dev/null +++ b/messages/src/lib.rs @@ -0,0 +1,366 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025 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] + +// Required for using String, Vec, format!... +extern crate alloc; + +use alloc::vec::Vec; + +pub use mintlayer_core_primitives::{ + AccountCommand, AccountNonce, AccountOutPoint, AccountSpending, Amount, CoinType as PCoinType, + Destination, H256, HashedTimelockContract, HtlcSecretHash, Id, IsTokenFreezable, + IsTokenUnfreezable, NftIssuance, OrderAccountCommand, OrderData, OutPointSourceId, + OutputTimeLock, OutputValue, PublicKey, PublicKeyHash, SchnorrkelPublicKey, Secp256k1PublicKey, + SighashInputCommitment, StakePoolData, TokenIssuance, TokenTotalSupply, TxInput, TxOutput, + UtxoOutPoint, VrfPublicKey, +}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +pub use parity_scale_codec::Encode; +use parity_scale_codec::{Decode, DecodeAll}; + +pub const APDU_CLASS: u8 = 0xE1; +pub const MAX_ADPU_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; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] +#[num_enum(error_type(name = WrongP1P2, constructor = wrong_p1p2))] +#[repr(u8)] +pub enum PubKeyP1 { + NoDisplayAddress = 0, + DisplayAddress = 1, +} + +impl PubKeyP1 { + pub fn display(&self) -> bool { + *self == Self::DisplayAddress + } +} + +pub struct Ins {} + +impl Ins { + pub const PUB_KEY: u8 = 0x00; + pub const SIGN_TX: u8 = 0x01; + pub const SIGN_MSG: u8 = 0x02; +} + +pub struct WrongP1P2; +fn wrong_p1p2(_: u8) -> WrongP1P2 { + WrongP1P2 +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)] +#[num_enum(error_type(name = WrongP1P2, constructor = wrong_p1p2))] +#[repr(u8)] +pub enum SignP1 { + Start = 0, + Next = 1, +} + +#[derive(Encode, Decode)] +pub struct PublicKeyReq { + pub coin_type: CoinType, + pub path: Bip32Path, +} + +#[derive(Encode, Decode)] +pub struct SignMessageReq { + pub coin: CoinType, + pub addr_type: AddrType, + pub path: Bip32Path, +} + +#[derive(Encode, Decode)] +pub enum SignTxReq { + Input(TxInputReq), + InputCommitment(SighashInputCommitment), + Output(TxOutputReq), + NextSignature, +} + +#[derive(Encode, Decode)] +pub struct TxMetadataReq { + pub coin: CoinType, + pub version: u8, + pub num_inputs: u32, + pub num_outputs: u32, +} + +#[derive(Encode, Decode)] +pub struct TxInputReq { + pub addresses: Vec, + pub inp: TxInputWithAdditionalInfo, +} + +#[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 { + Utxo(TxOutput), + PoolData { + utxo: TxOutput, + staker_balance: Amount, + }, +} + +impl From for SighashInputCommitment { + fn from(value: AdditionalUtxoInfo) -> Self { + match value { + AdditionalUtxoInfo::Utxo(output) => SighashInputCommitment::Utxo(output), + AdditionalUtxoInfo::PoolData { + 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)] +pub struct TxOutputReq { + pub out: TxOutput, +} + +#[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 PCoinType { + 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 enum Prerelease { + Alpha, + Beta, +} + +#[derive(Encode, Decode)] +pub struct GetVersionRespones { + pub major: u8, + pub minor: u8, + pub patch: u8, + pub prerelease_id: Option, + pub build_metadata: Vec, +} + +#[derive(Encode, Decode)] +pub struct GetPublicKeyRespones { + pub public_key: [u8; 65], + pub chain_code: [u8; 32], +} + +#[derive(Encode, Decode)] +pub struct Signature { + pub signature: [u8; 64], + pub multisig_idx: Option, +} + +#[derive(Encode, Decode)] +pub struct MsgSignature { + pub signature: [u8; 64], +} + +pub fn encode(t: T) -> Vec { + t.encode() +} + +pub fn encode_to(t: T, buf: &mut Vec) { + t.encode_to(buf) +} + +pub fn decode_all(mut bytes: &[u8]) -> Option { + T::decode_all(&mut bytes).ok() +} + +pub fn encode_as_compact(num: u32) -> Vec { + parity_scale_codec::Compact::::encode(&num.into()) +} + +/// 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_ADPU_DATA_LEN).then(|| Self { + instruction_byte, + param1_byte, + command_data, + is_last_chunk: true, + }) + } + + /// Creates a Vec of APDUs by chunking the data to MAX_ADPU_DATA_LEN + pub fn new_chunks(instruction_byte: u8, param1_byte: u8, data: &'a [u8]) -> Vec { + let mut adpus = Vec::new(); + let mut chunks_iter = data.chunks(MAX_ADPU_DATA_LEN).peekable(); + while let Some(chunk) = chunks_iter.next() { + let apdu = Self { + instruction_byte, + param1_byte, + command_data: chunk, + is_last_chunk: chunks_iter.peek().is_none(), + }; + adpus.push(apdu); + } + adpus + } + + 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()); + } +} 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/app_ui/address.rs b/src/app_ui/address.rs new file mode 100644 index 0000000..0d3bce8 --- /dev/null +++ b/src/app_ui/address.rs @@ -0,0 +1,78 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025 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::{app_ui::utils::to_address, StatusWord}; +use messages::{Destination, PCoinType, PublicKey, Secp256k1PublicKey}; + +use ledger_device_sdk::{ + ecc::ECPublicKey, + include_gif, + nbgl::{NbglAddressReview, NbglGlyph}, +}; + +pub fn compress_public_key( + public_key: &ECPublicKey<65, T>, +) -> Result<[u8; 33], StatusWord> { + 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] % 2 == 0 { + 0x02 + } else { + 0x03 + }; + + compressed_key[0] = prefix; + + let x_coordinate = &uncompressed_key[1..33]; + compressed_key[1..].copy_from_slice(x_coordinate); + + Ok(compressed_key) +} + +pub fn ui_display_pk( + public_key: &ECPublicKey<65, T>, + coin_type: PCoinType, +) -> Result { + let pk = compress_public_key(public_key)?; + + let dest = Destination::PublicKey(PublicKey::Secp256k1Schnorr(Secp256k1PublicKey(pk))); + let addr = to_address(&dest, coin_type)?; + + // Load glyph from file with include_gif macro. Creates an NBGL compatible glyph. + #[cfg(target_os = "apex_p")] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_48x48.png", NBGL)); + #[cfg(any(target_os = "stax", target_os = "flex"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_64x64.gif", NBGL)); + #[cfg(any(target_os = "nanosplus", target_os = "nanox"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("icons/mintlayer_14x14.gif", NBGL)); + + // Display the address confirmation screen. + Ok(NbglAddressReview::new() + .glyph(&FERRIS) + .review_title("Verify ML address") + .show(&addr)) +} diff --git a/src/app_ui/menu.rs b/src/app_ui/menu.rs new file mode 100644 index 0000000..1957e81 --- /dev/null +++ b/src/app_ui/menu.rs @@ -0,0 +1,51 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025 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::{ + include_gif, + io::Comm, + nbgl::{NbglGlyph, NbglHomeAndSettings}, +}; + +use crate::settings::Settings; + +pub fn ui_menu_main(_: &mut Comm) -> NbglHomeAndSettings { + // Load glyph from file with include_gif macro. Creates an NBGL compatible glyph. + #[cfg(target_os = "apex_p")] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_48x48.png", NBGL)); + #[cfg(any(target_os = "stax", target_os = "flex"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_64x64.gif", NBGL)); + #[cfg(any(target_os = "nanosplus", target_os = "nanox"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("icons/mintlayer_14x14.gif", NBGL)); + + let settings_strings = [["Display Memo", "Allow display of transaction memo."]]; + let mut settings: Settings = Default::default(); + + // Display the home screen. + NbglHomeAndSettings::new() + .glyph(&FERRIS) + .settings(settings.get_mut(), &settings_strings) + .infos( + "Mintlayer", + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_AUTHORS"), + ) +} diff --git a/src/app_ui/sign.rs b/src/app_ui/sign.rs new file mode 100644 index 0000000..1e75919 --- /dev/null +++ b/src/app_ui/sign.rs @@ -0,0 +1,536 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025 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 alloc::vec::Vec; +use alloc::{format, string::ToString}; +use core::fmt::Write; + +use crate::{ + app_ui::{ + address::compress_public_key, + utils::{bech32m_encode, to_address}, + }, + handlers::sign_tx::{CoinOrTokenId, TxContext, TxType}, + StatusWord, +}; +use messages::{ + encode, AddrType, Amount, Destination, IsTokenFreezable, NftIssuance, OutputTimeLock, + OutputValue, PCoinType, PublicKey, PublicKeyHash, Secp256k1PublicKey, TokenIssuance, + TokenTotalSupply, TxOutput, VrfPublicKey, H256, +}; + +use chrono::{TimeZone, Utc}; +use ledger_device_sdk::{ + ecc::ECPublicKey, + hash::{blake2::Blake2b_512, HashInit}, + include_gif, + nbgl::{ + Field, NbglGlyph, NbglReview, NbglStreamingReview, NbglStreamingReviewStatus, + TransactionType, + }, +}; + +pub fn new_streaming_review() -> NbglStreamingReview { + // Load glyph from file with include_gif macro. Creates an NBGL compatible glyph. + #[cfg(target_os = "apex_p")] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_48x48.png", NBGL)); + #[cfg(any(target_os = "stax", target_os = "flex"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_64x64.gif", NBGL)); + #[cfg(any(target_os = "nanosplus", target_os = "nanox"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("icons/mintlayer_14x14.gif", NBGL)); + + NbglStreamingReview::new() + .glyph(&FERRIS) + .tx_type(TransactionType::Transaction) +} + +pub fn start_streaming_review(review: &NbglStreamingReview) -> bool { + review.start("Review transaction", None) +} + +pub fn streaming_review_show_output( + review: &NbglStreamingReview, + output: &TxOutput, + coin: PCoinType, +) -> Result { + let (name, value) = format_output(output, coin)?; + let fields = [Field { + name, + value: &value, + }]; + + let res = match review.next(&fields) { + NbglStreamingReviewStatus::Rejected => false, + NbglStreamingReviewStatus::Next | NbglStreamingReviewStatus::Skipped => true, + }; + + Ok(res) +} + +pub fn approve_streaming_review( + review: &NbglStreamingReview, + output: &TxOutput, + ctx: &TxContext, +) -> Result { + if !streaming_review_show_output(review, output, ctx.coin())? { + return Ok(false); + } + + let fees = ctx.total_inputs().iter().try_fold( + String::new(), + |mut acc, (coin_or_token, amount)| -> Result<_, StatusWord> { + let out = *ctx + .total_outputs() + .get(coin_or_token) + .unwrap_or(&Amount::ZERO); + let fee: u128 = amount + .into_atoms() + .checked_sub(out.into_atoms()) + .ok_or(StatusWord::TxNumericOperationFail)?; + + match coin_or_token { + CoinOrTokenId::Coin => writeln!( + acc, + "{} {}", + format_amount(Amount::from_atoms(fee), ctx.coin()), + ctx.coin().coin_ticker() + ) + .map_err(|_| StatusWord::TxDisplayFail)?, + CoinOrTokenId::TokenId(token_id) => { + if fee != 0 { + writeln!( + acc, + "{fee} {}", + id_to_address(token_id, ctx.coin().token_id_address_prefix())? + ) + .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(ctx); + Ok(review.finish(title)) +} + +/// Displays a transaction and returns true if user approved it. +/// +/// This method can return [`AppSW::TxDisplayFail`] error if the coin name length is too long. +/// +/// # Arguments +/// +/// * `ctx` - TxContext to be displayed for validation +pub fn ui_display_tx(ctx: &TxContext, outputs: &[TxOutput]) -> Result { + let fees = ctx.total_inputs().iter().try_fold( + String::new(), + |mut acc, (coin_or_token, amount)| -> Result<_, StatusWord> { + let out = *ctx + .total_outputs() + .get(coin_or_token) + .unwrap_or(&Amount::ZERO); + let fee: u128 = amount + .into_atoms() + .checked_sub(out.into_atoms()) + .ok_or(StatusWord::TxNumericOperationFail)?; + + match coin_or_token { + CoinOrTokenId::Coin => writeln!( + acc, + "{} {}", + format_amount(Amount::from_atoms(fee), ctx.coin()), + ctx.coin().coin_ticker() + ) + .map_err(|_| StatusWord::TxDisplayFail)?, + CoinOrTokenId::TokenId(token_id) => { + if fee != 0 { + writeln!( + acc, + "{fee} {}", + id_to_address(token_id, ctx.coin().token_id_address_prefix())? + ) + .map_err(|_| StatusWord::TxDisplayFail)? + } + } + }; + + Ok(acc) + }, + )?; + + let formated_outputs: Vec<(&str, String)> = outputs + .iter() + .map(|out| format_output(out, ctx.coin())) + .collect::, _>>()?; + + // Define transaction review fields + let my_fields: Vec<_> = formated_outputs + .iter() + .map(|(name, value)| Field { name, value }) + .chain([Field { + name: "Fees:", + value: &fees, + }]) + .collect(); + + // Create transaction review + + // Load glyph from file with include_gif macro. Creates an NBGL compatible glyph. + #[cfg(target_os = "apex_p")] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_48x48.png", NBGL)); + #[cfg(any(target_os = "stax", target_os = "flex"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_64x64.gif", NBGL)); + #[cfg(any(target_os = "nanosplus", target_os = "nanox"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("icons/mintlayer_14x14.gif", NBGL)); + + let title = transaction_title(ctx); + + // Create NBGL review. Maximum number of fields and string buffer length can be customised + // with constant generic parameters of NbglReview. Default values are 32 and 1024 respectively. + let review: NbglReview = NbglReview::new() + .titles("Review transaction", "", title) + .glyph(&FERRIS); + + Ok(review.show(&my_fields)) +} + +fn transaction_title(tx: &TxContext) -> &'static str { + match tx.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::DelegationStake) => "Sign stake Delegation transaction", + Some(TxType::DelegationWithdrawl) => "Sign withdrawal Delegation 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` - A byte slice (`&[u8]`) containing the message to be signed. +/// +/// # Returns +/// +/// * `Ok(true)` if the user approves the signing. +/// * `Ok(false)` if the user rejects. +/// * `Err(AppSW)` on error. +pub fn ui_display_message( + message: &[u8], + public_key: &ECPublicKey<65, T>, + coin_type: PCoinType, + addr_type: AddrType, +) -> Result { + let pk = compress_public_key(public_key)?; + + let dest = match addr_type { + AddrType::PublicKey => { + Destination::PublicKey(PublicKey::Secp256k1Schnorr(Secp256k1PublicKey(pk))) + } + AddrType::PublicKeyHash => { + let mut blake2b256 = Blake2b_512::new(); + let mut public_key_hash: [u8; 64] = [0u8; 64]; + + blake2b256 + .update(&[0]) + .map_err(|_| StatusWord::TxHashFail)?; + blake2b256.update(&pk).map_err(|_| StatusWord::TxHashFail)?; + + blake2b256 + .finalize(&mut public_key_hash) + .map_err(|_| StatusWord::TxHashFail)?; + + let mut pkh = [0u8; 20]; + pkh.copy_from_slice(&public_key_hash[0..20]); + + Destination::PublicKeyHash(PublicKeyHash(pkh)) + } + }; + let addr = to_address(&dest, coin_type)?; + + let message_str = match core::str::from_utf8(message) { + Ok(s) if s.is_ascii() => s.to_string(), + Ok(_) | Err(_) => format!("0x{}", hex::encode(message)), + }; + + let my_fields = [ + Field { + name: "Address", + value: addr.as_str(), + }, + Field { + name: "Message", + value: message_str.as_str(), + }, + ]; + + // Load glyph from file with include_gif macro. Creates an NBGL compatible glyph. + #[cfg(target_os = "apex_p")] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_48x48.png", NBGL)); + #[cfg(any(target_os = "stax", target_os = "flex"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("glyphs/mintlayer_64x64.gif", NBGL)); + #[cfg(any(target_os = "nanosplus", target_os = "nanox"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("icons/mintlayer_14x14.gif", NBGL)); + + // Create the NBGL review flow with titles appropriate for message signing. + let review: NbglReview = NbglReview::new() + .titles( + "Review message", // Initial title + "Cannot be undone", // Warning on the second screen + "Sign message", // Final confirmation prompt + ) + .tx_type(TransactionType::Message) + .glyph(&FERRIS); + + // Show the review screen with the defined fields and return the user's choice. + Ok(review.show(&my_fields)) +} + +fn vrf_to_address(key: &VrfPublicKey, coin: PCoinType) -> 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 format_amount(amount: Amount, coin: PCoinType) -> 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 +} + +fn format_atoms(amount: Amount) -> String { + amount.into_atoms().to_string() +} + +fn format_value(value: &OutputValue, coin: PCoinType) -> Result { + match value { + OutputValue::Coin(amount) => Ok(format!("Coins: {}", format_amount(*amount, coin))), + OutputValue::TokenV1(token_id, amount) => Ok(format!( + "Token: {} {}", + id_to_address(token_id.hash(), coin.token_id_address_prefix())?, + format_atoms(*amount) + )), + } +} + +fn format_timestamp(seconds_u64: u64) -> Result { + let seconds_i64: i64 = seconds_u64 + .try_into() + .map_err(|_| StatusWord::TxDisplayFail)?; + let datetime = Utc + .timestamp_opt(seconds_i64, 0) + .earliest() + .ok_or(StatusWord::TxDisplayFail)?; + + 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 tuple of (short_address, amount, address_label). +/// +/// # Arguments +/// * `output` - A reference to the `TxOutput` enum variant to format. +/// * `coin` - A reference to the coin information, used for formatting amounts. +/// +/// # Returns +/// A tuple containing three `String`s: `(short_address, amount, address_label)`. +pub fn format_output(output: &TxOutput, coin: PCoinType) -> Result<(&str, String), StatusWord> { + let res = 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)? + ); + ("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", + id_to_address(pool_id.hash(), coin.pool_id_address_prefix())?, 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_amount(data.cost_per_block, coin), + format_amount(data.pledge, coin)); + ("Create staking pool", address_short) + } + + TxOutput::ProduceBlockFromStake(destination, _pool_id) => ( + "Produce block from stake", + format!("New staker key: {}", to_address(destination, coin)?), + ), + + TxOutput::CreateDelegationId(destination, pool_id) => { + let address_short = format!( + "Address: {}\nPoolId: {}", + to_address(destination, coin)?, + id_to_address(pool_id.hash(), coin.pool_id_address_prefix())? + ); + ("Create delegation", address_short) + } + + TxOutput::DelegateStaking(amount, delegation_id) => ( + "Delegate staking", + format!( + "{}\n{}", + id_to_address(delegation_id.hash(), coin.delegation_id_address_prefix())?, + format_value(&OutputValue::Coin(*amount), coin)? + ), + ), + + TxOutput::IssueFungibleToken(x) => { + let TokenIssuance::V1(data) = x; + + let ticker = String::from_utf8_lossy(data.token_ticker.as_ref()); + let metadata_uri = String::from_utf8_lossy(data.metadata_uri.as_ref()); + + 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_amount(amount, coin); + 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 + ); + ("Issue fungible token", address_short) + } + + TxOutput::IssueNft(_nft_id, data, destination) => { + let data = match data { + NftIssuance::V0(data) => data, + }; + let address_short = format!( + "Name: {}\nCreator: {}\nTicker: {}\nAddress: {}\nIcon URI: {}\nAdditional medatada URI: {}\nMedia URI: {}", + String::from_utf8_lossy(data.name.as_ref()), + data.creator.clone().map(|creator| to_address(&Destination::PublicKey(creator), coin)).transpose()?.unwrap_or_default(), + String::from_utf8_lossy(data.ticker.as_ref()), + to_address(destination, coin)?, + String::from_utf8_lossy(data.icon_uri.as_ref()), + String::from_utf8_lossy(data.additional_metadata_uri.as_ref()), + String::from_utf8_lossy(data.media_uri.as_ref()) + ); + ("Issue NFT token", address_short) + } + + TxOutput::DataDeposit(data) => ("Data deposit", hex::encode(data)), + + TxOutput::Htlc(value, data) => { + let address_short = format!( + "Secret hash: {}\nSpend key: {}\nRefund key: {}\nRefund time lock: {}\n{}", + hex::encode(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(res) +} diff --git a/src/app_ui/utils.rs b/src/app_ui/utils.rs new file mode 100644 index 0000000..fc292b3 --- /dev/null +++ b/src/app_ui/utils.rs @@ -0,0 +1,35 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025 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 crate::StatusWord; +use messages::{encode, Destination, PCoinType}; + +pub fn bech32m_encode(hrp: &str, data: &[u8]) -> Result { + let parsed_hrp = bech32::Hrp::parse(hrp).map_err(|_| StatusWord::TxAddressFail)?; + + let encoded = bech32::encode::(parsed_hrp, data) + .map_err(|_| StatusWord::TxAddressFail)?; + + Ok(encoded) +} + +pub fn to_address(destination: &Destination, coin: PCoinType) -> Result { + let hrp = coin.address_prefix(destination.into()); + bech32m_encode(hrp, &encode(destination)) +} diff --git a/src/handlers/get_public_key.rs b/src/handlers/get_public_key.rs new file mode 100644 index 0000000..035988c --- /dev/null +++ b/src/handlers/get_public_key.rs @@ -0,0 +1,54 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025 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::app_ui::address::ui_display_pk; +use crate::StatusWord; +use messages::{encode, GetPublicKeyRespones, PCoinType, PublicKeyReq}; + +use ledger_device_sdk::ecc::{Secp256k1, SeedDerive}; +use ledger_device_sdk::io::Comm; + +pub fn handle_get_public_key( + comm: &mut Comm, + req: PublicKeyReq, + display: bool, +) -> Result<(), StatusWord> { + if req.path.as_ref().len() < 3 { + return Err(StatusWord::InvalidPath); + } + let coin_type: PCoinType = req.coin_type.into(); + if req.path.as_ref()[1] != coin_type.bip44_coin_type() { + return Err(StatusWord::InvalidPath); + } + + 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 = GetPublicKeyRespones { + public_key: pk.pubkey, + chain_code: code.value, + }; + + comm.append(&encode(response)); + + Ok(()) +} diff --git a/src/handlers/sign_message.rs b/src/handlers/sign_message.rs new file mode 100644 index 0000000..a2b15ee --- /dev/null +++ b/src/handlers/sign_message.rs @@ -0,0 +1,177 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025 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::app_ui::sign::ui_display_message; +use crate::{DataContext, StatusWord}; +use messages::{encode, AddrType, Bip32Path, MsgSignature, PCoinType, SignMessageReq}; + +use alloc::vec::Vec; +use ledger_device_sdk::{ + ecc::{CxError, ECPrivateKey, Secp256k1, SeedDerive}, + hash::{blake2::Blake2b_512, HashInit}, + io::Comm, +}; + +use ledger_secure_sdk_sys::*; + +const MAX_MESSAGE_LEN: usize = 510; + +pub struct SignMessageContext { + message: Vec, + path: Bip32Path, + coin: PCoinType, + addr_type: AddrType, + review_finished: bool, +} + +impl SignMessageContext { + pub fn new(req: SignMessageReq) -> Self { + Self { + message: Vec::new(), + 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: SignMessageReq, ctx: &mut DataContext) -> Result<(), StatusWord> { + *ctx = DataContext::SignMessageContext(SignMessageContext::new(req)); + Ok(()) +} + +pub fn handle_sign_message( + comm: &mut Comm, + more: bool, + ctx: &mut DataContext, +) -> Result<(), StatusWord> { + let ctx = match ctx { + DataContext::SignMessageContext(ctx) => ctx, + _ => return Err(StatusWord::WrongContext), + }; + let chunk = comm.get_data().map_err(|_| StatusWord::WrongApduLength)?; + + if ctx.message.len() + chunk.len() > MAX_MESSAGE_LEN { + return Err(StatusWord::TxWrongLength); + } + + ctx.message.extend(chunk); + + if more { + ctx.review_finished = false; + Ok(()) + } else { + 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(&ctx.message, &public_key, ctx.coin, ctx.addr_type)? { + ctx.review_finished = true; + compute_signature_and_append(comm, &private_key, ctx) + } else { + ctx.review_finished = true; + Err(StatusWord::Deny) + } + } +} + +fn compute_signature_and_append( + comm: &mut Comm, + private_key: &ECPrivateKey, + ctx: &mut SignMessageContext, +) -> Result<(), StatusWord> { + const MESSAGE_MAGIC_PREFIX: &str = "===MINTLAYER MESSAGE BEGIN===\n"; + const MESSAGE_MAGIC_SUFFIX: &str = "\n===MINTLAYER MESSAGE END==="; + + let mut blake2b256 = Blake2b_512::new(); + let mut message_hash: [u8; 64] = [0u8; 64]; + + let message = MESSAGE_MAGIC_PREFIX + .as_bytes() + .iter() + .chain(ctx.message.iter()) + .chain(MESSAGE_MAGIC_SUFFIX.as_bytes().iter()) + .copied() + .collect::>(); + + blake2b256 + .hash(&message, &mut message_hash) + .map_err(|_| StatusWord::TxHashFail)?; + let mut message_hash_32: [u8; 32] = [0u8; 32]; + message_hash_32.copy_from_slice(&message_hash[0..32]); + + let mut blake2b256 = Blake2b_512::new(); + let mut message_hash2: [u8; 64] = [0u8; 64]; + blake2b256 + .hash(&message_hash_32, &mut message_hash2) + .map_err(|_| StatusWord::TxHashFail)?; + + let mut message_hash2_32: [u8; 32] = [0u8; 32]; + message_hash2_32.copy_from_slice(&message_hash2[0..32]); + + let hash_algorithm_id = CX_SHA256; + let signing_mode = CX_ECSCHNORR_BIP0340 | CX_RND_PROVIDED | CX_LAST; + + let sig = schnorr_sign( + private_key, + &message_hash2_32, + hash_algorithm_id, + signing_mode, + )?; + + let response = MsgSignature { signature: sig }; + + comm.append(&encode(response)); + Ok(()) +} + +pub fn schnorr_sign( + private_key: &ECPrivateKey, + msg: &[u8], + hash_id: u8, + mode: u32, +) -> Result<[u8; 64], CxError> { + 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, + mode, + hash_id, + msg.as_ptr(), + msg.len(), + sig.as_mut_ptr(), + &mut sig_len, + ) + }; + + if err_code != CX_OK { + Err(err_code.into()) + } else { + Ok(sig) + } +} diff --git a/src/handlers/sign_tx.rs b/src/handlers/sign_tx.rs new file mode 100644 index 0000000..52ede1f --- /dev/null +++ b/src/handlers/sign_tx.rs @@ -0,0 +1,900 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2025 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 alloc::vec::Vec; + +use crate::{ + app_ui::sign::{ + approve_streaming_review, new_streaming_review, start_streaming_review, + streaming_review_show_output, ui_display_tx, + }, + handlers::sign_message::schnorr_sign, + DataContext, StatusWord, P2_DONE, P2_MORE, +}; +use messages::{ + encode, encode_as_compact, encode_to, AccountCommand, AccountSpending, AdditionalOrderInfo, + AdditionalUtxoInfo, Amount, Encode, InputAddressPath, OrderAccountCommand, OutputValue, + PCoinType, SighashInputCommitment, SignTxReq, Signature, TxInputReq, TxInputWithAdditionalInfo, + TxMetadataReq, TxOutput, TxOutputReq, H256, +}; + +use ledger_device_sdk::{ + ecc::{Secp256k1, SeedDerive}, + hash::{blake2::Blake2b_512, HashInit}, + io::Comm, + nbgl::{NbglSpinner, NbglStreamingReview}, +}; +use ledger_secure_sdk_sys::*; + +const BIP44: u32 = 44 + (1 << 31); + +// BIP44/COIN/ACCOUNT/PURPOSE/INDEX +const DERIVATION_PATH_LEN: usize = 5; +// DERIVATION_PATH_LEN without the BIP44 and COIN as they are the same for all +const COMPRESSED_DERIVATION_PATH_LEN: usize = 3; + +#[derive(Eq, Ord, PartialEq, PartialOrd)] +pub enum CoinOrTokenId { + Coin, + TokenId(H256), +} + +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.hash()), *amount)) + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum TxType { + Transfer, + Burn, + Htlc, + CreateDelegation, + DelegationStake, + DelegationWithdrawl, + CreateStakePool, + DecommissionStakePool, + CreateNft, + CreateToken, + MintTokens, + UnmintTokens, + FreezeToken, + UnfreezeToken, + LockTokenSupply, + ChangeTokenAuthority, + ChangeTokenMetadataUri, + FillOrder, + FreezeOrder, + CreateOrder, + ConcludeOrder, + ComplexTransaction, + DataDeposit, +} + +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), + } +} + +pub struct InputCompressed { + pub path: [u32; COMPRESSED_DERIVATION_PATH_LEN], + pub input_idx: usize, + pub multisig_idx: Option, +} + +impl InputCompressed { + fn new(addr: InputAddressPath, input_idx: usize, coin: PCoinType) -> Result { + let path = addr.path.as_ref(); + if path.len() != DERIVATION_PATH_LEN { + return Err(StatusWord::TxInvalidInputPath); + } + + if path[0] != BIP44 { + return Err(StatusWord::TxInvalidInputPath); + } + + if path[1] != coin.bip44_coin_type() { + return Err(StatusWord::TxInvalidInputPath); + } + + Ok(Self { + path: path[2..] + .try_into() + .map_err(|_| StatusWord::TxInvalidInputPath)?, + input_idx, + multisig_idx: addr.multisig_idx, + }) + } +} + +#[derive(PartialEq, Eq)] +enum TxParsingState { + Input(usize), + InputCommitment { + inp_idx: usize, + input_commitments_hash: [u8; 64], + }, + Output(usize), + CompleteNotApproved { + inp_idx: usize, + sighash: [u8; 32], + }, + ApprovedNotFinishedSigning { + inp_idx: usize, + sighash: [u8; 32], + }, + Finished, +} + +#[derive(PartialEq, Eq)] +pub enum NextTxOutputParsingState { + Output(usize), + CompleteNotApproved { inp_idx: usize, sighash: [u8; 32] }, +} + +pub struct TxContext { + raw_buf: Vec, + coin: PCoinType, + num_inputs: u32, + num_outputs: u32, + review_finished: bool, + state: TxParsingState, + tx_type: Option, + + tx_hasher: Blake2b_512, + input_commitments_hasher: Blake2b_512, + + total_inputs: BTreeMap, + total_outputs: BTreeMap, + inputs: Vec, + + spinner: NbglSpinner, +} + +pub enum Review { + Review(Vec), + StreamingReview(NbglStreamingReview), +} + +pub enum SigningState<'a> { + StreamingReviewStart(&'a NbglStreamingReview), + StreamingReviewOutput(&'a NbglStreamingReview, TxOutput), + StreamingReviewApprove { + review: &'a NbglStreamingReview, + output: TxOutput, + inp_idx: usize, + sighash: [u8; 32], + }, + TxParsingNotComplete, + CompleteNotApproved { + inp_idx: usize, + sighash: [u8; 32], + outputs: &'a [TxOutput], + }, + ApprovedNotFinishedSigning { + inp_idx: usize, + sighash: [u8; 32], + }, +} + +// Implement constructor for TxInfo with default values +impl TxContext { + // Constructor + pub fn new( + TxMetadataReq { + coin, + version, + num_inputs, + num_outputs, + }: TxMetadataReq, + ) -> Result { + let mut tx_hasher = Blake2b_512::new(); + let input_commitments_hasher = Blake2b_512::new(); + // mode + tx_hasher + .update(b"\x01") + .map_err(|_| StatusWord::TxHashFail)?; + // version + tx_hasher + .update(&[version]) + .map_err(|_| StatusWord::TxHashFail)?; + // flags + tx_hasher + .update(&[0; 16]) + .map_err(|_| StatusWord::TxHashFail)?; + + Ok(TxContext { + coin: coin.into(), + raw_buf: Vec::with_capacity(251), + num_inputs, + num_outputs, + review_finished: false, + tx_hasher, + input_commitments_hasher, + state: TxParsingState::Input(0), + tx_type: None, + + total_inputs: Default::default(), + total_outputs: Default::default(), + inputs: Vec::with_capacity(20), + spinner: NbglSpinner::new(), + }) + } + + pub fn coin(&self) -> PCoinType { + self.coin + } + + 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 + } + + fn state(&self) -> &TxParsingState { + &self.state + } + + fn update_hash(&mut self, data: &T) -> Result<(), StatusWord> { + update_hash(&mut self.raw_buf, data, &mut self.tx_hasher) + } + + fn update_input_commitments_hash(&mut self, data: &T) -> Result<(), StatusWord> { + update_hash(&mut self.raw_buf, data, &mut self.input_commitments_hasher) + } + + fn completed_all_signatures(&self) -> bool { + self.state == TxParsingState::Finished + } + + // Get review status + #[allow(dead_code)] + pub fn finished(&self) -> bool { + self.review_finished + } + + fn advance_next_input_step<'a>( + &mut self, + current_input_step: usize, + ) -> Result, StatusWord> { + let finished_with_inputs = current_input_step >= (self.num_inputs - 1) as usize; + + self.state = if finished_with_inputs { + // Update hash for input commitments and proceed with outputs + self.tx_hasher + .update(&self.num_inputs.to_le_bytes()) + .map_err(|_| StatusWord::TxHashFail)?; + + let mut input_commitments_hash: [u8; 64] = [0u8; 64]; + self.input_commitments_hasher + .finalize(&mut input_commitments_hash) + .map_err(|_| StatusWord::TxHashFail)?; + + self.input_commitments_hasher = Blake2b_512::new(); + + TxParsingState::InputCommitment { + inp_idx: 0, + input_commitments_hash, + } + } else { + TxParsingState::Input(current_input_step + 1) + }; + + Ok(SigningState::TxParsingNotComplete) + } + + fn advance_next_input_additional_info_step<'a>( + &mut self, + current_input_step: usize, + expected_input_commitments_hash: [u8; 64], + review: &'a Review, + ) -> Result, StatusWord> { + let finished_with_inputs = current_input_step >= (self.num_inputs - 1) as usize; + + let signing_state = if finished_with_inputs { + // Make sure the hashes match before continuing with the outputs + let mut input_commitments_hash: [u8; 64] = [0u8; 64]; + self.input_commitments_hasher + .finalize(&mut input_commitments_hash) + .map_err(|_| StatusWord::TxHashFail)?; + + if input_commitments_hash != expected_input_commitments_hash { + return Err(StatusWord::DifferentInputCommitmentHash); + } + + self.state = TxParsingState::Output(0); + + match review { + Review::Review(_) => SigningState::TxParsingNotComplete, + Review::StreamingReview(review) => SigningState::StreamingReviewStart(review), + } + } else { + self.state = TxParsingState::InputCommitment { + inp_idx: current_input_step + 1, + input_commitments_hash: expected_input_commitments_hash, + }; + SigningState::TxParsingNotComplete + }; + + Ok(signing_state) + } + + // After processing an output advance the internal state + fn advance_next_output_state( + &mut self, + n: usize, + ) -> Result { + let next_state = if n < (self.num_outputs - 1) as usize { + NextTxOutputParsingState::Output(n + 1) + } else { + let inp_idx = 0; + // Finalize the tx hash for signing + let mut message_hash: [u8; 64] = [0u8; 64]; + self.tx_hasher + .finalize(&mut message_hash) + .map_err(|_| StatusWord::TxHashFail)?; + + let mut blake2b256 = Blake2b_512::new(); + let mut message_hash2: [u8; 64] = [0u8; 64]; + blake2b256 + .hash(&message_hash[0..32], &mut message_hash2) + .map_err(|_| StatusWord::TxHashFail)?; + + NextTxOutputParsingState::CompleteNotApproved { + inp_idx, + sighash: message_hash2[..32] + .try_into() + .map_err(|_| StatusWord::TxHashFail)?, + } + }; + + self.state = match next_state { + NextTxOutputParsingState::Output(out) => TxParsingState::Output(out), + NextTxOutputParsingState::CompleteNotApproved { inp_idx, sighash } => { + TxParsingState::CompleteNotApproved { inp_idx, sighash } + } + }; + + Ok(next_state) + } + + // After processing a signature advance the internal state + fn advance_next_signing_step(&mut self, inp_idx: usize, sighash: &[u8; 32]) { + self.state = if (inp_idx + 1) < self.inputs.len() { + TxParsingState::ApprovedNotFinishedSigning { + inp_idx: inp_idx + 1, + sighash: *sighash, + } + } else { + TxParsingState::Finished + }; + } + + // show a spinner for bigger transactions + pub fn show_spinner(&mut self) { + let is_transaction_big = self.num_inputs * 2 + self.num_outputs > 10; + let returning_signatures = match self.state { + TxParsingState::ApprovedNotFinishedSigning { + inp_idx: _, + sighash: _, + } + | TxParsingState::CompleteNotApproved { + inp_idx: _, + sighash: _, + } => true, + TxParsingState::Input(_) + | TxParsingState::InputCommitment { + inp_idx: _, + input_commitments_hash: _, + } + | TxParsingState::Finished + | TxParsingState::Output(_) => false, + }; + + if returning_signatures && self.num_inputs > 1 { + self.spinner.show("Signing..."); + } else if is_transaction_big { + self.spinner.show("Parsing transaction..."); + } + } +} + +pub fn setup_sign_tx(req: TxMetadataReq, ctx: &mut DataContext) -> Result<(), StatusWord> { + let mut tx_ctx = TxContext::new(req)?; + + tx_ctx.show_spinner(); + + // if has many outputs use a streaming review + if tx_ctx.num_outputs > 10 { + *ctx = DataContext::TxContext(tx_ctx, Review::StreamingReview(new_streaming_review())); + } else { + *ctx = DataContext::TxContext(tx_ctx, Review::Review(Vec::new())); + } + + Ok(()) +} + +fn handle_input_commitment_req<'a>( + req: SighashInputCommitment, + input_step: usize, + input_commitments_hash: [u8; 64], + ctx: &mut TxContext, + review: &'a mut Review, +) -> Result, StatusWord> { + ctx.update_input_commitments_hash(&req)?; + ctx.update_hash(&req)?; + ctx.advance_next_input_additional_info_step(input_step, input_commitments_hash, review) +} + +fn handle_input_req<'a>( + req: TxInputReq, + input_step: usize, + ctx: &mut TxContext, +) -> Result, StatusWord> { + let addresses = req + .addresses + .into_iter() + .map(|a| InputCompressed::new(a, input_step, ctx.coin)) + .collect::, StatusWord>>()?; + ctx.inputs.extend(addresses); + + process_input(ctx, &req.inp)?; + + let (input, commitment) = req.inp.into_input_and_commitment(); + ctx.update_input_commitments_hash(&commitment)?; + + if input_step == 0 { + ctx.tx_hasher + .update(&ctx.num_inputs.to_le_bytes()) + .map_err(|_| StatusWord::TxHashFail)?; + } + ctx.update_hash(&input)?; + ctx.advance_next_input_step(input_step) +} + +fn handle_output_req<'a>( + req: TxOutputReq, + output_step: usize, + ctx: &mut TxContext, + review: &'a mut Review, +) -> Result, StatusWord> { + process_output(ctx, &req.out)?; + // on the first output add the number of outputs to the hash + if output_step == 0 { + ctx.tx_hasher + .update(&encode_as_compact(ctx.num_outputs)) + .map_err(|_| StatusWord::TxHashFail)?; + } + ctx.update_hash(&req.out)?; + let next_step = ctx.advance_next_output_state(output_step)?; + let signin_state = match review { + Review::Review(outputs) => { + outputs.push(req.out); + match next_step { + NextTxOutputParsingState::Output(_) => SigningState::TxParsingNotComplete, + NextTxOutputParsingState::CompleteNotApproved { inp_idx, sighash } => { + SigningState::CompleteNotApproved { + inp_idx, + sighash, + outputs, + } + } + } + } + Review::StreamingReview(review) => { + // on last output show it and ask for approval + match next_step { + NextTxOutputParsingState::Output(_) => { + SigningState::StreamingReviewOutput(review, req.out) + } + NextTxOutputParsingState::CompleteNotApproved { inp_idx, sighash } => { + SigningState::StreamingReviewApprove { + review, + output: req.out, + inp_idx, + sighash, + } + } + } + } + }; + + Ok(signin_state) +} + +pub fn handle_sign_tx( + comm: &mut Comm, + req: SignTxReq, + ctx: &mut TxContext, + review: &mut Review, +) -> Result<(), StatusWord> { + let signing_state = match (req, ctx.state()) { + (SignTxReq::Input(req), TxParsingState::Input(n)) => handle_input_req(req, *n, ctx)?, + ( + SignTxReq::InputCommitment(req), + TxParsingState::InputCommitment { + inp_idx, + input_commitments_hash, + }, + ) => handle_input_commitment_req(req, *inp_idx, *input_commitments_hash, ctx, review)?, + (SignTxReq::Output(req), TxParsingState::Output(n)) => { + handle_output_req(req, *n, ctx, review)? + } + ( + SignTxReq::NextSignature, + TxParsingState::ApprovedNotFinishedSigning { inp_idx, sighash }, + ) => SigningState::ApprovedNotFinishedSigning { + inp_idx: *inp_idx, + sighash: *sighash, + }, + ( + SignTxReq::NextSignature, + TxParsingState::CompleteNotApproved { + inp_idx: _, + sighash: _, + }, + ) => return Err(StatusWord::Deny), + (SignTxReq::NextSignature, TxParsingState::Finished) => { + return Err(StatusWord::TxAlreadyFinished) + } + (_, _) => return Err(StatusWord::WrongP1P2), + }; + + match signing_state { + SigningState::TxParsingNotComplete => Ok(()), + SigningState::StreamingReviewStart(review) => { + if start_streaming_review(review) { + Ok(()) + } else { + ctx.review_finished = true; + Err(StatusWord::Deny) + } + } + SigningState::StreamingReviewOutput(review, output) => { + if streaming_review_show_output(review, &output, ctx.coin)? { + Ok(()) + } else { + ctx.review_finished = true; + Err(StatusWord::Deny) + } + } + SigningState::StreamingReviewApprove { + review, + output, + inp_idx, + sighash, + } => { + if approve_streaming_review(review, &output, ctx)? { + compute_signature_and_append(comm, ctx, inp_idx, &sighash)?; + if ctx.completed_all_signatures() { + ctx.review_finished = true; + } else { + ctx.show_spinner(); + } + Ok(()) + } else { + ctx.review_finished = true; + Err(StatusWord::Deny) + } + } + SigningState::CompleteNotApproved { + inp_idx, + sighash, + outputs, + } => { + // Display transaction. If user approves the transaction, sign it. + // Otherwise, return a "deny" status word. + if ui_display_tx(ctx, outputs)? { + compute_signature_and_append(comm, ctx, inp_idx, &sighash)?; + if ctx.completed_all_signatures() { + ctx.review_finished = true; + } else { + ctx.show_spinner(); + } + + Ok(()) + } else { + ctx.review_finished = true; + Err(StatusWord::Deny) + } + } + SigningState::ApprovedNotFinishedSigning { inp_idx, sighash } => { + // Already approved sign and return the next signature + compute_signature_and_append(comm, ctx, inp_idx, &sighash)?; + if ctx.completed_all_signatures() { + ctx.review_finished = true; + } else { + ctx.show_spinner(); + } + + Ok(()) + } + } +} + +fn process_output(ctx: &mut TxContext, out: &TxOutput) -> Result<(), StatusWord> { + match &out { + TxOutput::Transfer(value, _) | TxOutput::LockThenTransfer(value, _, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::Transfer); + + let (coin_or_token_id, amount) = into_coin_or_token_id_and_amount(value)?; + increase_output_totals(&mut ctx.total_outputs, coin_or_token_id, amount)?; + } + TxOutput::Burn(value) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::Burn); + + let (coin_or_token_id, amount) = into_coin_or_token_id_and_amount(value)?; + increase_output_totals(&mut ctx.total_outputs, coin_or_token_id, amount)?; + } + TxOutput::Htlc(value, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::Htlc); + + let (coin_or_token_id, amount) = into_coin_or_token_id_and_amount(value)?; + increase_output_totals(&mut ctx.total_outputs, coin_or_token_id, amount)?; + } + TxOutput::CreateStakePool(_, data) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::CreateStakePool); + + increase_output_totals(&mut ctx.total_outputs, CoinOrTokenId::Coin, data.pledge)?; + } + TxOutput::ProduceBlockFromStake(_, _) => {} + TxOutput::DelegateStaking(amount, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::DelegationStake); + increase_output_totals(&mut ctx.total_outputs, CoinOrTokenId::Coin, *amount)?; + } + TxOutput::CreateDelegationId(_, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::CreateDelegation); + } + TxOutput::IssueFungibleToken(_) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::CreateToken); + } + TxOutput::DataDeposit(_) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::DataDeposit); + } + TxOutput::IssueNft(_, _, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::CreateNft); + } + TxOutput::CreateOrder(_) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::CreateOrder); + } + } + + Ok(()) +} + +fn process_input(ctx: &mut TxContext, inp: &TxInputWithAdditionalInfo) -> Result<(), StatusWord> { + match inp { + TxInputWithAdditionalInfo::Utxo(_, info) => match info { + AdditionalUtxoInfo::PoolData { + utxo: _, + staker_balance, + } => { + increase_input_totals(&mut ctx.total_inputs, 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)?; + increase_input_totals(&mut ctx.total_inputs, 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) => { + increase_input_totals( + &mut ctx.total_inputs, + CoinOrTokenId::Coin, + data.pledge, + )?; + } + TxOutput::IssueNft(nft_id, _, _) => { + increase_input_totals( + &mut ctx.total_inputs, + CoinOrTokenId::TokenId(*nft_id.hash()), + Amount::from_atoms(1), + )?; + } + }; + } + }, + TxInputWithAdditionalInfo::Account(acc) => match acc.spending { + AccountSpending::DelegationBalance(_, amount) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::DelegationWithdrawl); + increase_input_totals(&mut ctx.total_inputs, CoinOrTokenId::Coin, amount)?; + } + }, + TxInputWithAdditionalInfo::AccountCommand(_, cmd) => match cmd { + AccountCommand::MintTokens(token_id, amount) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::MintTokens); + increase_input_totals( + &mut ctx.total_inputs, + CoinOrTokenId::TokenId(*token_id.hash()), + *amount, + )?; + } + AccountCommand::ConcludeOrder(_) | AccountCommand::FillOrder(_, _, _) => { + return Err(StatusWord::OrdersV0NotSupported) + } + AccountCommand::UnmintTokens(_) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::UnmintTokens); + } + AccountCommand::LockTokenSupply(_) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::LockTokenSupply); + } + AccountCommand::FreezeToken(_, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::FreezeToken); + } + AccountCommand::UnfreezeToken(_) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::UnfreezeToken); + } + AccountCommand::ChangeTokenAuthority(_, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::ChangeTokenAuthority); + } + AccountCommand::ChangeTokenMetadataUri(_, _) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::ChangeTokenMetadataUri); + } + }, + TxInputWithAdditionalInfo::OrderAccountCommand( + cmd, + AdditionalOrderInfo { + initially_asked, + initially_given, + ask_balance, + give_balance, + }, + ) => match cmd { + OrderAccountCommand::FillOrder(_, fill_amount) => { + let (fill_coin_or_token_id, asked_amount) = + into_coin_or_token_id_and_amount(&initially_asked)?; + let (given_coin_or_token_id, given_amount) = + into_coin_or_token_id_and_amount(&initially_given)?; + + increase_output_totals( + &mut ctx.total_outputs, + 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); + increase_input_totals(&mut ctx.total_inputs, given_coin_or_token_id, amount)?; + + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::FillOrder); + } + OrderAccountCommand::ConcludeOrder(_) => { + let (coin_or_token_id, _) = into_coin_or_token_id_and_amount(&initially_asked)?; + increase_input_totals(&mut ctx.total_inputs, coin_or_token_id, *ask_balance)?; + + let (coin_or_token_id, _) = into_coin_or_token_id_and_amount(&initially_given)?; + increase_input_totals(&mut ctx.total_inputs, coin_or_token_id, *give_balance)?; + + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::ConcludeOrder); + } + OrderAccountCommand::FreezeOrder(_) => { + ctx.tx_type = merge_tx_type(ctx.tx_type, TxType::FreezeOrder); + } + }, + }; + + Ok(()) +} + +fn increase_input_totals( + total_inputs: &mut BTreeMap, + key: CoinOrTokenId, + amount: Amount, +) -> Result<(), StatusWord> { + let total = 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( + total_outputs: &mut BTreeMap, + key: CoinOrTokenId, + amount: Amount, +) -> Result<(), StatusWord> { + let total = 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(()) +} + +fn compute_signature_and_append( + comm: &mut Comm, + ctx: &mut TxContext, + inp_idx: usize, + sighash: &[u8; 32], +) -> Result<(), StatusWord> { + let hash_algorithm_id = CX_SHA256; + let signing_mode = CX_ECSCHNORR_BIP0340 | CX_RND_PROVIDED | CX_LAST; + + let address = ctx.inputs.get(inp_idx).ok_or(StatusWord::WrongContext)?; + + let [p1, p2, p3] = address.path; + let addr = [BIP44, ctx.coin.bip44_coin_type(), p1, p2, p3]; + + let private_key = Secp256k1::derive_from_path(&addr); + let sig = schnorr_sign(&private_key, sighash, hash_algorithm_id, signing_mode)?; + + let sig = Signature { + signature: sig, + multisig_idx: address.multisig_idx, + }; + let input_idx = address.input_idx as u8; + + ctx.advance_next_signing_step(inp_idx, sighash); + + comm.append(&[input_idx]); + comm.append(&encode(sig)); + if ctx.state == TxParsingState::Finished { + comm.append(&[P2_DONE]) + } else { + comm.append(&[P2_MORE]) + } + + Ok(()) +} + +fn update_hash( + raw_buf: &mut Vec, + data: &T, + hasher: &mut Blake2b_512, +) -> Result<(), StatusWord> { + raw_buf.clear(); + encode_to(data, raw_buf); + hasher + .update(raw_buf.as_slice()) + .map_err(|_| StatusWord::TxHashFail)?; + raw_buf.clear(); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..06fcd2c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,392 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025 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] + +mod app_ui { + pub mod address; + pub mod menu; + pub mod sign; + pub mod utils; +} +mod handlers { + pub mod get_public_key; + pub mod sign_message; + pub mod sign_tx; +} + +mod settings; + +// Required for using String, Vec, format!... +extern crate alloc; +use alloc::vec::Vec; + +use ledger_device_sdk::{ + ecc::CxError, + io::{ApduHeader, Comm, Reply, StatusWords}, + nbgl::{init_comm, NbglHomeAndSettings, NbglReviewStatus, StatusType}, +}; + +use app_ui::menu::ui_menu_main; +use handlers::{ + get_public_key::handle_get_public_key, + sign_message::{handle_sign_message, setup_sign_message, SignMessageContext}, + sign_tx::{setup_sign_tx, Review, TxContext}, +}; +use messages::{ + decode_all, Ins, PubKeyP1, SignP1, WrongP1P2, APDU_CLASS, MAX_ADPU_DATA_LEN, P2_DONE, P2_MORE, +}; + +use crate::handlers::sign_tx::handle_sign_tx; + +ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); + +pub const MAX_BUFFER_LEN: usize = 4 * MAX_ADPU_DATA_LEN; + +impl From for StatusWord { + fn from(_: WrongP1P2) -> Self { + Self::WrongP1P2 + } +} + +// Application status words. +#[repr(u16)] +#[derive(Clone, Copy, PartialEq)] +pub enum StatusWord { + Ok = StatusWords::Ok as u16, + Deny = StatusWords::UserCancelled as u16, + ClaNotSupported = StatusWords::BadCla as u16, + WrongP1P2 = StatusWords::BadP1P2 as u16, + InsNotSupported = StatusWords::BadIns as u16, + WrongApduLength = StatusWords::BadLen as u16, + + TxDisplayFail = 0xB000, + AddrDisplayFail = 0xB001, + TxWrongLength = 0xB002, + TxParsingFail = 0xB003, + TxHashFail = 0xB004, + TxAddressFail = 0xB005, + TxSignFail = 0xB006, + KeyDeriveFail = 0xB007, + VersionParsingFail = 0xB008, + WrongContext = 0xB009, + DeserializeFail = 0xB00A, + TxInvalidInputUtxo = 0xB00B, + TxNumericOperationFail = 0xB00C, + TxUnsupportedInput = 0xB00D, + TxInvalidTokenV0 = 0xB00E, + TxInvalidInputPath = 0xB00F, + NothingToSign = 0xB010, + TxAlreadyFinished = 0xB011, + InvalidPath = 0xB012, + InvalidUncompressedPublicKey = 0xB013, + MaxBufferLenExceeded = 0xB014, + DifferentInputCommitmentHash = 0xB015, + OrdersV0NotSupported = 0xB016, + + // The following errors come from ecc::CxError + EccCarry = 0xB100, + EccLocked = 0xB101, + EccUnlocked = 0xB102, + EccNotLocked = 0xB103, + EccNotUnlocked = 0xB104, + EccInternalError = 0xB105, + EccInvalidParameterSize = 0xB106, + EccInvalidParameterValue = 0xB107, + EccInvalidParameter = 0xB108, + EccNotInvertible = 0xB109, + EccOverflow = 0xB10A, + EccMemoryFull = 0xB10B, + EccNoResidue = 0xB10C, + EccPointAtInfinity = 0xB10D, + EccInvalidPoint = 0xB10E, + EccInvalidCurve = 0xB10F, + EccGenericError = 0xB110, +} + +impl From for StatusWord { + fn from(value: CxError) -> Self { + match value { + CxError::Carry => Self::EccCarry, + CxError::Locked => Self::EccLocked, + CxError::Unlocked => Self::EccUnlocked, + CxError::NotLocked => Self::EccNotLocked, + CxError::NotUnlocked => Self::EccNotUnlocked, + CxError::InternalError => Self::EccInternalError, + CxError::InvalidParameterSize => Self::EccInvalidParameterSize, + CxError::InvalidParameterValue => Self::EccInvalidParameterValue, + CxError::InvalidParameter => Self::EccInvalidParameter, + CxError::NotInvertible => Self::EccNotInvertible, + CxError::Overflow => Self::EccOverflow, + CxError::MemoryFull => Self::EccMemoryFull, + CxError::NoResidue => Self::EccNoResidue, + CxError::PointAtInfinity => Self::EccPointAtInfinity, + CxError::InvalidPoint => Self::EccInvalidPoint, + CxError::InvalidCurve => Self::EccInvalidCurve, + CxError::GenericError => Self::EccGenericError, + } + } +} + +impl From for Reply { + fn from(sw: StatusWord) -> Reply { + Reply(sw as u16) + } +} + +/// 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, +} + +/// State machine to handle APDU packet chaining (P2_MORE / P2_DONE). +pub struct ApduTransport { + buffer: Vec, + current_ins: Option, + current_p1: Option, +} + +impl ApduTransport { + pub fn new() -> Self { + Self { + buffer: Vec::with_capacity(255), // Pre-alloc for at least one standard APDU + current_ins: None, + current_p1: None, + } + } + + /// 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, StatusWord> { + let header: ApduHeader = comm.next_command(); + let data = comm.get_data().map_err(|_| StatusWord::WrongApduLength)?; + + // 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 || 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 { + return Err(StatusWord::MaxBufferLenExceeded); + } + + self.buffer.extend_from_slice(data); + + match header.p2 { + P2_MORE => { + // Signal host that we received the chunk and are waiting for more + comm.reply(StatusWord::Ok); + Ok(None) + } + 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(Some(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: PubKeyP1, data: Vec }, + SignTx { p1: SignP1, data: Vec }, + SignMessage { p1: SignP1, data: Vec }, +} + +impl TryFrom for Command { + type Error = StatusWord; + + fn try_from(raw: RawInstruction) -> Result { + match raw.ins { + Ins::PUB_KEY => { + let p1: PubKeyP1 = raw.p1.try_into()?; + Ok(Command::GetPubkey { p1, data: raw.data }) + } + Ins::SIGN_TX => { + let p1: SignP1 = raw.p1.try_into()?; + Ok(Command::SignTx { p1, data: raw.data }) + } + Ins::SIGN_MSG => { + let p1: SignP1 = raw.p1.try_into()?; + Ok(Command::SignMessage { p1, data: raw.data }) + } + _ => Err(StatusWord::InsNotSupported), + } + } +} + +fn show_status_and_home_if_needed(cmd: &Command, ctx: &mut Context, 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) + } + (_, _) => (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 { + Empty, + TxContext(TxContext, Review), + SignMessageContext(SignMessageContext), +} + +struct Context { + pub data: DataContext, + pub home: NbglHomeAndSettings, +} + +impl Context { + fn new() -> Self { + Self { + data: DataContext::Empty, + home: Default::default(), + } + } + + fn finished(&self) -> bool { + match &self.data { + DataContext::Empty => false, + DataContext::SignMessageContext(ctx) => ctx.finished(), + DataContext::TxContext(ctx, _) => ctx.finished(), + } + } +} + +#[no_mangle] +extern "C" fn sample_main() { + let mut comm = Comm::new().set_expected_cla(APDU_CLASS); + + let mut tx_ctx = Context::new(); + + // Initialize reference to Comm instance for NBGL API calls. + init_comm(&mut comm); + tx_ctx.home = ui_menu_main(&mut comm); + tx_ctx.home.show_and_return(); + + let mut transport = ApduTransport::new(); + + loop { + let raw_instruction = match transport.receive(&mut comm) { + Ok(Some(raw)) => raw, + Ok(None) => continue, // Waiting for more chunks, loop around + Err(sw) => { + comm.reply(sw); + continue; + } + }; + + let command = match Command::try_from(raw_instruction) { + Ok(cmd) => cmd, + Err(sw) => { + comm.reply(sw); + continue; + } + }; + + let status = match handle_command(&mut comm, &command, &mut tx_ctx) { + Ok(()) => { + comm.reply_ok(); + StatusWord::Ok + } + Err(sw) => { + comm.reply(sw); + sw + } + }; + + show_status_and_home_if_needed(&command, &mut tx_ctx, &status); + } +} + +fn handle_command(comm: &mut Comm, cmd: &Command, ctx: &mut Context) -> Result<(), StatusWord> { + match cmd { + Command::GetPubkey { p1, data } => { + let req = decode_all(data).ok_or(StatusWord::DeserializeFail)?; + handle_get_public_key(comm, req, p1.display()) + } + Command::SignTx { p1, data } => match p1 { + SignP1::Start => { + let req = decode_all(data).ok_or(StatusWord::DeserializeFail)?; + setup_sign_tx(req, &mut ctx.data) + } + SignP1::Next => { + let (tx_ctx, review) = match &mut ctx.data { + DataContext::TxContext(c, r) => (c, r), + _ => return Err(StatusWord::WrongContext), + }; + + tx_ctx.show_spinner(); + + let req = decode_all(data).ok_or(StatusWord::DeserializeFail)?; + handle_sign_tx(comm, req, tx_ctx, review) + } + }, + Command::SignMessage { p1, data } => match p1 { + SignP1::Start => { + let req = decode_all(&data).ok_or(StatusWord::DeserializeFail)?; + setup_sign_message(req, &mut ctx.data) + } + SignP1::Next => handle_sign_message(comm, false, &mut ctx.data), + }, + } +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..f53c734 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,71 @@ +/***************************************************************************** + * Mintlayer Ledger App. + * (c) 2023 Ledger SAS. + * (c) 2025 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::nvm::*; +use ledger_device_sdk::NVMData; + +// This is necessary to store the object in NVM and not in RAM +const SETTINGS_SIZE: usize = 10; +#[link_section = ".nvm_data"] +static mut DATA: NVMData> = + NVMData::new(AtomicStorage::new(&[0u8; SETTINGS_SIZE])); + +#[derive(Clone, Copy)] +pub struct Settings; + +impl Default for Settings { + fn default() -> Self { + Settings + } +} + +impl Settings { + #[inline(never)] + #[allow(unused)] + pub fn get_mut(&mut self) -> &mut AtomicStorage<[u8; SETTINGS_SIZE]> { + let data = &raw mut DATA; + unsafe { (*data).get_mut() } + } + + #[inline(never)] + #[allow(unused)] + pub fn get_ref(&mut self) -> &AtomicStorage<[u8; SETTINGS_SIZE]> { + let data = &raw const DATA; + unsafe { (*data).get_ref() } + } + + #[allow(unused)] + pub fn get_element(&self, index: usize) -> u8 { + let data = &raw const DATA; + let storage = unsafe { (*data).get_ref() }; + let settings = storage.get_ref(); + settings[index] + } + + #[allow(unused)] + // Not used, but can be used to set a value in the settings + pub fn set_element(&self, index: usize, value: u8) { + let data = &raw mut DATA; + let storage = unsafe { (*data).get_mut() }; + let mut updated_data = *storage.get_ref(); + updated_data[index] = value; + unsafe { + storage.update(&updated_data); + } + } +} diff --git a/tests/application_client/__init__.py b/tests/application_client/__init__.py new file mode 100644 index 0000000..a542bc7 --- /dev/null +++ b/tests/application_client/__init__.py @@ -0,0 +1,719 @@ +# Copyright (c) 2022 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", + "OutputValue": { + "type": "enum", + "type_mapping": [ + ["Coin", "Amount"], + ["TokenV0", ""], # deprecated + ["TokenV1", "(TokenId, Amount)"], + ], + }, + "InputAddressPath": { + "type": "struct", + "type_mapping": [ + ["path", "Vec"], + ["multisig_idx", "Option"], + ], + }, + "InputMeta": { + "type": "struct", + "type_mapping": [ + ["addresses", "Vec"], + ], + }, + "Destination": { + "type": "enum", + "type_mapping": [ + ["AnyoneCanSpend", "()"], + ["Address", "(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"], + ], + }, + "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)"], + ], + }, + "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", "Compat"], + ], + }, + "PoolId": "H256", + "DelegationId": "H256", + "TokenId": "H256", + "OrderId": "H256", + "StakePoolData": { + "type": "struct", + "type_mapping": [ + ["value", "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"], + ], + }, + "TxInput": { + "type": "enum", + "type_mapping": [ + ["Utxo", "OutPoint"], + ["Account", "(AccountOutPoint)"], + ["AccountCommand", "(AccountNonce, AccountCommand)"], + ["OrderAccountCommand", "OrderAccountCommand"], + ], + }, + "TxInputWithAdditionalInfo": { + "type": "enum", + "type_mapping": [ + ["Utxo", "(OutPoint, AdditionalUtxoInfo)"], + ["Account", "(AccountOutPoint)"], + ["AccountCommand", "(AccountNonce, AccountCommand)"], + ["OrderAccountCommand", "(OrderAccountCommand, AdditionalOrderInfo)"], + ], + }, + "AccountOutPoint": { + "type": "struct", + "type_mapping": [ + ["nonce", "Compact"], + ["account", "AccountSpending"], + ], + }, + "AccountSpending": { + "type": "enum", + "type_mapping": [ + ["Delegation", "(H256, Amount)"], + ], + }, + "TransactionV1": { + "type": "struct", + "type_mapping": [ + ["version", "u8"], # has to be 1 + ["flags", "Compact"], + ["inputs", "Vec"], + ["outputs", "Vec"], + ], + }, + "InputWitness": { + "type": "enum", + "type_mapping": [ + ["NoSignature", "Option>"], + ["Standard", "StandardInputSignature"], + ], + }, + "TokenAdditionalInfo": { + "type": "struct", + "type_mapping": [ + ["num_decimals", "u8"], + ["ticker", "Vec"], + ], + }, + "PoolAdditionalInfo": { + "type": "struct", + "type_mapping": [ + ["staker_balance", "Amount"], + ], + }, + "OrderAdditionalInfo": { + "type": "struct", + "type_mapping": [ + ["initially_asked", "OutputValue"], + ["initially_given", "OutputValue"], + ["ask_balance", "Amount"], + ["give_balance", "Amount"], + ], + }, + "InfoId": { + "type": "enum", + "type_mapping": [ + ["TokenId", "H256"], + ["PoolId", "H256"], + ["OrderId", "H256"], + ], + }, + "SighashInputCommitment": { + "type": "enum", + "type_mapping": [ + ["None", "()"], + ["Utxo", "TxOutput"], + ["ProduceBlockFromStakeUtxo", "(TxOutput, Amount)"], + ["FillOrderAccountCommand", "(OutputValue, OutputValue)"], + [ + "ConcludeOrderAccountCommand", + "(OutputValue, OutputValue, Amount, Amount)", + ], + ], + }, + "TxAdditionalInfo": { + "type": "struct", + "type_mapping": [ + ["token_info", "BTreeMap"], + ["pool_info", "BTreeMap"], + ["order_info", "BTreeMap"], + ], + }, + "StandardInputSignature": { + "type": "struct", + "type_mapping": [ + ["sighash_type", "u8"], + ["raw_signature", "Vec"], + ], + }, + "SignedTransaction": { + "type": "struct", + "type_mapping": [ + ["transaction", "TransactionV1"], + ["signatures", "Vec"], + ], + }, + "PartiallySignedTransaction": { + "type": "struct", + "type_mapping": [ + ["tx", "TransactionV1"], + ["witnesses", "Vec>"], + ["input_utxos", "Vec>"], + ["destinations", "Vec>"], + ["htlc_secrets", "Vec>"], + ["additional_infos", "TxAdditionalInfo"], + ], + }, + "SignedTransactionIntent": { + "type": "struct", + "type_mapping": [ + ["signed_message", "String"], + ["signatures", "Vec>"], + ], + }, + "AuthorizedPublicKeyHashSpend": { + "type": "struct", + "type_mapping": [ + ["public_key", "PublicKey"], + ["signature", "Signature"], + ], + }, + "TxMetadataReq": { + "type": "struct", + "type_mapping": [ + ["coin", "u8"], + ["version", "u8"], + ["num_inputs", "u32"], + ["num_outputs", "u32"], + ], + }, + "TxInputReq": { + "type": "struct", + "type_mapping": [ + ["addresses", "Vec"], + ["inp", "TxInputWithAdditionalInfo"], + ], + }, + "AdditionalUtxoInfo": { + "type": "enum", + "type_mapping": [ + ["Utxo", "TxOutput"], + [ + "PoolInfo", + "(TxOutput, Amount)", + ], + ], + }, + "AdditionalOrderInfo": { + "type": "struct", + "type_mapping": [ + ["initially_asked", "OutputValue"], + ["initially_given", "OutputValue"], + ["ask_balance", "Amount"], + ["give_balance", "Amount"], + ], + }, + "TxOutputReq": { + "type": "struct", + "type_mapping": [ + ["out", "TxOutput"], + ], + }, + "SignTxReq": { + "type": "enum", + "type_mapping": [ + ["Input", "TxInputReq"], + ["InputCommitment", "SighashInputCommitment"], + ["Output", "TxOutputReq"], + ["NextSignature", "()"], + ], + }, + "MsgSignature": { + "type": "struct", + "type_mapping": [ + ["signature", "[u8; 64]"], + ], + }, + "GetPublicKeyRespones": { + "type": "struct", + "type_mapping": [ + ["public_key", "[u8; 65]"], + ["chain_code", "[u8; 32]"], + ], + }, + "SemVer": { + "type": "struct", + "type_mapping": [ + ["major", "u8"], + ["minor", "u8"], + ["patch", "u16"], + ], + }, + "PeerAddressIp4": { + "type": "struct", + "type_mapping": [ + ["ip", "[u8; 4]"], + ["port", "u16"], + ], + }, + "PeerAddressIp6": { + "type": "struct", + "type_mapping": [ + ["ip", "[u8; 16]"], + ["port", "u16"], + ], + }, + "PeerAddress": { + "type": "enum", + "type_mapping": [ + ["Ip4", "PeerAddressIp4"], + ["Ip6", "PeerAddressIp6"], + ], + }, + "HandshakeHello": { + "type": "struct", + "type_mapping": [ + ["protocol", "u32"], + ["network", "[u8; 4]"], + ["services", "u64"], + ["user_agent", "String"], + ["version", "SemVer"], + ["receiver_address", "Option"], + ["current_time", "Compact"], + ["handshake_nonce", "u64"], + ], + }, + "HandshakeHelloAck": { + "type": "struct", + "type_mapping": [ + ["protocol", "u32"], + ["network", "[u8; 4]"], + ["services", "u64"], + ["user_agent", "String"], + ["version", "SemVer"], + ["receiver_address", "Option"], + ["current_time", "Compact"], + ], + }, + "HandshakeMessage": { + "type": "enum", + "type_mapping": [ + ["Hello", "HandshakeHello"], + ["HelloAck", "HandshakeHelloAck"], + ], + }, + "PingMessage": { + "type": "struct", + "type_mapping": [ + ["nonce", "u64"], + ], + }, + "BlockHeader": { + "type": "struct", + "type_mapping": [ + ["version", "u8"], + ["prev_block_id", "H256"], + ["tx_merkle_root", "H256"], + ["witness_merkle_root", "H256"], + ["timestamp", "Compact"], + ["consensus_data", "ConsensusData"], + ], + }, + "SignedBlockHeader": { + "type": "struct", + "type_mapping": [ + ["header", "BlockHeader"], + ["signature", "BlockHeaderSignature"], + ], + }, + "BlockHeaderSignature": { + "type": "enum", + "type_mapping": [ + ["None", "()"], + ["HeaderSignature", "(BlockHeaderSignatureData)"], + ], + }, + "BlockHeaderSignatureData": { + "type": "struct", + "type_mapping": [ + ["signature", "(Signature)"], + ], + }, + "Signature": { + "type": "enum", + "type_mapping": [ + ["Secp256k1Schnorr", "[u8; 64]"], + ], + }, + "ConsensusData": { + "type": "enum", + "type_mapping": [ + ["None", "()"], + ["PoW", "PoWData"], + ["PoS", "PoSData"], + ], + }, + "PoWData": { + "type": "struct", + "type_mapping": [ + ["bits", "u32"], + ["nonce", "u128"], + ], + }, + "PoSData": { + "type": "struct", + "type_mapping": [ + ["kernel_inputs", "Vec"], + ["kernel_witness", "Vec"], + ["stake_pool_id", "PoolId"], + ["vrf_data", "VRFReturn"], + ["compact_target", "u32"], + ], + }, + "VRFReturn": { + "type": "enum", + "type_mapping": [ + ["Schnorrkel", "(SchnorrkelVRFReturn)"], + ], + }, + "SchnorrkelVRFReturn": { + "type": "struct", + "type_mapping": [ + ["preout", "[u8; 32]"], + ["proof", "[u8; 64]"], + ], + }, + "BlockIdAtHeight": "H256", + "BlockBody": { + "type": "struct", + "type_mapping": [ + ["reward", "BlockReward"], + ["transactions", "Vec"], + ], + }, + "BlockReward": { + "type": "struct", + "type_mapping": [ + ["reward_outputs", "Vec"], + ], + }, + "Block": { + "type": "enum", + "type_mapping": [ + ["V1", "(BlockV1)"], + ], + }, + "BlockV1": { + "type": "struct", + "type_mapping": [ + ["header", "SignedBlockHeader"], + ["body", "BlockBody"], + ], + }, + "Id": "[u8; 32]", + "HeaderListRequest": "Vec", + "HeaderList": "Vec", + "TransactionResponse": { + "type": "enum", + "type_mapping": [ + ["not_found", "Id"], + ["found", "SignedTransaction"], + ], + }, + "Message": { + "type": "enum", + "type_mapping": [ + ["handshake", "HandshakeMessage"], + ["ping_request", "PingMessage"], + ["ping_response", "PingMessage"], + ["new_transaction", "Id"], + ["header_list_request", "HeaderListRequest"], + ["header_list", "HeaderList"], + ["block_list_request", "Vec"], + ["block_response", "()"], + ["announce_addr_request", "PeerAddress"], + ["addr_list_request", "()"], + ["addr_list_response", "Vec"], + ["transaction_request", "Id"], + ["transaction_response", "TransactionResponse"], + ], + }, + "GenerateBlockInputData": { + "type": "enum", + "type_mapping": [ + ["None", "()"], + ["PoW", "PoWGenerateBlockInputData"], + ["PoS", "PoSGenerateBlockInputData"], + ], + }, + "PoSGenerateBlockInputData": { + "type": "struct", + "type_mapping": [ + ["stake_private_key", "PrivateKey"], + ["vrf_private_key", "VRFPrivateKey"], + ["pool_id", "PoolId"], + ["kernel_inputs", "Vec"], + ["kernel_input_utxo", "Vec"], + ], + }, + "Privatekey": { + "type": "struct", + "type_mapping": [ + ["key", "PrivateKeyHolder"], + ], + }, + "PrivateKeyHolder": { + "type": "enum", + "type_mapping": [ + ["Secp256k1Schnorr", "(Secp256k1PrivateKey)"], + ], + }, + "Secp256k1PrivateKey": { + "type": "struct", + "type_mapping": [ + ["data", "[u8; 32]"], + ], + }, + "VRFPrivateKey": { + "type": "struct", + "type_mapping": [ + ["key", "VRFPrivateKeyHolder"], + ], + }, + "VRFPrivateKeyHolder": { + "type": "enum", + "type_mapping": [ + ["Schnorrkel", "(SchnorrkelPrivateKey)"], + ], + }, + "SchnorrkelPrivateKey": { + "type": "struct", + "type_mapping": [["key", "[u8; 64]"]], + }, + "PoWGenerateBlockInputData": { + "type": "struct", + "type_mapping": [ + ["reward_destination", "Destination"], + ], + }, + } + } + + 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..b5f7aca --- /dev/null +++ b/tests/application_client/mintlayer_command_sender.py @@ -0,0 +1,277 @@ +from contextlib import contextmanager +from enum import IntEnum +from typing import Any, Generator, List, Optional + +import scalecodec # type: ignore +from ragger.backend.interface import RAPDU, BackendInterface + +from .mintlayer_transaction import Transaction + +tx_metadata_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "TxMetadataReq" +) +sign_tx_req_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "SignTxReq" +) + +MAX_APDU_LEN: int = 255 - 5 # 255 - CLA, INS, P1, P2, Lc + +CLA: int = 0xE1 + + +class P1(IntEnum): + # Parameter 1 for first APDU number. + P1_START = 0x00 + P1_TX_INPUT = 0x01 + P1_TX_INPUT_COMMITMENT = 0x02 + P1_TX_OUTPUT = 0x03 + P1_TX_NEXT_SIG = 0x04 + # Parameter 1 for maximum APDU number. + P1_MAX = 0x03 + # 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 + + +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_WRONG_RESPONSE_LENGTH = 0xB000 + SW_DISPLAY_BIP32_PATH_FAIL = 0xB001 + SW_WRONG_TX_LENGTH = 0xB002 + SW_WRONG_CONTEXT = 0xB009 + SW_DESERIALIZE_FAIL = 0xB00A + SW_MAX_BUFFER_LEN_EXCEEDED = 0xB014 + + +def split_message(message: bytes, max_size: int) -> List[bytes]: + 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=P1.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(path) + + return self.backend.exchange( + cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=P1.P1_START, + 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(path) + + with self.backend.exchange_async( + cla=CLA, + ins=InsType.GET_PUBLIC_KEY, + p1=P1.P1_CONFIRM, + p2=P2.P2_LAST, + data=data, + ) as response: + yield response + + @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(path) + ) + + self.backend.exchange( + cla=CLA, ins=InsType.SIGN_MESSAGE, p1=P1.P1_START, p2=P2.P2_LAST, data=data + ) + messages = split_message(message, MAX_APDU_LEN) + idx: int = P1.P1_START + 1 + + for msg in messages[:-1]: + self.backend.exchange( + cla=CLA, ins=InsType.SIGN_MESSAGE, p1=idx, p2=P2.P2_MORE, data=msg + ) + + with self.backend.exchange_async( + cla=CLA, ins=InsType.SIGN_MESSAGE, p1=idx, p2=P2.P2_LAST, data=messages[-1] + ) as response: + yield response + + @contextmanager + def sign_tx(self, transaction: Transaction) -> Generator[None, None, None]: + metadata = tx_metadata_obj.encode( + { + "coin": transaction.coin, + "version": 1, + "num_inputs": len(transaction.inputs), + "num_outputs": len(transaction.outputs), + } + ).data + + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_START, + p2=P2.P2_LAST, + data=bytes(metadata), + ) + print("metadata ", res) + + for inp in transaction.inputs: + chunks = split_message(inp, MAX_APDU_LEN) + for chunk in chunks[:-1]: + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_INPUT, + p2=P2.P2_MORE, + data=chunk, + ) + print("inp chunk ", res) + + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_INPUT, + p2=P2.P2_LAST, + data=chunks[-1], + ) + print("inp ", res) + + for inp in transaction.input_commitments: + chunks = split_message(inp, MAX_APDU_LEN) + for chunk in chunks[:-1]: + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_INPUT_COMMITMENT, + p2=P2.P2_MORE, + data=chunk, + ) + + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_INPUT_COMMITMENT, + p2=P2.P2_LAST, + data=chunks[-1], + ) + + for out in transaction.outputs[:-1]: + chunks = split_message(out, MAX_APDU_LEN) + for chunk in chunks[:-1]: + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_OUTPUT, + p2=P2.P2_MORE, + data=chunk, + ) + print("Out chunk ", res) + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_OUTPUT, + p2=P2.P2_LAST, + data=chunks[-1], + ) + print("Out ", res) + + chunks = split_message(transaction.outputs[-1], MAX_APDU_LEN) + + for chunk in chunks[:-1]: + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_OUTPUT, + p2=P2.P2_MORE, + data=chunk, + ) + print("Last Out chunk ", res) + + with self.backend.exchange_async( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_OUTPUT, + p2=P2.P2_LAST, + data=chunks[-1], + ) as response: + yield response + + def get_async_response(self) -> Optional[RAPDU]: + return self.backend.last_async_response + + def get_all_signatures(self, tx: Transaction) -> List[bytes | Any]: + if self.backend.last_async_response is None: + raise ValueError("None response") + + next_sig = sign_tx_req_obj.encode({"NextSignature": None}).data + responses = [self.backend.last_async_response.data] + for _ in tx.inputs[1:]: + res = self.backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_NEXT_SIG, + p2=P2.P2_LAST, + data=next_sig, + ) + if res is not None: + responses.append(res.data) + else: + raise ValueError("None response") + return responses + + +def hardened_index(index: int) -> int: + return index | 1 << 31 + + +def pack_derivation_path(derivation_path: str) -> bytes: + path_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("Bip32Path") + + split = derivation_path.split("/") + + if split[0] != "m": + raise ValueError("Error master expected") + + path = [] + for value in split[1:]: + if value == "": + raise ValueError(f'Error missing value in split list "{split}"') + if value.endswith("'"): + path.append(hardened_index(int(value[:-1]))) + else: + path.append(int(value)) + + return path_obj.encode(path).data diff --git a/tests/application_client/mintlayer_response_unpacker.py b/tests/application_client/mintlayer_response_unpacker.py new file mode 100644 index 0000000..d76ded4 --- /dev/null +++ b/tests/application_client/mintlayer_response_unpacker.py @@ -0,0 +1,101 @@ +from struct import unpack +from typing import Tuple + +import scalecodec # type: ignore + + +# 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: +# response = pub_key_len (1) +# pub_key (var) +# chain_code_len (1) +# chain_code (var) +def unpack_get_public_key_response(response: bytes) -> Tuple[int, bytes, int, bytes]: + response_bytes = scalecodec.base.ScaleBytes(response) + msg_signature_obj = scalecodec.base.RuntimeConfiguration().create_scale_object( + "GetPublicKeyRespones", data=response_bytes + ) + sig = msg_signature_obj.decode() + + print(sig) + + pub_key = bytes.fromhex(sig["public_key"][2:]) + pub_key_len = len(pub_key) + chain_code = bytes.fromhex(sig["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: +# response = sig_len (1) +# sig (var) +def unpack_sign_message_response(response: bytes) -> Tuple[int, bytes]: + response, sig_len, sig = pop_size_prefixed_buf_from_buf(response) + + assert sig_len == 64 + assert len(response) == 0 + return sig_len, sig + + +# Unpack from response: +# response = der_sig_len (1) +# der_sig (var) +# v (1) +def unpack_sign_tx_response(response: bytes) -> Tuple[int, bytes, int]: + response, der_sig_len, der_sig = pop_size_prefixed_buf_from_buf(response) + response, v = pop_sized_buf_from_buffer(response, 1) + + assert len(response) == 0 + + return der_sig_len, der_sig, int.from_bytes(v, byteorder="big") diff --git a/tests/application_client/mintlayer_transaction.py b/tests/application_client/mintlayer_transaction.py new file mode 100644 index 0000000..acd6ac1 --- /dev/null +++ b/tests/application_client/mintlayer_transaction.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from typing import List + + +class TransactionError(Exception): + pass + + +@dataclass +class Transaction: + coin: int + inputs: List[bytes] + input_commitments: List[bytes] + outputs: List[bytes] diff --git a/tests/application_client/mintlayer_utils.py b/tests/application_client/mintlayer_utils.py new file mode 100644 index 0000000..fd96e62 --- /dev/null +++ b/tests/application_client/mintlayer_utils.py @@ -0,0 +1,61 @@ +from io import BytesIO +from typing import Optional, Literal + + +UINT64_MAX: int = 2**64-1 +UINT32_MAX: int = 2**32-1 +UINT16_MAX: int = 2**16-1 + + +def write_varint(n: int) -> bytes: + if n < 0xFC: + return n.to_bytes(1, byteorder="little") + + if n <= UINT16_MAX: + return b"\xFD" + n.to_bytes(2, byteorder="little") + + if n <= UINT32_MAX: + return b"\xFE" + n.to_bytes(4, byteorder="little") + + if n <= UINT64_MAX: + return b"\xFF" + n.to_bytes(8, byteorder="little") + + raise ValueError(f"Can't write to varint: '{n}'!") + + +def read_varint(buf: BytesIO, + prefix: Optional[bytes] = None) -> int: + b: bytes = prefix if prefix else buf.read(1) + + if not b: + raise ValueError(f"Can't read prefix: '{b.hex()}'!") + + n: int = {b"\xfd": 2, b"\xfe": 4, b"\xff": 8}.get(b, 1) # default to 1 + + b = buf.read(n) if n > 1 else b + + if len(b) != n: + raise ValueError("Can't read varint!") + + return int.from_bytes(b, byteorder="little") + + +def read(buf: BytesIO, size: int) -> bytes: + b: bytes = buf.read(size) + + if len(b) < size: + raise ValueError(f"Can't read {size} bytes in buffer!") + + return b + + +def read_uint(buf: BytesIO, + bit_len: int, + byteorder: Literal['big', 'little'] = 'little') -> int: + size: int = bit_len // 8 + b: bytes = buf.read(size) + + if len(b) < size: + raise ValueError(f"Can't read u{bit_len} in buffer!") + + return int.from_bytes(b, byteorder) 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/boil-freeze.txt b/tests/boil-freeze.txt new file mode 100644 index 0000000..a74fc92 --- /dev/null +++ b/tests/boil-freeze.txt @@ -0,0 +1,60 @@ +aniso8601==9.0.1 +asn1crypto==1.5.1 +attrs==22.2.0 +bip-utils==2.7.0 +cbor2==5.4.6 +certifi==2022.12.7 +cffi==1.15.1 +charset-normalizer==3.0.1 +click==8.1.3 +coincurve==17.0.0 +construct==2.10.68 +crcmod==1.7 +cryptography==39.0.1 +ecdsa==0.16.1 +ed25519-blake2b==1.4 +exceptiongroup==1.1.0 +Flask==2.1.2 +Flask-RESTful==0.3.9 +hidapi==0.13.1 +idna==3.4 +importlib-metadata==6.0.0 +importlib-resources==5.12.0 +iniconfig==2.0.0 +intelhex==2.3.0 +itsdangerous==2.1.2 +Jinja2==3.1.2 +jsonschema==4.17.3 +ledgerwallet==0.2.3 +MarkupSafe==2.1.2 +mnemonic==0.20 +packaging==23.0 +Pillow==9.4.0 +pkg_resources==0.0.0 +pkgutil_resolve_name==1.3.10 +pluggy==1.0.0 +protobuf==3.20.3 +py-sr25519-bindings==0.1.4 +pycparser==2.21 +pycryptodome==3.17 +pyelftools==0.29 +PyNaCl==1.5.0 +PyQt5==5.15.9 +PyQt5-Qt5==5.15.2 +PyQt5-sip==12.11.1 +pyrsistent==0.19.3 +pysha3==1.0.2 +pytesseract==0.3.10 +pytest==7.2.1 +pytz==2022.7.1 +ragger==1.6.0 +requests==2.28.2 +semver==2.13.0 +six==1.16.0 +speculos==0.1.224 +tabulate==0.9.0 +toml==0.10.2 +tomli==2.0.1 +urllib3==1.26.14 +Werkzeug==2.2.3 +zipp==3.15.0 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/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..fdc689b --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,6 @@ +pytest +ragger[speculos,ledgerwallet]>=1.21.1 +ecdsa>=0.16.1,<0.17.0 +safe-pysha3>=1.0.0,<2.0.0 +tomli>=2.0.1 +scalecodec>=1.2.11 \ No newline at end of file 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/flex/test_app_mainmenu/00000.png b/tests/snapshots/flex/test_app_mainmenu/00000.png new file mode 100644 index 0000000..c0e88bb 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..be844f4 Binary files /dev/null and b/tests/snapshots/flex/test_app_mainmenu/00001.png differ diff --git a/tests/snapshots/flex/test_app_mainmenu/00002.png b/tests/snapshots/flex/test_app_mainmenu/00002.png new file mode 100644 index 0000000..ad64488 Binary files /dev/null and b/tests/snapshots/flex/test_app_mainmenu/00002.png differ diff --git a/tests/snapshots/flex/test_app_mainmenu/00003.png b/tests/snapshots/flex/test_app_mainmenu/00003.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_app_mainmenu/00003.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..c0e88bb 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_get_public_key_confirm_refused/00003.png b/tests/snapshots/flex/test_get_public_key_confirm_refused/00003.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_get_public_key_confirm_refused/00003.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..ab24a07 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..c0e88bb 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..ab24a07 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..c0e88bb 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..ab24a07 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_message_refused/00005.png b/tests/snapshots/flex/test_sign_message_refused/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_message_refused/00005.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..45a4df5 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/00001.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00001.png new file mode 100644 index 0000000..a35c8fe Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00002.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00002.png new file mode 100644 index 0000000..f8cf440 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00003.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00003.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_authority/00004.png b/tests/snapshots/flex/test_sign_tx_change_token_authority/00004.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_authority/00004.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..45a4df5 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/00001.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00001.png new file mode 100644 index 0000000..a35c8fe Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00002.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00002.png new file mode 100644 index 0000000..3d2dbe5 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00003.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00003.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00004.png b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00004.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_change_token_metadata_uri/00004.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00001.png new file mode 100644 index 0000000..22a1f7f Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00002.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00002.png new file mode 100644 index 0000000..9a4e534 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00003.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00003.png new file mode 100644 index 0000000..2fa6cf6 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00004.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00004.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_delegation/00005.png b/tests/snapshots/flex/test_sign_tx_create_delegation/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_delegation/00005.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00001.png new file mode 100644 index 0000000..4477f6e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00002.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00002.png new file mode 100644 index 0000000..780f8bd Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00003.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00003.png new file mode 100644 index 0000000..da03051 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00004.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00004.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_create_stake_pool/00005.png b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_create_stake_pool/00005.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegation_staking/00000.png b/tests/snapshots/flex/test_sign_tx_delegation_staking/00000.png new file mode 100644 index 0000000..45a4df5 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegation_staking/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegation_staking/00001.png b/tests/snapshots/flex/test_sign_tx_delegation_staking/00001.png new file mode 100644 index 0000000..77e8ab2 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegation_staking/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegation_staking/00002.png b/tests/snapshots/flex/test_sign_tx_delegation_staking/00002.png new file mode 100644 index 0000000..93e8caf Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegation_staking/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegation_staking/00003.png b/tests/snapshots/flex/test_sign_tx_delegation_staking/00003.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegation_staking/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_delegation_staking/00004.png b/tests/snapshots/flex/test_sign_tx_delegation_staking/00004.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_delegation_staking/00004.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..45a4df5 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/00001.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00001.png new file mode 100644 index 0000000..a35c8fe Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00002.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00002.png new file mode 100644 index 0000000..4a5b80c Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00003.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00003.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_freeze_tokens/00004.png b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00004.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_freeze_tokens/00004.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_htlc/00001.png new file mode 100644 index 0000000..f2dcdf5 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00002.png b/tests/snapshots/flex/test_sign_tx_htlc/00002.png new file mode 100644 index 0000000..fa66a8a Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00003.png b/tests/snapshots/flex/test_sign_tx_htlc/00003.png new file mode 100644 index 0000000..da95e9c Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00004.png b/tests/snapshots/flex/test_sign_tx_htlc/00004.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_htlc/00005.png b/tests/snapshots/flex/test_sign_tx_htlc/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_htlc/00005.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00001.png new file mode 100644 index 0000000..06c369e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00002.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00002.png new file mode 100644 index 0000000..9a4e534 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00003.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00003.png new file mode 100644 index 0000000..3babddd Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00004.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00004.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00005.png b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_fungible_token/00005.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00001.png new file mode 100644 index 0000000..f7f99ec Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00002.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00002.png new file mode 100644 index 0000000..36cc105 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00003.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00003.png new file mode 100644 index 0000000..2d3241f Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00004.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00004.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_issue_nft/00005.png b/tests/snapshots/flex/test_sign_tx_issue_nft/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_issue_nft/00005.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00001.png new file mode 100644 index 0000000..65cbecb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00002.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00002.png new file mode 100644 index 0000000..780f8bd Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00003.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00003.png new file mode 100644 index 0000000..41ed3ff Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00004.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00004.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00005.png b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_lock_then_transfer/00005.png differ diff --git a/tests/snapshots/flex/test_sign_tx_long_tx/00000.png b/tests/snapshots/flex/test_sign_tx_long_tx/00000.png new file mode 100644 index 0000000..0649145 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_long_tx/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_long_tx/00001.png b/tests/snapshots/flex/test_sign_tx_long_tx/00001.png new file mode 100644 index 0000000..fa1a974 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_long_tx/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_long_tx/00002.png b/tests/snapshots/flex/test_sign_tx_long_tx/00002.png new file mode 100644 index 0000000..ff0fd44 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_long_tx/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_long_tx/00003.png b/tests/snapshots/flex/test_sign_tx_long_tx/00003.png new file mode 100644 index 0000000..fd33e09 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_long_tx/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_long_tx/00004.png b/tests/snapshots/flex/test_sign_tx_long_tx/00004.png new file mode 100644 index 0000000..be51a9d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_long_tx/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_long_tx/00005.png b/tests/snapshots/flex/test_sign_tx_long_tx/00005.png new file mode 100644 index 0000000..8a9212d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_long_tx/00005.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00001.png new file mode 100644 index 0000000..a7e344d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00002.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00002.png new file mode 100644 index 0000000..82d6a13 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00003.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00003.png new file mode 100644 index 0000000..55d938e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00004.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00004.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_mint_tokens/00005.png b/tests/snapshots/flex/test_sign_tx_mint_tokens/00005.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_mint_tokens/00005.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..e22032b 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/00001.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00001.png new file mode 100644 index 0000000..b27ad50 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00002.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00002.png new file mode 100644 index 0000000..f198ed6 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00003.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00003.png new file mode 100644 index 0000000..5c9b386 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00004.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00004.png new file mode 100644 index 0000000..3ab6bae Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00005.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00005.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00005.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_conclude/00006.png b/tests/snapshots/flex/test_sign_tx_order_conclude/00006.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_conclude/00006.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..e22032b 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/00001.png b/tests/snapshots/flex/test_sign_tx_order_fill/00001.png new file mode 100644 index 0000000..7657c0b Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00002.png b/tests/snapshots/flex/test_sign_tx_order_fill/00002.png new file mode 100644 index 0000000..d67d882 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00003.png b/tests/snapshots/flex/test_sign_tx_order_fill/00003.png new file mode 100644 index 0000000..5c9b386 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00004.png b/tests/snapshots/flex/test_sign_tx_order_fill/00004.png new file mode 100644 index 0000000..4e3f52b Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00005.png b/tests/snapshots/flex/test_sign_tx_order_fill/00005.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00005.png differ diff --git a/tests/snapshots/flex/test_sign_tx_order_fill/00006.png b/tests/snapshots/flex/test_sign_tx_order_fill/00006.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_order_fill/00006.png differ diff --git a/tests/snapshots/flex/test_sign_tx_refused/00000.png b/tests/snapshots/flex/test_sign_tx_refused/00000.png new file mode 100644 index 0000000..c2a4a2f Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_refused/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_refused/00001.png b/tests/snapshots/flex/test_sign_tx_refused/00001.png new file mode 100644 index 0000000..5d919f7 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_refused/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_refused/00002.png b/tests/snapshots/flex/test_sign_tx_refused/00002.png new file mode 100644 index 0000000..9a270f5 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_refused/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_refused/00003.png b/tests/snapshots/flex/test_sign_tx_refused/00003.png new file mode 100644 index 0000000..6a11e11 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_refused/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_refused/00004.png b/tests/snapshots/flex/test_sign_tx_refused/00004.png new file mode 100644 index 0000000..6bbdf2f Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_refused/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_refused/00005.png b/tests/snapshots/flex/test_sign_tx_refused/00005.png new file mode 100644 index 0000000..8a9212d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_refused/00005.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx/00000.png b/tests/snapshots/flex/test_sign_tx_short_tx/00000.png new file mode 100644 index 0000000..d60a071 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx/00001.png b/tests/snapshots/flex/test_sign_tx_short_tx/00001.png new file mode 100644 index 0000000..fe24a7b Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx/00002.png b/tests/snapshots/flex/test_sign_tx_short_tx/00002.png new file mode 100644 index 0000000..7752e76 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx/00003.png b/tests/snapshots/flex/test_sign_tx_short_tx/00003.png new file mode 100644 index 0000000..6d18dff Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx/00004.png b/tests/snapshots/flex/test_sign_tx_short_tx/00004.png new file mode 100644 index 0000000..be51a9d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx/00005.png b/tests/snapshots/flex/test_sign_tx_short_tx/00005.png new file mode 100644 index 0000000..ca1717c Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx/00005.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00000.png b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00000.png new file mode 100644 index 0000000..c2a4a2f Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00000.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00001.png b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00001.png new file mode 100644 index 0000000..2d0c474 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00002.png b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00002.png new file mode 100644 index 0000000..9a270f5 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00003.png b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00003.png new file mode 100644 index 0000000..be51a9d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00004.png b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00004.png new file mode 100644 index 0000000..8a9212d Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_short_tx_no_memo/00004.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_transfer/00001.png new file mode 100644 index 0000000..55ea024 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00002.png b/tests/snapshots/flex/test_sign_tx_transfer/00002.png new file mode 100644 index 0000000..d7ec2da Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00003.png b/tests/snapshots/flex/test_sign_tx_transfer/00003.png new file mode 100644 index 0000000..4b294de Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00004.png b/tests/snapshots/flex/test_sign_tx_transfer/00004.png new file mode 100644 index 0000000..435aa78 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_transfer/00005.png b/tests/snapshots/flex/test_sign_tx_transfer/00005.png new file mode 100644 index 0000000..c0e88bb Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_transfer/00005.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..45a4df5 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/00001.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00001.png new file mode 100644 index 0000000..a35c8fe Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00002.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00002.png new file mode 100644 index 0000000..06b6496 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00003.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00003.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00004.png b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00004.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unfreeze_tokens/00004.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..28682b4 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/00001.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00001.png new file mode 100644 index 0000000..d8a9bad Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00001.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00002.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00002.png new file mode 100644 index 0000000..7a34a97 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00002.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00003.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00003.png new file mode 100644 index 0000000..a3c1ec1 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00003.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00004.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00004.png new file mode 100644 index 0000000..9f79b05 Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00004.png differ diff --git a/tests/snapshots/flex/test_sign_tx_unmint_tokens/00005.png b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00005.png new file mode 100644 index 0000000..553394e Binary files /dev/null and b/tests/snapshots/flex/test_sign_tx_unmint_tokens/00005.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..bdbc0ff 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..77bcafd Binary files /dev/null and b/tests/snapshots/nanosp/test_app_mainmenu/00002.png differ diff --git a/tests/snapshots/nanosp/test_app_mainmenu/00003.png b/tests/snapshots/nanosp/test_app_mainmenu/00003.png new file mode 100644 index 0000000..f265d69 Binary files /dev/null and b/tests/snapshots/nanosp/test_app_mainmenu/00003.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_accepted/00005.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00005.png new file mode 100644 index 0000000..32e7443 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00005.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00006.png b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00006.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_accepted/00006.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_get_public_key_confirm_refused/00006.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00006.png new file mode 100644 index 0000000..fbfed86 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00006.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00007.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00007.png new file mode 100644 index 0000000..bdbc0ff Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00007.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00008.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00008.png new file mode 100644 index 0000000..77bcafd Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00008.png differ diff --git a/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00009.png b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00009.png new file mode 100644 index 0000000..f265d69 Binary files /dev/null and b/tests/snapshots/nanosp/test_get_public_key_confirm_refused/00009.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..8cae4d6 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..602c22a 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..305308f 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..3617b8d 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..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_message/00006.png b/tests/snapshots/nanosp/test_sign_message/00006.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message/00006.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..8cae4d6 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..208f5d8 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..3617b8d 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..55bb5c8 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_pkh/00005.png b/tests/snapshots/nanosp/test_sign_message_pkh/00005.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_pkh/00005.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..8cae4d6 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..602c22a 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..305308f 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..3617b8d 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..55bb5c8 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..12caa58 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_message_refused/00007.png b/tests/snapshots/nanosp/test_sign_message_refused/00007.png new file mode 100644 index 0000000..d917d19 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_message_refused/00007.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00002.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00003.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00004.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00004.png new file mode 100644 index 0000000..2fca943 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00005.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00006.png b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00006.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_authority/00006.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00002.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00003.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00004.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00004.png new file mode 100644 index 0000000..c7d5676 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00005.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00006.png b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00006.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_change_token_metadata_uri/00006.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00001.png new file mode 100644 index 0000000..2ce4f7e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00002.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00002.png new file mode 100644 index 0000000..3169a43 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00003.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00003.png new file mode 100644 index 0000000..4100b0b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00004.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00004.png new file mode 100644 index 0000000..46b368c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00005.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00005.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00006.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00006.png new file mode 100644 index 0000000..32a3fd1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00007.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00007.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_delegation/00008.png b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00008.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_delegation/00008.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00001.png new file mode 100644 index 0000000..3fbfcda Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00002.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00002.png new file mode 100644 index 0000000..dc3f263 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00003.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00003.png new file mode 100644 index 0000000..dd49ec2 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00004.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00004.png new file mode 100644 index 0000000..7623766 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00005.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00005.png new file mode 100644 index 0000000..0562b8b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00006.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00006.png new file mode 100644 index 0000000..9880fbf Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00007.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00007.png new file mode 100644 index 0000000..5d19c53 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00008.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00008.png new file mode 100644 index 0000000..7184c47 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00009.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00009.png new file mode 100644 index 0000000..f278bff Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00009.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00010.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00010.png new file mode 100644 index 0000000..06704db Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00010.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00011.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00011.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00011.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00012.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00012.png new file mode 100644 index 0000000..17ff048 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00012.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00013.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00013.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00013.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00014.png b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00014.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_create_stake_pool/00014.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00000.png b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00001.png b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00001.png new file mode 100644 index 0000000..3645243 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00002.png b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00002.png new file mode 100644 index 0000000..2df9d67 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00003.png b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00003.png new file mode 100644 index 0000000..0623b1b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00004.png b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00004.png new file mode 100644 index 0000000..bbd76c8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00005.png b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00005.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00006.png b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00006.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_delegation_staking/00006.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00002.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00003.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00004.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00004.png new file mode 100644 index 0000000..bc9b1a8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00005.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00006.png b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00006.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_freeze_tokens/00006.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00001.png new file mode 100644 index 0000000..aab0aed Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00002.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00002.png new file mode 100644 index 0000000..f113d41 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00003.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00003.png new file mode 100644 index 0000000..195e6f3 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00004.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00004.png new file mode 100644 index 0000000..778f66f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00005.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00005.png new file mode 100644 index 0000000..0d5d509 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00006.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00006.png new file mode 100644 index 0000000..5fc12d1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00007.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00007.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00008.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00008.png new file mode 100644 index 0000000..62b7685 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00009.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00009.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00009.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00010.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00010.png new file mode 100644 index 0000000..b168952 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00010.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00011.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00011.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00011.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_htlc/00012.png b/tests/snapshots/nanosp/test_sign_tx_htlc/00012.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_htlc/00012.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00001.png new file mode 100644 index 0000000..00051f8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00002.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00002.png new file mode 100644 index 0000000..c09832c Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00003.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00003.png new file mode 100644 index 0000000..419f5f0 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00004.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00004.png new file mode 100644 index 0000000..26db145 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00005.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00005.png new file mode 100644 index 0000000..060d61b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00006.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00006.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00007.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00007.png new file mode 100644 index 0000000..d3ca23a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00008.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00008.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00009.png b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00009.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_fungible_token/00009.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00001.png new file mode 100644 index 0000000..2318573 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00002.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00002.png new file mode 100644 index 0000000..d84fdcd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00003.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00003.png new file mode 100644 index 0000000..415b46b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00004.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00004.png new file mode 100644 index 0000000..7fb1f67 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00005.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00005.png new file mode 100644 index 0000000..e14acb6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00006.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00006.png new file mode 100644 index 0000000..03f61f6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00007.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00007.png new file mode 100644 index 0000000..f678edc Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00008.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00008.png new file mode 100644 index 0000000..0a262d5 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00009.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00009.png new file mode 100644 index 0000000..1a586fb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00009.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00010.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00010.png new file mode 100644 index 0000000..af149c9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00010.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00011.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00011.png new file mode 100644 index 0000000..4336cee Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00011.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00012.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00012.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00012.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_issue_nft/00013.png b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00013.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_issue_nft/00013.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00001.png new file mode 100644 index 0000000..a7b61f1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00002.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00002.png new file mode 100644 index 0000000..c31d61b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00003.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00003.png new file mode 100644 index 0000000..291da46 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00004.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00004.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00005.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00005.png new file mode 100644 index 0000000..62b8f91 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00006.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00006.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00007.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00007.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00008.png b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00008.png new file mode 100644 index 0000000..f265d69 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_lock_then_transfer/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00000.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00000.png new file mode 100644 index 0000000..1597f7e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_long_tx/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00001.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00001.png new file mode 100644 index 0000000..8a3938b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_long_tx/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00002.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00002.png new file mode 100644 index 0000000..8b3c9da Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_long_tx/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00003.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00003.png new file mode 100644 index 0000000..4a2929d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_long_tx/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_long_tx/00004.png b/tests/snapshots/nanosp/test_sign_tx_long_tx/00004.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_long_tx/00004.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00001.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00002.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00002.png new file mode 100644 index 0000000..5d5329a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00003.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00003.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00004.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00004.png new file mode 100644 index 0000000..2b9bccd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00005.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00005.png new file mode 100644 index 0000000..eeaad30 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00006.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00006.png new file mode 100644 index 0000000..b807f41 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00007.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00007.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00008.png b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00008.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_mint_tokens/00008.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00002.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00002.png new file mode 100644 index 0000000..b71b25b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00003.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00003.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00004.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00004.png new file mode 100644 index 0000000..5d5329a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00005.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00005.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00006.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00006.png new file mode 100644 index 0000000..58eac55 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00007.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00007.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00008.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00008.png new file mode 100644 index 0000000..732826a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00009.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00009.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00009.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_conclude/00010.png b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00010.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_conclude/00010.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00002.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00002.png new file mode 100644 index 0000000..62b7685 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00003.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00003.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00004.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00004.png new file mode 100644 index 0000000..5d5329a Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00005.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00005.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00006.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00006.png new file mode 100644 index 0000000..6cf46ea Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00007.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00007.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00007.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00008.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00008.png new file mode 100644 index 0000000..b0aa4e8 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00009.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00009.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00009.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00010.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00010.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00010.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_order_fill/00011.png b/tests/snapshots/nanosp/test_sign_tx_order_fill/00011.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_order_fill/00011.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00000.png b/tests/snapshots/nanosp/test_sign_tx_refused/00000.png new file mode 100644 index 0000000..1597f7e Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_refused/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00001.png b/tests/snapshots/nanosp/test_sign_tx_refused/00001.png new file mode 100644 index 0000000..8a3938b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_refused/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00002.png b/tests/snapshots/nanosp/test_sign_tx_refused/00002.png new file mode 100644 index 0000000..8b3c9da Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_refused/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00003.png b/tests/snapshots/nanosp/test_sign_tx_refused/00003.png new file mode 100644 index 0000000..4a2929d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_refused/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00004.png b/tests/snapshots/nanosp/test_sign_tx_refused/00004.png new file mode 100644 index 0000000..09a1af1 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_refused/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_refused/00005.png b/tests/snapshots/nanosp/test_sign_tx_refused/00005.png new file mode 100644 index 0000000..26c6197 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_refused/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00000.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00000.png new file mode 100644 index 0000000..030b884 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_short_tx/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00001.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00001.png new file mode 100644 index 0000000..192c2bd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_short_tx/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00002.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00002.png new file mode 100644 index 0000000..75337cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_short_tx/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00003.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00003.png new file mode 100644 index 0000000..57832de Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_short_tx/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00004.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00004.png new file mode 100644 index 0000000..7389ab6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_short_tx/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00005.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00005.png new file mode 100644 index 0000000..32e7443 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_short_tx/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_short_tx/00006.png b/tests/snapshots/nanosp/test_sign_tx_short_tx/00006.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_short_tx/00006.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00001.png new file mode 100644 index 0000000..192c2bd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00002.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00002.png new file mode 100644 index 0000000..75337cb Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00003.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00003.png new file mode 100644 index 0000000..0378fc6 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00004.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00004.png new file mode 100644 index 0000000..639af51 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00005.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00005.png new file mode 100644 index 0000000..6e3cb60 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00006.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00006.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_transfer/00007.png b/tests/snapshots/nanosp/test_sign_tx_transfer/00007.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_transfer/00007.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00002.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00003.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00004.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00004.png new file mode 100644 index 0000000..fb41633 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00005.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00006.png b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00006.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unfreeze_tokens/00006.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/00001.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00002.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00003.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00003.png new file mode 100644 index 0000000..8973d80 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00004.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00004.png new file mode 100644 index 0000000..8ed052d Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00005.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00005.png new file mode 100644 index 0000000..fce7d0b Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00006.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00006.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00006.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00007.png b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00007.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_unmint_tokens/00007.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..bdbc0ff 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..77bcafd Binary files /dev/null and b/tests/snapshots/nanox/test_app_mainmenu/00002.png differ diff --git a/tests/snapshots/nanox/test_app_mainmenu/00003.png b/tests/snapshots/nanox/test_app_mainmenu/00003.png new file mode 100644 index 0000000..f265d69 Binary files /dev/null and b/tests/snapshots/nanox/test_app_mainmenu/00003.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_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..8cae4d6 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..602c22a 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..305308f 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..3617b8d 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..55bb5c8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_message/00006.png b/tests/snapshots/nanox/test_sign_message/00006.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message/00006.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..8cae4d6 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..208f5d8 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..3617b8d 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..55bb5c8 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_pkh/00005.png b/tests/snapshots/nanox/test_sign_message_pkh/00005.png new file mode 100644 index 0000000..ab20bae Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_pkh/00005.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..8cae4d6 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..602c22a 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..305308f 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..3617b8d 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..55bb5c8 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..12caa58 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_message_refused/00007.png b/tests/snapshots/nanox/test_sign_message_refused/00007.png new file mode 100644 index 0000000..d917d19 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_message_refused/00007.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/00001.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00002.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00003.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00004.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00004.png new file mode 100644 index 0000000..2fca943 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_authority/00005.png b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_authority/00005.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/00001.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00002.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00003.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00004.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00004.png new file mode 100644 index 0000000..c7d5676 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00005.png b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_change_token_metadata_uri/00005.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/00001.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00001.png new file mode 100644 index 0000000..2ce4f7e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00002.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00002.png new file mode 100644 index 0000000..3169a43 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00003.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00003.png new file mode 100644 index 0000000..4100b0b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00004.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00004.png new file mode 100644 index 0000000..46b368c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00005.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00005.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00006.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00006.png new file mode 100644 index 0000000..32a3fd1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_delegation/00007.png b/tests/snapshots/nanox/test_sign_tx_create_delegation/00007.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_delegation/00007.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/00001.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00001.png new file mode 100644 index 0000000..3fbfcda Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00002.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00002.png new file mode 100644 index 0000000..dc3f263 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00003.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00003.png new file mode 100644 index 0000000..dd49ec2 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00004.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00004.png new file mode 100644 index 0000000..7623766 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00005.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00005.png new file mode 100644 index 0000000..0562b8b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00006.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00006.png new file mode 100644 index 0000000..9880fbf Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00007.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00007.png new file mode 100644 index 0000000..5d19c53 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00007.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00008.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00008.png new file mode 100644 index 0000000..7184c47 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00008.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00009.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00009.png new file mode 100644 index 0000000..f278bff Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00009.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00010.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00010.png new file mode 100644 index 0000000..06704db Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00010.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00011.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00011.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00011.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00012.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00012.png new file mode 100644 index 0000000..17ff048 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00012.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00013.png b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00013.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_create_stake_pool/00013.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegation_staking/00000.png b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00000.png new file mode 100644 index 0000000..640e079 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegation_staking/00001.png b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00001.png new file mode 100644 index 0000000..3645243 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegation_staking/00002.png b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00002.png new file mode 100644 index 0000000..2df9d67 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegation_staking/00003.png b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00003.png new file mode 100644 index 0000000..0623b1b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegation_staking/00004.png b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00004.png new file mode 100644 index 0000000..bbd76c8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_delegation_staking/00005.png b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00005.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_delegation_staking/00005.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/00001.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00002.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00003.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00004.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00004.png new file mode 100644 index 0000000..bc9b1a8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00005.png b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_freeze_tokens/00005.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/00001.png b/tests/snapshots/nanox/test_sign_tx_htlc/00001.png new file mode 100644 index 0000000..aab0aed Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00002.png b/tests/snapshots/nanox/test_sign_tx_htlc/00002.png new file mode 100644 index 0000000..f113d41 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00003.png b/tests/snapshots/nanox/test_sign_tx_htlc/00003.png new file mode 100644 index 0000000..195e6f3 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00004.png b/tests/snapshots/nanox/test_sign_tx_htlc/00004.png new file mode 100644 index 0000000..778f66f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00005.png b/tests/snapshots/nanox/test_sign_tx_htlc/00005.png new file mode 100644 index 0000000..0d5d509 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00006.png b/tests/snapshots/nanox/test_sign_tx_htlc/00006.png new file mode 100644 index 0000000..5fc12d1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00007.png b/tests/snapshots/nanox/test_sign_tx_htlc/00007.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00007.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00008.png b/tests/snapshots/nanox/test_sign_tx_htlc/00008.png new file mode 100644 index 0000000..62b7685 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00008.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00009.png b/tests/snapshots/nanox/test_sign_tx_htlc/00009.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00009.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00010.png b/tests/snapshots/nanox/test_sign_tx_htlc/00010.png new file mode 100644 index 0000000..b168952 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00010.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_htlc/00011.png b/tests/snapshots/nanox/test_sign_tx_htlc/00011.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_htlc/00011.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/00001.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00001.png new file mode 100644 index 0000000..00051f8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00002.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00002.png new file mode 100644 index 0000000..c09832c Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00003.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00003.png new file mode 100644 index 0000000..419f5f0 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00004.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00004.png new file mode 100644 index 0000000..26db145 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00005.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00005.png new file mode 100644 index 0000000..060d61b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00006.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00006.png new file mode 100644 index 0000000..0f6ca54 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00007.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00007.png new file mode 100644 index 0000000..d3ca23a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00007.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00008.png b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00008.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_fungible_token/00008.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/00001.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00001.png new file mode 100644 index 0000000..2318573 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00002.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00002.png new file mode 100644 index 0000000..d84fdcd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00003.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00003.png new file mode 100644 index 0000000..415b46b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00004.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00004.png new file mode 100644 index 0000000..7fb1f67 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00005.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00005.png new file mode 100644 index 0000000..e14acb6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00006.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00006.png new file mode 100644 index 0000000..03f61f6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00007.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00007.png new file mode 100644 index 0000000..f678edc Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00007.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00008.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00008.png new file mode 100644 index 0000000..0a262d5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00008.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00009.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00009.png new file mode 100644 index 0000000..1a586fb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00009.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00010.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00010.png new file mode 100644 index 0000000..af149c9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00010.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00011.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00011.png new file mode 100644 index 0000000..4336cee Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00011.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_issue_nft/00012.png b/tests/snapshots/nanox/test_sign_tx_issue_nft/00012.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_issue_nft/00012.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/00001.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00001.png new file mode 100644 index 0000000..a7b61f1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00002.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00002.png new file mode 100644 index 0000000..c31d61b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00003.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00003.png new file mode 100644 index 0000000..291da46 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00004.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00004.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00005.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00005.png new file mode 100644 index 0000000..62b8f91 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00006.png b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00006.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_lock_then_transfer/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00000.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00000.png new file mode 100644 index 0000000..1597f7e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_long_tx/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00001.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00001.png new file mode 100644 index 0000000..8a3938b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_long_tx/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00002.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00002.png new file mode 100644 index 0000000..8b3c9da Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_long_tx/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00003.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00003.png new file mode 100644 index 0000000..4a2929d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_long_tx/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_long_tx/00004.png b/tests/snapshots/nanox/test_sign_tx_long_tx/00004.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_long_tx/00004.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/00001.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00001.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00002.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00002.png new file mode 100644 index 0000000..5d5329a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00003.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00003.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00004.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00004.png new file mode 100644 index 0000000..2b9bccd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00005.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00005.png new file mode 100644 index 0000000..eeaad30 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00006.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00006.png new file mode 100644 index 0000000..b807f41 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_mint_tokens/00007.png b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00007.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_mint_tokens/00007.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/00001.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00002.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00002.png new file mode 100644 index 0000000..b71b25b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00003.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00003.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00004.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00004.png new file mode 100644 index 0000000..5d5329a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00005.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00005.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00006.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00006.png new file mode 100644 index 0000000..58eac55 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00007.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00007.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00007.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00008.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00008.png new file mode 100644 index 0000000..732826a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00008.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_conclude/00009.png b/tests/snapshots/nanox/test_sign_tx_order_conclude/00009.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_conclude/00009.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/00001.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00002.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00002.png new file mode 100644 index 0000000..62b7685 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00003.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00003.png new file mode 100644 index 0000000..ec1e8cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00004.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00004.png new file mode 100644 index 0000000..5d5329a Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00005.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00005.png new file mode 100644 index 0000000..f12db18 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00006.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00006.png new file mode 100644 index 0000000..6cf46ea Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00007.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00007.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00007.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00008.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00008.png new file mode 100644 index 0000000..b0aa4e8 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00008.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_order_fill/00009.png b/tests/snapshots/nanox/test_sign_tx_order_fill/00009.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_order_fill/00009.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00000.png b/tests/snapshots/nanox/test_sign_tx_refused/00000.png new file mode 100644 index 0000000..1597f7e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_refused/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00001.png b/tests/snapshots/nanox/test_sign_tx_refused/00001.png new file mode 100644 index 0000000..8a3938b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_refused/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00002.png b/tests/snapshots/nanox/test_sign_tx_refused/00002.png new file mode 100644 index 0000000..8b3c9da Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_refused/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00003.png b/tests/snapshots/nanox/test_sign_tx_refused/00003.png new file mode 100644 index 0000000..4a2929d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_refused/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00004.png b/tests/snapshots/nanox/test_sign_tx_refused/00004.png new file mode 100644 index 0000000..09a1af1 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_refused/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_refused/00005.png b/tests/snapshots/nanox/test_sign_tx_refused/00005.png new file mode 100644 index 0000000..26c6197 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_refused/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00000.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00000.png new file mode 100644 index 0000000..1597f7e Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_short_tx/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00001.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00001.png new file mode 100644 index 0000000..ce243c5 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_short_tx/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00002.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00002.png new file mode 100644 index 0000000..8b3c9da Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_short_tx/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00003.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00003.png new file mode 100644 index 0000000..4a2929d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_short_tx/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_short_tx/00004.png b/tests/snapshots/nanox/test_sign_tx_short_tx/00004.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_short_tx/00004.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/00001.png b/tests/snapshots/nanox/test_sign_tx_transfer/00001.png new file mode 100644 index 0000000..192c2bd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00002.png b/tests/snapshots/nanox/test_sign_tx_transfer/00002.png new file mode 100644 index 0000000..75337cb Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00003.png b/tests/snapshots/nanox/test_sign_tx_transfer/00003.png new file mode 100644 index 0000000..0378fc6 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00004.png b/tests/snapshots/nanox/test_sign_tx_transfer/00004.png new file mode 100644 index 0000000..639af51 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00005.png b/tests/snapshots/nanox/test_sign_tx_transfer/00005.png new file mode 100644 index 0000000..6e3cb60 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_transfer/00006.png b/tests/snapshots/nanox/test_sign_tx_transfer/00006.png new file mode 100644 index 0000000..3a16119 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_transfer/00006.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/00001.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00002.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00003.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00003.png new file mode 100644 index 0000000..2737bf9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00004.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00004.png new file mode 100644 index 0000000..fb41633 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00005.png b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00005.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unfreeze_tokens/00005.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/00001.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00001.png new file mode 100644 index 0000000..f8e1b29 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00002.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00002.png new file mode 100644 index 0000000..31b2df9 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00003.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00003.png new file mode 100644 index 0000000..8973d80 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00004.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00004.png new file mode 100644 index 0000000..8ed052d Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00005.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00005.png new file mode 100644 index 0000000..fce7d0b Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00006.png b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00006.png new file mode 100644 index 0000000..b2e3594 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_unmint_tokens/00006.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..d755792 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..381d216 Binary files /dev/null and b/tests/snapshots/stax/test_app_mainmenu/00001.png differ diff --git a/tests/snapshots/stax/test_app_mainmenu/00002.png b/tests/snapshots/stax/test_app_mainmenu/00002.png new file mode 100644 index 0000000..b0c222a Binary files /dev/null and b/tests/snapshots/stax/test_app_mainmenu/00002.png differ diff --git a/tests/snapshots/stax/test_app_mainmenu/00003.png b/tests/snapshots/stax/test_app_mainmenu/00003.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_app_mainmenu/00003.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..d755792 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_get_public_key_confirm_refused/00003.png b/tests/snapshots/stax/test_get_public_key_confirm_refused/00003.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_get_public_key_confirm_refused/00003.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..5d5c468 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..d755792 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..5d5c468 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..d755792 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..5d5c468 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_message_refused/00005.png b/tests/snapshots/stax/test_sign_message_refused/00005.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_message_refused/00005.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..15da40f 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/00001.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00001.png new file mode 100644 index 0000000..013b703 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00002.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00002.png new file mode 100644 index 0000000..269b12a Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00003.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00003.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_authority/00004.png b/tests/snapshots/stax/test_sign_tx_change_token_authority/00004.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_authority/00004.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..15da40f 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/00001.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00001.png new file mode 100644 index 0000000..013b703 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00002.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00002.png new file mode 100644 index 0000000..0d66aad Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00003.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00003.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00004.png b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00004.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_change_token_metadata_uri/00004.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00001.png new file mode 100644 index 0000000..d218625 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00002.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00002.png new file mode 100644 index 0000000..c009444 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00003.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00003.png new file mode 100644 index 0000000..3343c26 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00004.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00004.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_delegation/00005.png b/tests/snapshots/stax/test_sign_tx_create_delegation/00005.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_delegation/00005.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00001.png new file mode 100644 index 0000000..608c3d6 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00002.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00002.png new file mode 100644 index 0000000..b1c1cd8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00003.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00003.png new file mode 100644 index 0000000..b91a1ee Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00004.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00004.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_create_stake_pool/00005.png b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00005.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_create_stake_pool/00005.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegation_staking/00000.png b/tests/snapshots/stax/test_sign_tx_delegation_staking/00000.png new file mode 100644 index 0000000..15da40f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegation_staking/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegation_staking/00001.png b/tests/snapshots/stax/test_sign_tx_delegation_staking/00001.png new file mode 100644 index 0000000..35ed6fc Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegation_staking/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegation_staking/00002.png b/tests/snapshots/stax/test_sign_tx_delegation_staking/00002.png new file mode 100644 index 0000000..c9c4a7d Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegation_staking/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegation_staking/00003.png b/tests/snapshots/stax/test_sign_tx_delegation_staking/00003.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegation_staking/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_delegation_staking/00004.png b/tests/snapshots/stax/test_sign_tx_delegation_staking/00004.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_delegation_staking/00004.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..15da40f 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/00001.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00001.png new file mode 100644 index 0000000..013b703 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00002.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00002.png new file mode 100644 index 0000000..d676523 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00003.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00003.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_freeze_tokens/00004.png b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00004.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_freeze_tokens/00004.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_htlc/00001.png new file mode 100644 index 0000000..6620584 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00002.png b/tests/snapshots/stax/test_sign_tx_htlc/00002.png new file mode 100644 index 0000000..bc6517b Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00003.png b/tests/snapshots/stax/test_sign_tx_htlc/00003.png new file mode 100644 index 0000000..4978a94 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00004.png b/tests/snapshots/stax/test_sign_tx_htlc/00004.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_htlc/00005.png b/tests/snapshots/stax/test_sign_tx_htlc/00005.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_htlc/00005.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00001.png new file mode 100644 index 0000000..3db0e08 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00002.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00002.png new file mode 100644 index 0000000..c009444 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00003.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00003.png new file mode 100644 index 0000000..56b7625 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00004.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00004.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00005.png b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00005.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_fungible_token/00005.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00001.png new file mode 100644 index 0000000..f230c4a Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00002.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00002.png new file mode 100644 index 0000000..51f50f2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00003.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00003.png new file mode 100644 index 0000000..f6bc246 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00004.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00004.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_issue_nft/00005.png b/tests/snapshots/stax/test_sign_tx_issue_nft/00005.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_issue_nft/00005.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00001.png new file mode 100644 index 0000000..fd9ba80 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00002.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00002.png new file mode 100644 index 0000000..b1c1cd8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00003.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00003.png new file mode 100644 index 0000000..f541d00 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00004.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00004.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00005.png b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00005.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_lock_then_transfer/00005.png differ diff --git a/tests/snapshots/stax/test_sign_tx_long_tx/00000.png b/tests/snapshots/stax/test_sign_tx_long_tx/00000.png new file mode 100644 index 0000000..46a27b1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_long_tx/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_long_tx/00001.png b/tests/snapshots/stax/test_sign_tx_long_tx/00001.png new file mode 100644 index 0000000..71804d2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_long_tx/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_long_tx/00002.png b/tests/snapshots/stax/test_sign_tx_long_tx/00002.png new file mode 100644 index 0000000..19e831c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_long_tx/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_long_tx/00003.png b/tests/snapshots/stax/test_sign_tx_long_tx/00003.png new file mode 100644 index 0000000..f5e027f Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_long_tx/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_long_tx/00004.png b/tests/snapshots/stax/test_sign_tx_long_tx/00004.png new file mode 100644 index 0000000..392165d Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_long_tx/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_long_tx/00005.png b/tests/snapshots/stax/test_sign_tx_long_tx/00005.png new file mode 100644 index 0000000..52f987c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_long_tx/00005.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00001.png new file mode 100644 index 0000000..71d44cc Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00002.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00002.png new file mode 100644 index 0000000..835929b Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00003.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00003.png new file mode 100644 index 0000000..12f0520 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00004.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00004.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_mint_tokens/00005.png b/tests/snapshots/stax/test_sign_tx_mint_tokens/00005.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_mint_tokens/00005.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..3c0339b 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/00001.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00001.png new file mode 100644 index 0000000..545c8fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00002.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00002.png new file mode 100644 index 0000000..538cefb Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00003.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00003.png new file mode 100644 index 0000000..034fc05 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00004.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00004.png new file mode 100644 index 0000000..596f1b1 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00005.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00005.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00005.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_conclude/00006.png b/tests/snapshots/stax/test_sign_tx_order_conclude/00006.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_conclude/00006.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..3c0339b 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/00001.png b/tests/snapshots/stax/test_sign_tx_order_fill/00001.png new file mode 100644 index 0000000..6789ed4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00002.png b/tests/snapshots/stax/test_sign_tx_order_fill/00002.png new file mode 100644 index 0000000..538cefb Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00003.png b/tests/snapshots/stax/test_sign_tx_order_fill/00003.png new file mode 100644 index 0000000..034fc05 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00004.png b/tests/snapshots/stax/test_sign_tx_order_fill/00004.png new file mode 100644 index 0000000..1b588b9 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00005.png b/tests/snapshots/stax/test_sign_tx_order_fill/00005.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00005.png differ diff --git a/tests/snapshots/stax/test_sign_tx_order_fill/00006.png b/tests/snapshots/stax/test_sign_tx_order_fill/00006.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_order_fill/00006.png differ diff --git a/tests/snapshots/stax/test_sign_tx_refused/00000.png b/tests/snapshots/stax/test_sign_tx_refused/00000.png new file mode 100644 index 0000000..7601f58 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_refused/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_refused/00001.png b/tests/snapshots/stax/test_sign_tx_refused/00001.png new file mode 100644 index 0000000..f2de498 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_refused/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_refused/00002.png b/tests/snapshots/stax/test_sign_tx_refused/00002.png new file mode 100644 index 0000000..bb50c81 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_refused/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_refused/00003.png b/tests/snapshots/stax/test_sign_tx_refused/00003.png new file mode 100644 index 0000000..abc9677 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_refused/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_refused/00004.png b/tests/snapshots/stax/test_sign_tx_refused/00004.png new file mode 100644 index 0000000..2b66970 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_refused/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_refused/00005.png b/tests/snapshots/stax/test_sign_tx_refused/00005.png new file mode 100644 index 0000000..52f987c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_refused/00005.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx/00000.png b/tests/snapshots/stax/test_sign_tx_short_tx/00000.png new file mode 100644 index 0000000..7601f58 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx/00001.png b/tests/snapshots/stax/test_sign_tx_short_tx/00001.png new file mode 100644 index 0000000..4168d43 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx/00002.png b/tests/snapshots/stax/test_sign_tx_short_tx/00002.png new file mode 100644 index 0000000..bb50c81 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx/00003.png b/tests/snapshots/stax/test_sign_tx_short_tx/00003.png new file mode 100644 index 0000000..392165d Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx/00004.png b/tests/snapshots/stax/test_sign_tx_short_tx/00004.png new file mode 100644 index 0000000..52f987c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00000.png b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00000.png new file mode 100644 index 0000000..7601f58 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00001.png b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00001.png new file mode 100644 index 0000000..d658c95 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00002.png b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00002.png new file mode 100644 index 0000000..bb50c81 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00003.png b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00003.png new file mode 100644 index 0000000..392165d Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00004.png b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00004.png new file mode 100644 index 0000000..52f987c Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_short_tx_no_memo/00004.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..15da40f 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/00001.png b/tests/snapshots/stax/test_sign_tx_transfer/00001.png new file mode 100644 index 0000000..edec633 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00002.png b/tests/snapshots/stax/test_sign_tx_transfer/00002.png new file mode 100644 index 0000000..ea259ca Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00003.png b/tests/snapshots/stax/test_sign_tx_transfer/00003.png new file mode 100644 index 0000000..ceda6a8 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_transfer/00004.png b/tests/snapshots/stax/test_sign_tx_transfer/00004.png new file mode 100644 index 0000000..d755792 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_transfer/00004.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..15da40f 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/00001.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00001.png new file mode 100644 index 0000000..013b703 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00002.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00002.png new file mode 100644 index 0000000..3dc1f34 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00003.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00003.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00004.png b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00004.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unfreeze_tokens/00004.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..ea44baa 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/00001.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00001.png new file mode 100644 index 0000000..b33c474 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00002.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00002.png new file mode 100644 index 0000000..d71d1f4 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00003.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00003.png new file mode 100644 index 0000000..cb9e0d0 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00004.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00004.png new file mode 100644 index 0000000..a5379fa Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00004.png differ diff --git a/tests/snapshots/stax/test_sign_tx_unmint_tokens/00005.png b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00005.png new file mode 100644 index 0000000..9e274be Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_unmint_tokens/00005.png differ diff --git a/tests/test_app_mainmenu.py b/tests/test_app_mainmenu.py new file mode 100644 index 0000000..8103dcd --- /dev/null +++ b/tests/test_app_mainmenu.py @@ -0,0 +1,22 @@ +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, + NavInsID.RIGHT_CLICK + ] + else: + instructions = [ + NavInsID.USE_CASE_HOME_SETTINGS, + NavInsID.USE_CASE_SUB_SETTINGS_NEXT, + NavInsID.USE_CASE_SUB_SETTINGS_EXIT + ] + 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..7b3dbcb --- /dev/null +++ b/tests/test_error_cmd.py @@ -0,0 +1,215 @@ +import pytest +import scalecodec # type: ignore +from ragger.error import ExceptionRAPDU + +from application_client import MAINNET +from application_client.mintlayer_command_sender import (CLA, P1, P2, Errors, + InsType) + +tx_metadata_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("TxMetadataReq") +sign_tx_req_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("SignTxReq") + +# 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=P1.P1_START, p2=123 + ) + assert e.value.status == Errors.SW_WRONG_P1P2 + + backend.exchange( + cla=CLA, ins=InsType.GET_PUBLIC_KEY, p1=P1.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=P1.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=P1.P1_TX_INPUT, # 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 + metadata = tx_metadata_obj.encode( + { + "coin": invalid_coin, + "version": 1, + "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=P1.P1_START, + p2=P2.P2_LAST, + data=bytes(metadata), + ) + + assert e.value.status == Errors.SW_DESERIALIZE_FAIL + + +def test_sign_tx_invalid_P2_for_input(backend, scenario_navigator, device, navigator): + """ + After metadata try to pass an output instead of the input + expect an error for wrong P1/P2 + """ + num_inputs = 2 + num_outputs = 2 + metadata = tx_metadata_obj.encode( + { + "coin": MAINNET, + "version": 1, + "num_inputs": num_inputs, + "num_outputs": num_outputs, + } + ).data + + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_START, + p2=P2.P2_LAST, + data=bytes(metadata), + ) + + assert res.status == 0x9000 + + with pytest.raises(ExceptionRAPDU) as e: + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_OUTPUT, + p2=P2.P2_LAST, + data=sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + } + ).data, + ) + assert e.value.status == Errors.SW_WRONG_P1P2 + + +def test_sign_tx_invalid_input(backend, scenario_navigator, device, navigator): + num_inputs = 2 + num_outputs = 2 + metadata = tx_metadata_obj.encode( + { + "coin": MAINNET, + "version": 1, + "num_inputs": num_inputs, + "num_outputs": num_outputs, + } + ).data + + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_START, + p2=P2.P2_LAST, + data=bytes(metadata), + ) + + print("res, ", res.status) + assert res.status == 0x9000 + + with pytest.raises(ExceptionRAPDU) as e: + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_INPUT, + 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 + metadata = tx_metadata_obj.encode( + { + "coin": MAINNET, + "version": 1, + "num_inputs": num_inputs, + "num_outputs": num_outputs, + } + ).data + + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_START, + p2=P2.P2_LAST, + data=bytes(metadata), + ) + + assert res.status == 0x9000 + + with pytest.raises(ExceptionRAPDU) as e: + for _ in range(100): + res = backend.exchange( + cla=CLA, + ins=InsType.SIGN_TX, + p1=P1.P1_TX_INPUT, + p2=P2.P2_MORE, + data=b"big_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..8ff22f9 --- /dev/null +++ b/tests/test_name_version.py @@ -0,0 +1,16 @@ +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 == "app" + assert version == "1.33.7" diff --git a/tests/test_pubkey_cmd.py b/tests/test_pubkey_cmd.py new file mode 100644 index 0000000..eccfc93 --- /dev/null +++ b/tests/test_pubkey_cmd.py @@ -0,0 +1,81 @@ +import pytest +from ragger.bip import CurveChoice, calculate_public_key_and_chaincode +from ragger.error import ExceptionRAPDU +from ragger.navigator import NavIns, NavInsID + +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 +from utils import ROOT_SCREENSHOT_PATH + +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_cmd.py b/tests/test_sign_cmd.py new file mode 100644 index 0000000..aa36c2f --- /dev/null +++ b/tests/test_sign_cmd.py @@ -0,0 +1,1811 @@ +import pytest +import scalecodec +from ragger.error import ExceptionRAPDU +from ragger.navigator import NavIns, NavInsID + +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, unpack_sign_tx_response) +from application_client.mintlayer_transaction import Transaction +from utils import ROOT_SCREENSHOT_PATH, check_signature_validity + +sign_tx_req_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("SignTxReq") + +TX_RESPONSE_SIZE = 67 + + +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 = sign_tx_req_obj.encode( + { + "Input": + { + "addresses": [ + {"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None} + ], + "inp": { + "Utxo": [ + { + "id": {"Transaction": "0x{}".format(bytes([0] * 32).hex())}, + "index": 1, + }, + additional_info, + ] + } + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info + } + ).data + + output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + ], + } + } + } + ).data + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + # Enable display of transaction memo (NBGL devices only) + if not device.is_nano: + navigator.navigate( + [ + NavInsID.USE_CASE_HOME_SETTINGS, + NavIns(NavInsID.TOUCH, (200, 113)), + NavInsID.USE_CASE_SUB_SETTINGS_EXIT, + ], + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + ) + + # Send the sign device instruction. + # As it requires on-screen validation, the function is asynchronous. + # It will yield the result when the navigation is done + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation appropriate for this device + scenario_navigator.review_approve(custom_screen_text=r"Sign\stransfer") + + # The device as yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + + assert len(response) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": + { + "addresses": [ + {"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None} + ], + "inp": { + "Account": { + "nonce": 1, + "account": {"Delegation": [[0] * 32, 11]}, + } + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": {"None": None} + } + ).data + + output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "LockThenTransfer": [ + {"Coin": 10}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + {"UntilHeight": 10}, + ], + } + } + } + ).data + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + # Enable display of transaction memo (NBGL devices only) + if not device.is_nano: + navigator.navigate( + [ + NavInsID.USE_CASE_HOME_SETTINGS, + NavIns(NavInsID.TOUCH, (200, 113)), + NavInsID.USE_CASE_SUB_SETTINGS_EXIT, + ], + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + ) + + # Send the sign device instruction. + # As it requires on-screen validation, the function is asynchronous. + # It will yield the result when the navigation is done + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation appropriate for this device + scenario_navigator.review_approve(custom_screen_text=r"Sign\swithdrawal") + + # The device as yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + + assert len(response) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": + { + "addresses": [ + {"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None} + ], + "inp": { + "Utxo": [ + { + "id": {"Transaction": "0x{}".format(bytes([0] * 32).hex())}, + "index": 1, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info + } + ).data + + output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "CreateDelegationId": [ + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([0] * 33)}} + } + }, + [0] * 32, + ], + } + } + } + ).data + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + # Enable display of transaction memo (NBGL devices only) + if not device.is_nano: + navigator.navigate( + [ + NavInsID.USE_CASE_HOME_SETTINGS, + NavIns(NavInsID.TOUCH, (200, 113)), + NavInsID.USE_CASE_SUB_SETTINGS_EXIT, + ], + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + ) + + print("create delegation test") + # Send the sign device instruction. + # As it requires on-screen validation, the function is asynchronous. + # It will yield the result when the navigation is done + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation appropriate for this device + scenario_navigator.review_approve(custom_screen_text=r"Sign\screate") + + # The device as yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + + assert len(response) == TX_RESPONSE_SIZE + + +def test_sign_tx_delegation_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 = sign_tx_req_obj.encode( + { + "Input": + { + "addresses": [ + {"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None} + ], + "inp": { + "Utxo": [ + { + "id": {"Transaction": "0x{}".format(bytes([0] * 32).hex())}, + "index": 1, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info + } + ).data + + output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "DelegateStaking": [5, [0] * 32], + } + } + } + ).data + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + # Enable display of transaction memo (NBGL devices only) + if not device.is_nano: + navigator.navigate( + [ + NavInsID.USE_CASE_HOME_SETTINGS, + NavIns(NavInsID.TOUCH, (200, 113)), + NavInsID.USE_CASE_SUB_SETTINGS_EXIT, + ], + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + ) + + # Send the sign device instruction. + # As it requires on-screen validation, the function is asynchronous. + # It will yield the result when the navigation is done + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation appropriate for this device + scenario_navigator.review_approve(custom_screen_text=r"Sign\sstake") + + # The device as yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + + assert len(response) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": + { + "addresses": [ + {"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None} + ], + "inp": { + "Utxo": [ + { + "id": {"Transaction": "0x{}".format(bytes([0] * 32).hex())}, + "index": 1, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info + } + ).data + + output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "CreateStakePool": [ + [0] * 32, + { + "value": 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, + }, + ], + } + } + } + ).data + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + # Enable display of transaction memo (NBGL devices only) + if not device.is_nano: + navigator.navigate( + [ + NavInsID.USE_CASE_HOME_SETTINGS, + NavIns(NavInsID.TOUCH, (200, 113)), + NavInsID.USE_CASE_SUB_SETTINGS_EXIT, + ], + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False, + ) + + # Send the sign device instruction. + # As it requires on-screen validation, the function is asynchronous. + # It will yield the result when the navigation is done + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation appropriate for this device + scenario_navigator.review_approve(custom_screen_text=r"Sign\screate\sstake") + + # The device as yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + + assert len(response) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": + { + "addresses": [ + {"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None} + ], + "inp": { + "Utxo": [ + { + "id": {"Transaction": "0x{}".format(bytes([0] * 32).hex())}, + "index": 1, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info + } + ).data + + output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "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}, + } + }, + } + } + } + ).data + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + # Send the sign device instruction + with client.sign_tx(transaction=transaction): + # Validate the on-screen request + scenario_navigator.review_approve(custom_screen_text=r"Sign\screate\sToken") + + # The device has yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + assert len(response) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": + { + "addresses": [ + {"path": [44 + h, 19788 + h, 0 + h, 0, 0], "multisig_idx": None} + ], + "inp": { + "Utxo": [ + { + "id": {"Transaction": "0x{}".format(bytes([0] * 32).hex())}, + "index": 1, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info + } + ).data + + # This is the new output for issuing an NFT. + # The structure is (TokenId, NftIssuance, Destination) + output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "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)}} + } + }, + ], + } + } + } + ).data + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + coin=MAINNET, inputs=[inp], input_commitments=[inp_commitment], outputs=[output] + ) + + # Send the sign device instruction + with client.sign_tx(transaction=transaction): + # Validate the on-screen request + scenario_navigator.review_approve(custom_screen_text=r"Sign\screate\sNFT") + + # The device has yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + assert len(response) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info + } + ).data + + # This is the AccountCommand to mint 1000 units of a new token + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "AccountCommand": [ + 1, # AccountNonce + { + "MintTokens": [ + f"0x{bytes([0]*32).hex()}", # TokenId + 1000, # Amount to mint + ] + }, + ] + }, + } + } + ).data + + acc_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": {"None": None} + } + ).data + + mint_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"TokenV1": [f"0x{bytes([0]*32).hex()}", 1000]}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[mint_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\smint\sTokens") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 2 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +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": [f"0x{bytes([0]*32).hex()}", 1000]}, + { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)} + } + } + }, + ], + } + } + utxo_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + utxo_input2 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 2, + }, + additional_info2, + ] + }, + } + } + ).data + + inp_commitment2 = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info2, + } + ).data + + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "AccountCommand": [ + 1, # AccountNonce + { + "UnmintTokens": f"0x{bytes([0]*32).hex()}", # TokenId + }, + ] + }, + } + } + ).data + + acc_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": {"None": None} + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input, utxo_input2], + input_commitments=[inp_commitment, acc_inp_commitment, inp_commitment2], + outputs=[change_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\sunmint\sTokens") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 3 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + # This is the AccountCommand to mint 1000 units of a new token + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "AccountCommand": [ + 1, # AccountNonce + {"FreezeToken": [f"0x{bytes([0]*32).hex()}", {"No": None}]}, # TokenId + ] + }, + } + } + ).data + + acc_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": {"None": None} + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\sfreeze\sTokens") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 2 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + }).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + # This is the AccountCommand to mint 1000 units of a new token + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "AccountCommand": [ + 1, # AccountNonce + { + "UnfreezeToken": f"0x{bytes([0]*32).hex()}", # TokenId + }, + ] + }, + } + } + ).data + + acc_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": {"None": None} + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\sunfreeze") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 2 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + }).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + # This is the AccountCommand to mint 1000 units of a new token + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "AccountCommand": [ + 1, # AccountNonce + { + "ChangeTokenAuthority": [ + f"0x{bytes([0]*32).hex()}", # TokenId + { + "PublicKey": { + "key": { + "Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)} + } + } + }, + ] + }, + ] + }, + } + } + ).data + + acc_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": {"None": None} + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\schange\sToken") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 2 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + # This is the AccountCommand to mint 1000 units of a new token + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "AccountCommand": [ + 1, # AccountNonce + { + "ChangeTokenMetadataUri": [ + f"0x{bytes([0]*32).hex()}", # TokenId + "uri.com".encode(), + ] + }, + ] + }, + } + } + ).data + + acc_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": {"None": None} + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 99}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, acc_inp_commitment], + outputs=[change_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\schange\sToken") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 2 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +def test_sign_tx_order_fill(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 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 additionl 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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + fill_amount = 10 + fill_ask = 100 + fill_give = 1000 + + additional_order_info = { + "initially_asked": {"Coin": fill_ask}, + "initially_given": {"TokenV1": [f"0x{bytes([0]*32).hex()}", fill_give]}, + "ask_balance": 0, + "give_balance": 0, + } + # This is the OrderAccountCommand to fill 10 units + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "OrderAccountCommand": [ + { + "FillOrder": [ + f"0x{bytes([0]*32).hex()}", # OrderId + fill_amount, + ] + }, + additional_order_info, + ] + }, + } + } + ).data + + fill_order_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": { + "FillOrderAccountCommand": [ + additional_order_info["initially_asked"], + additional_order_info["initially_given"], + ] + }, + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 100 - 1 - fill_amount}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + fill_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + { + "TokenV1": [ + f"0x{bytes([0]*32).hex()}", + fill_amount * fill_give // fill_ask, + ] + }, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, fill_order_inp_commitment], + outputs=[change_output, fill_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\sfill\sOrder") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 2 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + initial_ask = 100 + initial_give = 1000 + ask_balance = 10 + give_balance = 900 + + additional_order_info = { + "initially_asked": {"Coin": initial_ask}, + "initially_given": {"TokenV1": [f"0x{bytes([0]*32).hex()}", initial_give]}, + "ask_balance": ask_balance, + "give_balance": give_balance, + } + + # This is the OrderAccountCommand to fill 10 units + account_input = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "OrderAccountCommand": [ + { + "ConcludeOrder": f"0x{bytes([0]*32).hex()}", # OrderId + }, + additional_order_info, + ] + }, + } + } + ).data + + conclude_order_inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": { + "ConcludeOrderAccountCommand": [ + additional_order_info["initially_asked"], + additional_order_info["initially_given"], + additional_order_info["ask_balance"], + additional_order_info["give_balance"] + ] + }, + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 100 - 1 + ask_balance}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + conclude_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"TokenV1": [f"0x{bytes([0]*32).hex()}", give_balance]}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input, account_input], + input_commitments=[inp_commitment, conclude_order_inp_commitment], + outputs=[change_output, conclude_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\sconclude\sOrder") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned two signatures, one for each input that + # required signing (the Utxo and the AccountCommand). + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 2 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE + + +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 = sign_tx_req_obj.encode( + { + "Input": { + "addresses": [{"path": bip44_path, "multisig_idx": None}], + "inp": { + "Utxo": [ + { + "id": {"Transaction": f"0x{bytes([1]*32).hex()}"}, + "index": 0, + }, + additional_info, + ] + }, + } + } + ).data + + inp_commitment = sign_tx_req_obj.encode( + { + "InputCommitment": additional_info, + } + ).data + + change_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "Transfer": [ + {"Coin": 89}, + { + "PublicKey": { + "key": {"Secp256k1Schnorr": {"pubkey_data": bytes([2] * 33)}} + } + }, + ], + } + } + } + ).data + + htlc_output = sign_tx_req_obj.encode( + { + "Output": { + "out": { + "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)} + } + } + }, + }, + ], + } + } + } + ).data + + transaction = Transaction( + coin=MAINNET, + inputs=[utxo_input], + input_commitments=[inp_commitment], + outputs=[htlc_output, change_output], + ) + + # Send the sign transaction instruction. + # It will yield the result when the user validates on-screen. + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation + scenario_navigator.review_approve(custom_screen_text=r"Sign\screate\sHTLC") + # The device has yielded the result, parse it and ensure the signatures are correct + responses = client.get_all_signatures(transaction) + + # The device should have returned one signature + # Each signature is 64 bytes + 3 sighash byte = 67 bytes. + assert len(responses) == 1 + for resp in responses: + assert len(resp) == TX_RESPONSE_SIZE diff --git a/tests/test_sign_message.py b/tests/test_sign_message.py new file mode 100644 index 0000000..c3d2995 --- /dev/null +++ b/tests/test_sign_message.py @@ -0,0 +1,55 @@ +import pytest +import scalecodec # type: ignore +from ragger.bip import CurveChoice, calculate_public_key_and_chaincode +from ragger.error import ExceptionRAPDU +from ragger.navigator import NavIns, NavInsID + +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 +from utils import ROOT_SCREENSHOT_PATH + +MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + + +# 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" + client = MintlayerCommandSender(backend) + with client.sign_message(coin=MAINNET, addr_type=0, path=path, message=message): + scenario_navigator.review_approve() + + response = scalecodec.base.ScaleBytes(client.get_async_response().data) + msg_signature_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("MsgSignature", data=response) + sig = msg_signature_obj.decode() + +def test_sign_message_pkh(backend, scenario_navigator): + path = "m/44'/19788'/0'/0/0" + message = b"Hello" + client = MintlayerCommandSender(backend) + with client.sign_message(coin=MAINNET, addr_type=1, path=path, message=message): + scenario_navigator.review_approve() + + response = scalecodec.base.ScaleBytes(client.get_async_response().data) + msg_signature_obj = scalecodec.base.RuntimeConfiguration().create_scale_object("MsgSignature", data=response) + sig = msg_signature_obj.decode() + + +# 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" + + with pytest.raises(ExceptionRAPDU) as e: + with client.sign_message(coin=MAINNET, 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 diff --git a/tests/usage.md b/tests/usage.md new file mode 100644 index 0000000..201d146 --- /dev/null +++ b/tests/usage.md @@ -0,0 +1,74 @@ +# How to use the Ragger test framework + +This framework allows testing the application on the Speculos emulator or on a real device using LedgerComm or LedgerWallet + + +## Quickly get started with Ragger and Speculos + +### Install ragger and dependencies + +``` +pip install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt +sudo apt-get update && sudo apt-get install qemu-user-static +``` + +### Compile the application + +The application to test must be compiled for all required devices. +You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: +``` +docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest +cd # replace with the name of your app, (eg boilerplate) +docker run --user "$(id -u)":"$(id -g)" --rm -ti -v "$(realpath .):/app" --privileged -v "/dev/bus/usb:/dev/bus/usb" ledger-app-builder-lite:latest +make clean && make BOLOS_SDK=$_SDK # replace with one of [NANOS, NANOX, NANOSP, STAX, FLEX] +exit +``` + +### Run a simple test using the Speculos emulator + +You can use the following command to get your first experience with Ragger and Speculos +``` +pytest -v --tb=short --device nanox --display +``` +Or you can refer to the section `Available pytest options` to configure the options you want to use + + +### Run a simple test using a real device + +The application to test must be loaded and started on a Ledger device plugged in USB. +You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: +``` +docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest +cd app-/ # replace with the name of your app, (eg boilerplate) +docker run --user "$(id -u)":"$(id -g)" --rm -ti -v "$(realpath .):/app" --privileged -v "/dev/bus/usb:/dev/bus/usb" ledger-app-builder-lite:latest +make clean && make BOLOS_SDK=$_SDK load # replace with one of [NANOS, NANOX, NANOSP, STAX, FLEX] +exit +``` + +You can use the following command to get your first experience with Ragger and Ledgerwallet on a NANOX. +Make sure that the device is plugged, unlocked, and that the tested application is open. +``` +pytest -v --tb=short --device nanox --backend ledgerwallet +``` +Or you can refer to the section `Available pytest options` to configure the options you want to use + + +## Available pytest options + +Standard useful pytest options +``` + -v formats the test summary in a readable way + -s enable logs for successful tests, on Speculos it will enable app logs if compiled with DEBUG=1 + -k only run the tests that contain in their names + --tb=short in case of errors, formats the test traceback in a readable way +``` + +Custom pytest options +``` + --device run the test on the specified device [nanos,nanox,nanosp,stax,flex,all]. This parameter is mandatory + --backend run the tests against the backend [speculos, ledgercomm, ledgerwallet]. Speculos is the default + --display on Speculos, enables the display of the app screen using QT + --golden_run on Speculos, screen comparison functions will save the current screen instead of comparing + --log_apdu_file log all apdu exchanges to the file in parameter. The previous file content is erased +``` + diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..cb52233 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,23 @@ +from pathlib import Path +from hashlib import sha256 +from sha3 import keccak_256 + +from ecdsa.curves import SECP256k1 +from ecdsa.keys import VerifyingKey +from ecdsa.util import sigdecode_der + + +ROOT_SCREENSHOT_PATH = Path(__file__).parent.resolve() + + +# Check if a signature of a given message is valid +def check_signature_validity(public_key: bytes, signature: bytes, message: bytes) -> bool: + pk: VerifyingKey = VerifyingKey.from_string( + public_key, + curve=SECP256k1, + hashfunc=sha256 + ) + return pk.verify(signature=signature, + data=message, + hashfunc=keccak_256, + sigdecode=sigdecode_der)