├── .clang-format ├── .github ├── codeql │ └── codeql-config.yml └── workflows │ ├── build.yml │ ├── codeql.yml │ ├── guidelines_enforcer.yml │ └── lint-workflow.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── Makefile ├── README.md ├── build.sh ├── docs ├── apdu_request.png ├── apdu_request.svg ├── data_buffer_states.png ├── data_buffer_states.svg ├── images │ ├── address.png │ ├── blindsigning_1.png │ ├── blindsigning_ne.png │ ├── claim_smr_1.png │ ├── claim_smr_2.png │ ├── internal_transfer_1.png │ └── internal_transfer_2.png └── specification.md ├── fuzz ├── CMakeLists.txt ├── README.md ├── fuzztest.c └── os.h ├── glyphs ├── icon_back.gif ├── icon_coggle.gif ├── icon_iota.gif ├── icon_shimmer.gif ├── icon_warning.gif ├── x_icon_back.gif ├── x_icon_check.gif ├── x_icon_cross.gif ├── x_icon_dash.gif ├── x_icon_info.gif ├── x_icon_left.gif ├── x_icon_load.gif ├── x_icon_right.gif └── x_iota_logo.gif ├── icons ├── nanos_app_iota.gif ├── nanos_app_shimmer.gif ├── nanox_app_iota.gif └── nanox_app_shimmer.gif ├── makefile_conf └── chain │ ├── iota.mk │ └── shimmer.mk ├── resources ├── IOTA-Ledger Manager icon template.ai └── IOTA-Ledger Nano S icon template.ai ├── src ├── api.c ├── api.h ├── debugprintf.c ├── debugprintf.h ├── iota │ ├── .gitignore │ ├── abstraction.c │ ├── abstraction.h │ ├── address.c │ ├── address.h │ ├── bech32.c │ ├── bech32.h │ ├── blindsigning_stardust.c │ ├── blindsigning_stardust.h │ ├── constants.h │ ├── ed25519.c │ ├── ed25519.h │ ├── essence_stardust.c │ ├── essence_stardust.h │ ├── internal_transfer.c │ ├── internal_transfer.h │ ├── signing.c │ └── signing.h ├── iota_io.c ├── iota_io.h ├── macros.h ├── main.c ├── nv_mem.c ├── nv_mem.h └── ui │ ├── nano │ ├── flow_generating_addresses.c │ ├── flow_generating_addresses.h │ ├── flow_main_menu.c │ ├── flow_main_menu.h │ ├── flow_signed_successfully.c │ ├── flow_signed_successfully.h │ ├── flow_signing.c │ ├── flow_signing.h │ ├── flow_user_confirm.c │ ├── flow_user_confirm.h │ ├── flow_user_confirm_blindsigning.c │ ├── flow_user_confirm_blindsigning.h │ ├── flow_user_confirm_new_address.c │ ├── flow_user_confirm_new_address.h │ ├── flow_user_confirm_transaction.c │ └── flow_user_confirm_transaction.h │ ├── ui.c │ ├── ui.h │ ├── ui_common.c │ └── ui_common.h ├── tests ├── Dockerfile ├── README.md ├── build_docker.sh ├── chain │ ├── iota │ │ ├── reference_nanos.bin │ │ ├── reference_nanos.hex │ │ ├── reference_nanos.json │ │ ├── reference_nanosplus.bin │ │ ├── reference_nanosplus.hex │ │ ├── reference_nanosplus.json │ │ ├── reference_nanox.bin │ │ ├── reference_nanox.hex │ │ └── reference_nanox.json │ └── shimmer │ │ ├── reference_nanos.bin │ │ ├── reference_nanos.hex │ │ ├── reference_nanos.json │ │ ├── reference_nanosplus.bin │ │ ├── reference_nanosplus.hex │ │ ├── reference_nanosplus.json │ │ ├── reference_nanox.bin │ │ ├── reference_nanox.hex │ │ └── reference_nanox.json ├── run_tests_docker.sh ├── test_headless.sh └── tests.c └── tools └── gdbinit /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | # BasedOnStyle 3 | 4 | TabWidth: 8 5 | UseTab: Never 6 | IndentWidth: 4 7 | ColumnLimit: 80 8 | MaxEmptyLinesToKeep: 2 9 | 10 | # Break after function definitions and else 11 | BreakBeforeBraces: Stroustrup 12 | 13 | AlignAfterOpenBracket: true 14 | AlignEscapedNewlinesLeft: false 15 | AlignOperands: true 16 | AlwaysBreakAfterReturnType: None 17 | BinPackArguments: true 18 | BinPackParameters: true 19 | DerivePointerAlignment: false 20 | PointerAlignment: Right 21 | SortIncludes: false 22 | SpaceInEmptyParentheses: false 23 | SpacesInCStyleCastParentheses: false 24 | SpacesInParentheses: false 25 | SpacesInSquareBrackets: false 26 | 27 | # Never allow single line statements 28 | AllowShortFunctionsOnASingleLine: false 29 | AllowShortBlocksOnASingleLine: false 30 | AllowShortCaseLabelsOnASingleLine: false 31 | AllowShortIfStatementsOnASingleLine: false 32 | AllowShortLoopsOnASingleLine: false 33 | 34 | -------------------------------------------------------------------------------- /.github/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL Config" 2 | 3 | queries: 4 | - uses: security-and-quality 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and run functional tests using ragger through reusable workflow 2 | 3 | # This workflow will build the app and then run functional tests using the Ragger framework upon Speculos emulation. 4 | # It calls a reusable workflow developed by Ledger's internal developer team to build the application and upload the 5 | # resulting binaries. 6 | # It then calls another reusable workflow to run the Ragger tests on the compiled application binary. 7 | # 8 | # While this workflow is optional, having functional testing on your application is mandatory and this workflow and 9 | # tooling environment is meant to be easy to use and adapt after forking your application 10 | 11 | on: 12 | workflow_dispatch: 13 | push: 14 | branches: 15 | - master 16 | - main 17 | - develop 18 | pull_request: 19 | 20 | jobs: 21 | build_application: 22 | name: Build application using the reusable workflow 23 | uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1 24 | with: 25 | run_for_devices: '["nanos", "nanox", "nanosp"]' 26 | upload_app_binaries_artifact: "compiled_app_binaries" 27 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "develop" 7 | pull_request: 8 | branches: 9 | - "develop" 10 | schedule: 11 | - cron: '0 0 * * *' 12 | 13 | jobs: 14 | CodeQL-Build: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | security-events: write 18 | actions: read 19 | packages: read 20 | container: 21 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest 22 | 23 | steps: 24 | - name: Install git 25 | run: apt-get update && apt-get install -y git 26 | 27 | - name: Checkout repository 28 | uses: actions/checkout@v2 29 | with: 30 | submodules: true 31 | 32 | - name: Initialize CodeQL 33 | uses: github/codeql-action/init@v2 34 | with: 35 | languages: cpp 36 | config-file: ./.github/codeql/codeql-config.yml 37 | 38 | - name: Build app 39 | run: make clean && BOLOS_SDK=/opt/nanos-secure-sdk make 40 | 41 | - name: Perform CodeQL Analysis 42 | uses: github/codeql-action/analyze@v2 43 | -------------------------------------------------------------------------------- /.github/workflows/guidelines_enforcer.yml: -------------------------------------------------------------------------------- 1 | name: Ensure compliance with Ledger guidelines 2 | 3 | # This workflow is mandatory in all applications 4 | # It calls a reusable workflow guidelines_enforcer developed by Ledger's internal developer team. 5 | # The successful completion of the reusable workflow is a mandatory step for an app to be available on the Ledger 6 | # application store. 7 | # 8 | # More information on the guidelines can be found in the repository: 9 | # LedgerHQ/ledger-app-workflows/ 10 | 11 | on: 12 | workflow_dispatch: 13 | push: 14 | branches: 15 | - master 16 | - main 17 | - develop 18 | pull_request: 19 | 20 | jobs: 21 | guidelines_enforcer: 22 | name: Call Ledger guidelines_enforcer 23 | uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 24 | with: 25 | run_for_devices: '["nanos", "nanox", "nanosp"]' 26 | -------------------------------------------------------------------------------- /.github/workflows/lint-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Code style check 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | - develop 12 | 13 | jobs: 14 | job_lint: 15 | name: Lint 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Clone 20 | uses: actions/checkout@v2 21 | 22 | - name: Lint 23 | uses: DoozyX/clang-format-lint-action@v0.11 24 | with: 25 | source: './src' 26 | extensions: 'h,c' 27 | clangFormatVersion: 11 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fuzz/build/ 2 | fuzz/corpus/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/.gitmodules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "GDB", 10 | "type": "gdb", 11 | "request": "attach", 12 | "target": "127.0.0.1:1234", 13 | "remote": true, 14 | "executable": "", 15 | "cwd": "${workspaceRoot}", 16 | "gdbpath": "gdb-multiarch", 17 | "stopAtConnect": true, 18 | "autorun": [ 19 | //"source \"${workspaceRoot}/tools/gdbinit\"", 20 | "set architecture arm", 21 | "handle SIGILL nostop pass noprint", 22 | //"add-symbol-file \"/home/thomas/git/ledger/speculos/tools/../build/src/launcher\" 0x000101c0", 23 | "add-symbol-file \"${workspaceRoot}/bin/app.elf\" 0x40000000", 24 | "b *0x40000000", 25 | ] 26 | }, 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "options": { 6 | "env": { 7 | //"CLANGPATH": "${HOME}/programme/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/bin/", 8 | //"GCCPATH": "${HOME}/programme/gcc-arm-none-eabi-5_4-2016q3/bin/", 9 | //"BOLOS_SDK": "${workspaceRoot}/dev/sdk/nanos-secure-sdk" 10 | } 11 | }, 12 | "tasks": [ 13 | { 14 | "label": "Build IOTA App for Nano S (Speculos)", 15 | "type": "shell", 16 | "command": "${workspaceRoot}/build.sh -m nanos", 17 | "problemMatcher": [ 18 | "$gcc" 19 | ] 20 | }, 21 | { 22 | "label": "Run IOTA App for Nano S (Speculos)", 23 | "type": "shell", 24 | "command": "${workspaceRoot}/build.sh -m nanos -s" 25 | }, 26 | { 27 | "label": "Debug IOTA App for Nano S (Speculos)", 28 | "type": "shell", 29 | "command": "${workspaceRoot}/build.sh -m nanos -s -g" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # **************************************************************************** 2 | # Ledger App 3 | # (c) 2017 Ledger 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # **************************************************************************** 17 | 18 | ifeq ($(BOLOS_SDK),) 19 | $(error Environment variable BOLOS_SDK is not set) 20 | endif 21 | 22 | include $(BOLOS_SDK)/Makefile.defines 23 | 24 | APPVERSION_M = 0 25 | APPVERSION_N = 8 26 | APPVERSION_P = 6 27 | APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" 28 | 29 | APP_LOAD_PARAMS = --path "44'/1'" --curve ed25519 --appFlags 0x240 $(COMMON_LOAD_PARAMS) 30 | 31 | 32 | ifeq ($(CHAIN),) 33 | CHAIN=iota 34 | endif 35 | 36 | # Check if chain is available 37 | ifeq ($(shell test -s ./makefile_conf/chain/$(CHAIN).mk && echo -n yes), yes) 38 | include ./makefile_conf/chain/$(CHAIN).mk 39 | else 40 | $(error Unsupported CHAIN - use $(SUPPORTED_CHAINS)) 41 | endif 42 | 43 | all: default 44 | 45 | DEFINES += $(DEFINES_LIB) 46 | DEFINES += APPNAME=\"$(APPNAME)\" 47 | DEFINES += APPVERSION=\"$(APPVERSION)\" 48 | DEFINES += APPVERSION_MAJOR=$(APPVERSION_M) APPVERSION_MINOR=$(APPVERSION_N) APPVERSION_PATCH=$(APPVERSION_P) 49 | DEFINES += OS_IO_SEPROXYHAL 50 | DEFINES += HAVE_BAGL HAVE_UX_FLOW HAVE_SPRINTF HAVE_SNPRINTF_FORMAT_U 51 | DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=6 IO_HID_EP_LENGTH=64 HAVE_USB_APDU 52 | DEFINES += USB_SEGMENT_SIZE=64 53 | DEFINES += BLE_SEGMENT_SIZE=32 54 | DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL="" 55 | DEFINES += UNUSED\(x\)=\(void\)x 56 | 57 | ifeq ($(TARGET_NAME),TARGET_NANOX) 58 | DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000 HAVE_BLE_APDU 59 | endif 60 | 61 | ifeq ($(TARGET_NAME),TARGET_NANOS) 62 | DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 63 | else 64 | # nanox, nanosplus 65 | DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 66 | DEFINES += HAVE_GLO096 67 | DEFINES += BAGL_WIDTH=128 BAGL_HEIGHT=64 68 | DEFINES += HAVE_BAGL_ELLIPSIS 69 | DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX 70 | DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX 71 | DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX 72 | endif 73 | 74 | # default disable DEBUG build 75 | DEBUG = 0 76 | 77 | # if speculos simulator is selected enable debuging features 78 | ifeq ($(SPECULOS), 1) 79 | DEFINES += SPECULOS 80 | DEBUG = 1 81 | endif 82 | 83 | ifneq ($(DEBUG),0) 84 | APP_LOAD_PARAMS += --path "44'/01'" 85 | DEFINES += HAVE_BOLOS_APP_STACK_CANARY 86 | DEFINES += APP_DEBUG 87 | 88 | # we don't need printf 89 | DEFINES += HAVE_PRINTF PRINTF= 90 | else 91 | # Release flags 92 | DEFINES += PRINTF\(...\)= 93 | endif 94 | 95 | ifneq ($(BOLOS_ENV),) 96 | $(info BOLOS_ENV=$(BOLOS_ENV)) 97 | CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/ 98 | GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/ 99 | else 100 | $(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH) 101 | endif 102 | ifeq ($(CLANGPATH),) 103 | $(info CLANGPATH is not set: clang will be used from PATH) 104 | endif 105 | ifeq ($(GCCPATH),) 106 | $(info GCCPATH is not set: arm-none-eabi-* will be used from PATH) 107 | endif 108 | 109 | CC := $(CLANGPATH)clang 110 | 111 | AS := $(GCCPATH)arm-none-eabi-gcc 112 | AFLAGS += 113 | 114 | LD := $(GCCPATH)arm-none-eabi-gcc 115 | 116 | ifeq ($(DEBUG),1) 117 | LDFLAGS += -O0 -g3 118 | else 119 | LDFLAGS += -O2 120 | endif 121 | 122 | LDLIBS += -lm -lgcc -lc 123 | 124 | # import rules to compile glyphs(/pone) 125 | include $(BOLOS_SDK)/Makefile.glyphs 126 | 127 | APP_SOURCE_PATH += src 128 | SDK_SOURCE_PATH += lib_stusb lib_stusb_impl lib_ux 129 | 130 | # Allow usage of function from lib_standard_app/crypto_helpers.c 131 | APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/crypto_helpers.c 132 | 133 | ifeq ($(TARGET_NAME),TARGET_NANOX) 134 | SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl 135 | endif 136 | 137 | load: all 138 | python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) 139 | 140 | load-offline: all 141 | python3 -m ledgerblue.loadApp $(APP_LOAD_PARAMS) --offline 142 | 143 | delete: 144 | python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) 145 | 146 | include $(BOLOS_SDK)/Makefile.rules 147 | 148 | dep/%.d: %.c Makefile 149 | 150 | listvariants: 151 | @echo VARIANTS CHAIN iota shimmer 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IOTA Chrysalis App for Ledger Hardware Wallets 2 | 3 | [![license](https://img.shields.io/github/license/IOTA-Ledger/blue-app-iota.svg)](https://github.com/IOTA-Ledger/blue-app-iota/blob/master/LICENSE) 4 | 5 | 6 | ***It is strongly recommended to take a few minutes to read this document. You need to make sure to fully understand how the Ledger hardware wallet works in combination with the IOTA network.*** 7 | 8 | ## Table of contents 9 | 10 | - [IOTA Chrysalis App for Ledger Hardware Wallets](#iota-chrysalis-app-for-ledger-hardware-wallets) 11 | - [Table of contents](#table-of-contents) 12 | - [Introduction](#introduction) 13 | - [Terminology](#terminology) 14 | - [Address Reuse](#address-reuse) 15 | - [IOTA Block](#iota-block) 16 | - [Parts of an IOTA Block](#parts-of-an-iota-block) 17 | - [How Ledger Hardware Wallets Work](#how-ledger-hardware-wallets-work) 18 | - [IOTA Specific Considerations for Ledger Hardware Wallets](#iota-specific-considerations-for-ledger-hardware-wallets) 19 | - [IOTA User-Facing App Functions](#iota-user-facing-app-functions) 20 | - [Functions](#functions) 21 | - [Display](#display) 22 | - [IOTA Security Concerns Relating to Ledger Hardware Wallets](#iota-security-concerns-relating-to-ledger-hardware-wallets) 23 | - [Limitations of Ledger Hardware Wallets](#limitations-of-ledger-hardware-wallets) 24 | - [FAQ](#faq) 25 | - [I lost my ledger, what should I do now?](#i-lost-my-ledger-what-should-i-do-now) 26 | - [Development](#development) 27 | - [Prepare Repository and Submodules](#prepare-repository-and-submodules) 28 | - [Using Docker](#using-docker) 29 | - [Building the App](#building-the-app) 30 | - [Running the Speculos Simulator](#running-the-speculos-simulator) 31 | - [Loading the IOTA Ledger App](#loading-the-iota-ledger-app) 32 | - [Specification](#specification) 33 | 34 | --- 35 | 36 | ## Introduction 37 | 38 | IOTA is an unique cryptocurrency with specific design considerations that must be taken into account. This document explains how the Ledger hardware wallet works and how to stay safe when using a Ledger to store IOTA funds. 39 | 40 | ### Terminology 41 | 42 | *Seed:* A single secret key from which all private keys are derived (aka "*24 words*", [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)). 43 | 44 | *Private Key:* Private keys are derived from the seed ([BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) with ED25519 curve, [SLIP10](https://github.com/satoshilabs/slips/blob/master/slip-0010.md). The private key is used to generate a public key. The private key is also used to prove that you own said public key by means of creating a signature for a specific transaction. 45 | 46 | *Public Key:* The public part of a private/public key pair. 47 | 48 | *Address:* A hashed representation of the public key that is used to receive funds. 49 | 50 | *Input:* The unspent transaction output (UTXO) from which funds are transfered. 51 | 52 | *Output:* The address to which funds are transfered. 53 | 54 | *Change/Remainder:* After sending funds to a 3rd party, all remaining funds on the account must be transferred to a new address - this is called the change or remainder address. 55 | 56 | ### Address Reuse 57 | 58 | With the Chrysalis update IOTA switched from quantum resistance signatures (Winternitz One-Time) to ED25519 signatures and from an account based model to an UTXO model. For this reason, address reuse is no longer a problem and IOTA is as secure as *other major crypto currencies*. 59 | 60 | ### IOTA Block 61 | 62 | A block is mainly just a group of inputs and outputs. If Bob has 10Mi and he wants to send 3Mi to Alice then the block could look like this: 63 | 64 | **input:** Bob -10Mi 65 | 66 | **output:** Alice +3Mi 67 | 68 | **output:** Bob +7Mi (change output / remainder) 69 | 70 | This example highlights how the IOTA network handles blocks. First an input of 10Mi from Bob's address is used. After this 3Mi are sent to Alice and the remaining funds are sent to a new address belonging to Bob's seed. 71 | 72 | All inputs require the private key to generate signatures which prove that you own the funds. 73 | 74 | Because blocks are *atomic units*, the network will never accept Bob's input of -10Mi without accepting sending 3Mi to Alice and 7Mi to a new address owned by Bob (in this example). This new address where the remaining funds are sent to is called *remainder address.* 75 | 76 | ### Parts of an IOTA Block 77 | 78 | An IOTA block is split into different parts. The first part is to create a block and signatures for this block. 79 | 80 | The second part is to attach this block to the tangle. Therefore the block needs between one and eight tip messages which are requested from a node of the IOTA network. 81 | 82 | The Ledger hardware wallet is **only** responsible for generating signatures for a specific block. After that the host machine (or anybody else for that matter) can take the signatures and broadcast it to the network (however the signatures are only valid for the specific block). 83 | 84 | ## How Ledger Hardware Wallets Work 85 | 86 | The Ledger hardware wallet works by deterministically generating IOTA keys based on your 24 word mnemonic (created when setting up the device). 87 | 88 | Instead of storing the seed on your computer (which could be stolen by an attacker) the seed is **only** stored on the Ledger device itself. This ensures that the seed is never sent to the host machine it is connected to. 89 | 90 | Instead the host machine needs to ask the Ledger device to provide the information (such as public keys or signatures). When blocks are created the host generates an unsigned block and sends it to the Ledger device for signing. The Ledger uses the private keys associated with the inputs to generate unique signatures and transmits **only the signatures** to the host machine. 91 | 92 | The host is then able to use these signatures (which are only valid for that specific block) to broadcast the block to the network. However, as you can see, neither the seed nor any of the private keys ever leave the Ledger device. 93 | 94 | See [Ledger's documentation](https://developers.ledger.com/) to get more info about the inner workings of the Ledger hardware wallets. 95 | 96 | ## IOTA Specific Considerations for Ledger Hardware Wallets 97 | 98 | ### IOTA User-Facing App Functions 99 | 100 | #### Functions 101 | 102 | - *Display Address:* The wallet is able to ask the Ledger device to display the address for a specific index of the seed. It **only** accepts the index and subsequently generates the address itself. It thus verifies that the address belongs to the corresponding Ledger. 103 | 104 | *Note: Only the remainder address (shown as "Remainder") will be verified to belong to the Ledger. Outputs shown as "Send To" are not.* 105 | 106 | - *Sign Transaction:* The wallet will generate a block for the user to approve before the Ledger signs it. **Ensure all amounts and addresses are correct before signing**. These signatures are then sent back to the host machine. 107 | 108 | #### Display 109 | 110 | **TODO** 111 | 112 | 113 | ### IOTA Security Concerns Relating to Ledger Hardware Wallets 114 | 115 | All warnings on the Ledger are there for a reason. **MAKE SURE TO READ THEM** and refer to this document if there is any confusion. 116 | 117 | - **Don't rush through signing a block.** 118 | Before a block is signed all outputs of a block are shown on the display. The user can scroll through the individual outputs and finally choose whether to sign the block ("Accept") or not ("Reject"). 119 | 120 | Outputs that go to 3rd party addresses are shown on the display with "Send To". The address to which the rest is sent is shown as "remainder". 121 | 122 | In addition to the amount the remainder also shows the BIP32 path. This address is calculated on the Ledger and ensures that the rest of the IOTA funds are sent to an address owned by the Ledger device. 123 | 124 | If the input amount perfectly matches the output amount then there will be no remainder. **If there is no remainder, double check that you are sending the proper amount to the proper address because there is no remainder being sent back to your seed.** 125 | 126 | ### Limitations of Ledger Hardware Wallets 127 | 128 | Due to the memory limitations of the Ledger Nano S/X, the blocks have certain restrictions. The Ledger Nano S can only accept blocks with about 17 inputs/outputs (e.g. 4 inputs, 13 outputs). The Ledger Nano X has the ability to accept blocks with approximately 180 inputs/outputs. 129 | 130 | ## FAQ 131 | 132 | #### I lost my ledger, what should I do now? 133 | 134 | Hopefully you wrote down your 24 recovery words and your optional passphrase in a safe place. If not, all your funds are lost. 135 | 136 | If you did, the best solution is to buy a new Ledger and enter your 24 recovery words and your optional passphrase in the new device.
137 | After installation of the IOTA Ledger app, all your funds are restored. Take care to reinitialize your seed index correctly. 138 | 139 | ## Development 140 | 141 | You either can 142 | 143 | - run the app in a Ledger Nano S/X/S+ simulator or 144 | - load the app on your read Ledger Nano S/S+ 145 | 146 | ### Prepare Repository and Submodules 147 | 148 | - Clone this repo 149 | - Ensure that all git submodules are initialized 150 | ``` 151 | git submodule update --init --recursive 152 | ``` 153 | 154 | ### Using Docker 155 | 156 | Using the `build.sh` script is highly recommended because it uses official Docker images for building the app and running the Speculos simulator. 157 | 158 | #### Building the App 159 | 160 | ``` 161 | ./build.sh -m (nanos*|nanox|nanosplus) 162 | ``` 163 | 164 | #### Running the Speculos Simulator 165 | 166 | The app can be built by: 167 | ``` 168 | ./build.sh -m (nanos*|nanox|nanosplus) -s 169 | ``` 170 | 171 | The simulator will open the port `9999` for APDU commands and provide a web-interface on port `5000`. 172 | 173 | #### Loading the IOTA Ledger App 174 | 175 | The app can be built and installed in the following way: 176 | 177 | - Connect your Ledger to the computer and unlock it 178 | - To load the app, be sure that the dashboard is opened on the Ledger 179 | - Run the following command to compile the app from source and load it 180 | ``` 181 | ./build.sh -m (nanos*|nanox|nanosplus) -l 182 | ``` 183 | - Accept all the messages on the Ledger 184 | 185 | *: Default is `nanos` if parameter is not specified 186 | 187 | ## Specification 188 | 189 | See: [APDU API Specification](docs/specification_chrysalis.md) 190 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 IOTA-Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # get real path of script 18 | rpath="$( dirname $( readlink -f $0 ) )" 19 | cd $rpath 20 | 21 | # use the latest images 22 | IMAGE_BUILD="ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest" 23 | IMAGE_SPECULOS="ghcr.io/ledgerhq/speculos:latest" 24 | 25 | function error { 26 | echo "error: $1" 27 | exit 1 28 | } 29 | 30 | function usage { 31 | echo "usage: $0 [-h|--help] [-d|--debug] [-m|--model (nanos*|nanox|nanosplus)] [-l|--load] [-s|--speculos] [-c|--cxlib 1.0.2]" 32 | echo "-d|--debug: build app with DEBUG=1" 33 | echo "-b|--background: start simulator in background (detached)" 34 | echo "-m|--model: nanos (default), nanox, nanosplus" 35 | echo "-l|--load: load app to device" 36 | echo "-p|--pull: force pull docker images before using them" 37 | echo "-s|--speculos: run app after building with the speculos simulator" 38 | echo "-c|--cxlib: don't autodetect cx-lib version (for speculos)" 39 | echo "-g|--gdb: start speculos with -d (waiting for gdb debugger)" 40 | echo "-a|--analyze run static code analysis" 41 | echo "-v|--variant build for 'iota' or 'shimmer'" 42 | echo "-n|--nobuild don't build the app new" 43 | exit 1 44 | } 45 | 46 | # pull and tag image 47 | function pull_image { 48 | # already pulled? 49 | (( !pull )) && docker inspect --type=image "$2" >& /dev/null && return 0 50 | 51 | docker image pull "$1" && \ 52 | docker image tag "$1" "$2" 53 | } 54 | 55 | # check if we are using root/sudo 56 | whoami="$( whoami )" 57 | 58 | [[ "$whoami" == "root" ]] && { 59 | error "please don't run the script as root or with sudo." 60 | } 61 | 62 | if [ "$(uname)" == "Linux" ]; then 63 | # and if the user has permissions to use docker 64 | grep -q docker <<< "$( id -Gn $whoami )" || { 65 | echo "user $whoami not in docker group." 66 | echo "to add the user you can use (on Ubuntu):" 67 | echo 68 | echo "sudo usermod -a -G docker $whoami" 69 | echo 70 | echo "after adding, logout and login is required" 71 | exit 1 72 | } 73 | fi 74 | 75 | # let's parse argments 76 | device="nanos" # default 77 | nobuild=0 78 | load=0 79 | speculos=0 80 | debug=0 81 | gdb=0 82 | background=0 83 | analysis=0 84 | pull=0 85 | cxlib="" 86 | variant="" 87 | nobuild=0 88 | while (( $# )) 89 | do 90 | case "$1" in 91 | "-h" | "--help") 92 | usage 93 | ;; 94 | "-m" | "--model") 95 | shift 96 | device="$1" 97 | ;; 98 | "-l" | "--load") 99 | load=1 100 | ;; 101 | "-s" | "--speculos") 102 | speculos=1 103 | ;; 104 | "-c" | "--cxlib") 105 | shift 106 | cxlib="$1" 107 | ;; 108 | "-d" | "--debug") 109 | debug=1 110 | ;; 111 | "-b" | "--background") 112 | background=1 113 | ;; 114 | "-g" | "--gdb") 115 | gdb=1 116 | ;; 117 | "-a" | "--analyze") 118 | analysis=1 119 | ;; 120 | "-v" | "--variant") 121 | shift 122 | variant="$1" 123 | ;; 124 | "-p" | "--pull") 125 | pull=1 126 | ;; 127 | "-n" | "--nobuild") 128 | nobuild=1 129 | ;; 130 | *) 131 | error "unknown parameter: $1" 132 | ;; 133 | esac 134 | shift 135 | 136 | done 137 | 138 | # not supported combinations of flags? 139 | (( $speculos )) && (( $load )) && error "-l and -s cannot be used at the same time" 140 | 141 | # map device to model (different in speculos) 142 | case "$device" in 143 | nanos ) 144 | model="nanos" 145 | ;; 146 | nanox ) 147 | model="nanox" 148 | ;; 149 | nanosplus ) 150 | model="nanosp" 151 | ;; 152 | *) 153 | error "unknown device: $device" 154 | ;; 155 | esac 156 | 157 | # build the app 158 | # pull and tag image 159 | pull_image \ 160 | "$IMAGE_BUILD" \ 161 | ledger-app-builder || error "couldn't pull image" 162 | 163 | build_flags="BOLOS_SDK=/opt/$device-secure-sdk " 164 | 165 | # if speculos requested, add the flag 166 | (( $speculos )) && { 167 | build_flags+="SPECULOS=1 " 168 | } 169 | 170 | # if debug requested, add the flag 171 | (( $debug )) && { 172 | build_flags+="DEBUG=1 " 173 | } 174 | 175 | [ ! -z "$variant" ] && { 176 | build_flags+="CHAIN=$variant " 177 | } 178 | 179 | extra_args="" 180 | 181 | (( analysis )) && { 182 | build_flags+=" scan-build --use-cc=clang -analyze-headers -enable-checker security -enable-checker unix -enable-checker valist -o scan-build --status-bugs " 183 | extra_args+="-v /tmp:/app/scan-build " 184 | } 185 | 186 | # default make cmd 187 | cmd="make clean && $build_flags make " 188 | 189 | # we have to map usb into the docker when loading the app 190 | (( $load )) && { 191 | extra_args+="--privileged -v /dev/bus/usb:/dev/bus/usb " 192 | cmd+="&& $build_flags make load" 193 | } 194 | 195 | (( !nobuild )) && { 196 | docker run \ 197 | $extra_args \ 198 | --rm -v "$rpath:/app" \ 199 | ledger-app-builder \ 200 | bash -c "$cmd" || error "building failed" 201 | } 202 | 203 | (( $load )) && { 204 | # we are finished 205 | exit 0 206 | } 207 | 208 | # run the simulator 209 | (( $speculos )) && { 210 | # pull and tag image 211 | pull_image \ 212 | "$IMAGE_SPECULOS" \ 213 | speculos || error "couldn't pull image" 214 | 215 | # default Ledger seed 216 | seed="glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster seven myth punch hobby comfort wild raise skin" 217 | 218 | [ -f "testseed.txt" ] && { seed="$( cat "testseed.txt" )"; } 219 | 220 | [ ! -f "./bin/app.elf" ] && { 221 | error "binary missing. Something went wrong" 222 | } 223 | 224 | # get app name and version from Makefile 225 | # remove only whitespaces before and after = 226 | eval $( grep '^APPNAME' Makefile | sed -e ':loop;s/ =/=/g;s/= /=/g;t loop' ) 227 | # replace () to {} before eval 228 | eval $( grep '^APPVERSION' Makefile | tr -d ' ' | tr '()' '{}' ) 229 | 230 | { sleep 10; echo -e "\nPlease open your browser: http://localhost:5000\n"; echo; } & 231 | 232 | (( $gdb )) && extra_args="-d " 233 | 234 | (( $background )) && docker_extra_args="-d " 235 | 236 | docker run \ 237 | -v "$rpath:/speculos/apps" \ 238 | -p 9999:9999 \ 239 | -p 5000:5000 \ 240 | -p 1234:1234 \ 241 | -e SPECULOS_APPNAME="$APPNAME:$APPVERSION" \ 242 | $docker_extra_args \ 243 | --rm \ 244 | speculos \ 245 | --apdu-port 9999 \ 246 | --display headless \ 247 | --seed "$seed" \ 248 | $extra_args \ 249 | -m "$model" /speculos/apps/bin/app.elf 250 | } 251 | 252 | exit 0 253 | -------------------------------------------------------------------------------- /docs/apdu_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/apdu_request.png -------------------------------------------------------------------------------- /docs/apdu_request.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 54 | 55 | 57 | 58 | 60 | image/svg+xml 61 | 63 | 64 | 65 | 66 | 67 | 72 | 79 | 86 | 93 | CLA 105 | 110 | INS 121 | P1 132 | P2 143 | 148 | 153 | 158 | 164 | 169 | LEN 181 | DATA 192 | HEADER 203 | BODY 214 | 215 | 216 | -------------------------------------------------------------------------------- /docs/data_buffer_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/data_buffer_states.png -------------------------------------------------------------------------------- /docs/images/address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/images/address.png -------------------------------------------------------------------------------- /docs/images/blindsigning_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/images/blindsigning_1.png -------------------------------------------------------------------------------- /docs/images/blindsigning_ne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/images/blindsigning_ne.png -------------------------------------------------------------------------------- /docs/images/claim_smr_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/images/claim_smr_1.png -------------------------------------------------------------------------------- /docs/images/claim_smr_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/images/claim_smr_2.png -------------------------------------------------------------------------------- /docs/images/internal_transfer_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/images/internal_transfer_1.png -------------------------------------------------------------------------------- /docs/images/internal_transfer_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/docs/images/internal_transfer_2.png -------------------------------------------------------------------------------- /docs/specification.md: -------------------------------------------------------------------------------- 1 | # IOTA/Shimmer Chrysalis/Stardust App for Ledger Nano S(+)/X Hardware Wallets 2 | 3 | ## Date Buffer States 4 | 5 | 6 | The app uses an data buffer of 1506 bytes on the Nano S / 8032 bytes on the Nano X for exchanging data with the client. 7 | 8 | Read and write permissions are dependent from the current state the data buffer is in. Reading only is allowed if the buffer contains generated addresses or signatures. Writing only is allowed after it was cleared. 9 | 10 | Following image illustrates all states of the data buffer. The nodes represent the state, the edges the API commands that are executed to transition from one state to another. 11 | 12 | ![](data_buffer_states.png) 13 | 14 | ## API Commands 15 | 16 | The protocol used for communicating with Ledger wallets is based on the [APDU](https://de.wikipedia.org/wiki/Application_Protocol_Data_Unit) protocol defined in the ISO7816 (Smartcards). 17 | 18 | A transfer consists of an Header and a Body. 19 | 20 | 21 | ![](apdu_request.png) 22 | 23 | 24 | 25 | 26 | | Field Name | Length (bytes)| Description | 27 | |---------------|---------------|-----------------------------------------------| 28 | | CLA | 1 | Instruction Class (0x7b) | 29 | | INS | 1 | Instruction Code | 30 | | P1-P2 | 1 | Instruction Parameters | 31 | | LEN | 1 | Length of data to be sent | 32 | | DATA | LEN | Data 33 | 34 | ### API Commands 35 | --- 36 | #### 0x00 - No Operation 37 | 38 | **Precondition**: \- 39 | 40 | **After**: unchanged 41 | 42 | **Parameters**: \- 43 | 44 | **Request**: \- 45 | 46 | **Response**: \- 47 | 48 | **Errors**: \- 49 | 50 | --- 51 | #### 0x10 - Get App Config 52 | 53 | Returns information about the App and the Device. 54 | 55 | **Precondition**: \- 56 | 57 | **After**: unchanged 58 | 59 | **Parameters**: \- 60 | 61 | **Request**: \- 62 | 63 | **Response**: 64 | 65 | 66 | | Field | Bytes | Description | 67 | |-------------------|---|---| 68 | | `app_version_major` | 1 | Major version number | 69 | | `app_version_minor` | 1 | Minor version number | 70 | | `app_version_patch` | 1 | Patch level | 71 | | `app_flags` | 1 | Flags | 72 | | `device` | 1 | 0: Nano S
1: Nano X
2: Nano S Plus | 73 | | `debug`| 1 | 0: Not compiled in debug mode
1: compiled in debug mode| 74 | 75 | Flags: 76 | | Bit | Function | 77 | |-----|----------| 78 | | 0 | Ledger locked | 79 | | 1 | Blindsigning enabled (only Stardust) | 80 | | 2 | App (0: IOTA, 1: Shimmer) | 81 | 82 | **Errors**: \- 83 | 84 | 85 | --- 86 | #### 0x11 - Set Account 87 | 88 | All private keys are derived from the bip32-path `2c'/coin_type'/address'/change'/index'`. This command sets the address bip32 index. The MSB must be set to indicate a *hardened* index. When executing `Set Account` the API and data buffer is initialized. The data buffer state is set to `EMPTY`. In the following documentation, `BIP32 index` always means the 5th part of the BIP32 path and `BIP32 change` always the 4th. 89 | 90 | **Precondition**: \- 91 | 92 | **After**: `EMPTY` 93 | 94 | **Parameters**: \- 95 | 96 | **Request**: 97 | 98 | | Field | Bytes | Description | 99 | |-------------------|---|---| 100 | | `account` | 4 | Hardened BIP32 account index | 101 | | `p1` | 1 | App mode | 102 | 103 | App-Mode: 104 | 105 | IOTA-Variant: 106 | | Mode | Coin Type | Function | 107 | |-|-|-| 108 | | 0x00 | 0x107a | IOTA + Chrysalis (default, backwards compatible) | 109 | | 0x80 | 0x1 | IOTA + Chrysalis Testnet | 110 | | 0x01 | 0x107a | IOTA + Stardust | 111 | | 0x81 | 0x1 | IOTA + Stardust Testnet | 112 | 113 | Shimmer-Variant: 114 | | Mode | Coin Type | Function | 115 | |-|-|-| 116 | | 0x02 | 0x107a | Shimmer Claiming (from IOTA addresses) | 117 | | 0x82 | 0x1 | Shimmer Claiming Testnet | 118 | | 0x03 | 0x107b | Shimmer + Stardust | 119 | | 0x83 | 0x1 | Shimmer + Stardust Testnet | 120 | 121 | **Response**: \- 122 | 123 | **Errors**: 124 | 125 | | Error | Description | 126 | |-------|-------------| 127 | |`SW_ACCOUNT_NOT_VALID`|Account bip32 index is not hardened| 128 | |`SW_COMMAND_INVALID_DATA`|request data unexpected size, invalid BIP32 address index 129 | 130 | 131 | 132 | --- 133 | #### 0x80 - Get Data Buffer State 134 | 135 | Get current state of the data buffer. 136 | 137 | **Precondition**: \- 138 | 139 | **After**: unchanged 140 | 141 | **Parameters**: \- 142 | 143 | **Request**: \- 144 | 145 | **Response**: 146 | 147 | | Field | Bytes | Description | 148 | |-------------------|---|---| 149 | | `data_length` | 2 | Length of bytes of valid data in the buffer | 150 | | `data_type` | 1 | 0: `EMPTY`
1: `GENERATED_ADDRESSES`
2: `VALIDATED_ESSENCE`
3: `USER_CONFIRMED_ESSENCE`
4: `SIGNATURES`
5: `LOCKED` | 151 | | `data_block_size` | 1 | Size of one block in bytes | 152 | | `data_block_count` | 1 | Total number of usable data blocks | 153 | 154 | **Errors**: \- 155 | 156 | --- 157 | #### 0x81 - Write Data Block 158 | 159 | Writes a block of data into the data buffer. The (current) only usage is for transfering the Essence to the device before `Prepare Signing` is executed. Writing to the data buffer only is allowed if it's in `EMPTY` state (see [states](#anchor-data-buffer-states) ). 160 | 161 | **Precondition**: `EMPTY` 162 | 163 | **After**: unchanged 164 | 165 | **Parameters**: 166 | 167 | | Parameter | Description | 168 | |-------|-------------| 169 | |`p0` | number of block to be written 170 | 171 | **Request**: 172 | 173 | | Field | Bytes | Description | 174 | |-------------------|---|---| 175 | | `data` | `data_block_size` | Data to be written to the data buffer | 176 | 177 | 178 | **Response**: 179 | 180 | **Errors**: 181 | 182 | | Error | Description | 183 | |-------|-------------| 184 | |`SW_COMMAND_NOT_ALLOWED`|data buffer state is not `EMPTY`.| 185 | |`SW_INCORRECT_LENGTH`|length is unequal the block size 186 | |`SW_INCORRECT_P1P2`|block number too high 187 | 188 | 189 | --- 190 | #### 0x82 - Read Data Block 191 | 192 | 193 | Reads a block of data from the data buffer. Reading only is allowed if the data buffer is in `GENERATED_ADDRESSES` or `SIGNATURES` state (see [states](#anchor-data-buffer-states) ). 194 | 195 | **Precondition**: `GENERATED_ADDRESSES`, `SIGNATURES` 196 | 197 | **After**: unchanged 198 | 199 | **Parameters**: 200 | 201 | | Parameter | Description | 202 | |-------|-------------| 203 | |`p1` | number of block to be read 204 | 205 | 206 | **Request**: \- 207 | 208 | **Response**: 209 | 210 | | Field | Bytes | Description | 211 | |-------------------|---|---| 212 | | `data` | `data_block_size` | Data read from the data buffer | 213 | 214 | **Errors**: 215 | 216 | | Error | Description | 217 | |-------|-------------| 218 | |`SW_COMMAND_NOT_ALLOWED`|data buffer state is not `GENERATED_ADDRESSES` or `SIGNATURES`.| 219 | |`SW_INCORRECT_P1P2`|block number too high 220 | 221 | 222 | 223 | --- 224 | #### 0x83 - Clear Data Buffer 225 | 226 | Clears the API and data buffer and sets it to `EMPTY`. The set bip32 account index is not reset. If app was compiled in debug mode, also the non-interactive flag is not set to zero. 227 | 228 | **Precondition**: \- 229 | 230 | **After**: `EMPTY` 231 | 232 | **Parameters**: \- 233 | 234 | **Request**: \- 235 | 236 | **Response**: \- 237 | 238 | **Errors**: \- 239 | 240 | 241 | --- 242 | #### 0x90 - Show Flow 243 | 244 | Shows a flow on the UI. This instruction is for showing informative screens on the UI or switching back to the main menu. 245 | 246 | **Preconditions**: \- 247 | 248 | **After**: unchanged 249 | 250 | **Parameters**: 251 | 252 | | Parameter | Description | 253 | |-------|-------------| 254 | |`p1` | 0: `FLOW_MAIN_MENU`
1: `FLOW_GENERATING_ADDRESSES`
2: `FLOW_GENERIC_ERROR`
3: `FLOW_REJECTED`
4: `FLOW_SIGNED_SUCCESSFULLY`
5: `FLOW_SIGNING` 255 | 256 | 257 | **Request**: \- 258 | 259 | **Response**: \- 260 | 261 | **Errors**: 262 | 263 | | Error | Description | 264 | |-------|-------------| 265 | |`SW_INCORRECT_P1P2`|selected flow doesn't exist.| 266 | 267 | --- 268 | #### 0xa0 - Prepare Signing 269 | 270 | Prepare signing parses and validates the uploaded essence for a following signing call. 271 | 272 | In additionally to the essence also an array of input BIP32 indices (consisting of 32bit BIP32-index and 32bit BIP32-Change per input) is needed. The size of the essence is determined by the data and is calculated during validation. The array of input indices is directly appended after. 273 | 274 | **Preconditions**: `EMPTY` 275 | 276 | **After**: `VALIDATED_ESSENCE` 277 | 278 | **Parameters**: 279 | 280 | | Parameter | Description | 281 | |-------|-------------| 282 | |`p1` | (set to 1, compatibility) 283 | |`p2` | 0: Essence doesn't have an Remainder
1: Essence has an Remainder 284 | 285 | **Request**: 286 | 287 | | Field | Bytes | Description | 288 | |-------------------|---|---| 289 | | `remainder_index` | 2 | Output index in essence of Remainder address | 290 | | `remainder_bip32_index` | 4 | BIP32 index of remainder address in essence | 291 | | `remainder_bip32_change` | 4 | BIP32 change of remainder address in essence | 292 | 293 | 294 | 295 | **Response**: \- 296 | 297 | **Errors**: 298 | 299 | | Error | Description | 300 | |-------|-------------| 301 | |`SW_COMMAND_NOT_ALLOWED`|data buffer state is not `EMPTY`.| 302 | |`SW_ACCOUNT_NOT_VALID`|Account bip32 index is not hardened| 303 | |`SW_COMMAND_INVALID_DATA`|- invalid BIP32 remainder
- essence validation failed| 304 | |`SW_INCORRECT_LENGTH`| request data has unexpected size 305 | 306 | --- 307 | #### 0xa0 - Prepare Blindsigning 308 | 309 | Prepare blindsigning parses and validates the uploaded essence for a following signing call. 310 | 311 | The call expects an essence-hash, a 16bit value for the number of inputs and an array of input BIP32 indices (consisting of 32bit BIP32-index and 32bit BIP32-Change per input). 312 | 313 | Blindsigning is only supported with Stardust protocol. 314 | 315 | **Databuffer**: 316 | | Field | Bytes | Description | 317 | |-------------------|---|---| 318 | | `essence-hash` | 32 | Hash of the essence to sign | 319 | | `inputs_count` | 2 | Number of BIP32 input indices to follow | 320 | | `bip32 input indices` | `inputs_count*8` | BIP32 input indices | 321 | 322 | 323 | **Preconditions**: `EMPTY` 324 | 325 | **After**: `VALIDATED_ESSENCE` 326 | 327 | **Parameters**: \- 328 | 329 | **Parameters**: \- 330 | 331 | **Response**: \- 332 | 333 | **Errors**: 334 | 335 | | Error | Description | 336 | |-------|-------------| 337 | |`SW_COMMAND_NOT_ALLOWED`|- data buffer state is not `EMPTY`
- App is not used in Stardust protocol mode.| 338 | |`SW_ACCOUNT_NOT_VALID`|Account bip32 index is not hardened| 339 | |`SW_COMMAND_INVALID_DATA`|Essence validation failed| 340 | 341 | --- 342 | #### 0xa1 - Generate Address 343 | 344 | Generates addresses. If `p1` is set, the new address is shown to the user on the UI. In this mode, the data buffer states switches to `GENERATED_ADDRESSES` when the user confirmed the new address and only one single address is allowed to generate (for Remainder addresses). 345 | 346 | The addresses are saved (including the ED25519 address type) in the data buffer one after another. 347 | 348 | **Preconditions**: `EMPTY` 349 | 350 | **After**: `GENERATED_ADDRESSS`* 351 | 352 | **Parameters**: 353 | 354 | | Parameter | Description | 355 | |-------|-------------| 356 | |`p1` | 0: don't show to the user as interactive flow
1: show to the user as interactive flow (for Remainders). 357 | 358 | **Request**: 359 | 360 | | Field | Bytes | Description | 361 | |-------------------|---|---| 362 | | `bip32_index` | 4 | BIP32 index of first address | 363 | | `bip32_change` | 4 | BIP32 change of first address | 364 | | `count` | 4 | Count of addresses to generate | 365 | 366 | 367 | **Response**: \- 368 | 369 | **Errors**: 370 | 371 | | Error | Description | 372 | |-------|-------------| 373 | |`SW_ACCOUNT_NOT_VALID`|Account bip32 index is not hardened| 374 | |`SW_COMMAND_NOT_ALLOWED`|data buffer state is not `EMPTY`.| 375 | |`SW_INCORRECT_LENGTH`| request data has unexpected size 376 | |`SW_COMMAND_INVALID_DATA`|too many addresses to generate, `show_on_screen` set but more than one address to generate, invalid BIP32 index or BIP32 index would wrap around during generation| 377 | |`SW_COMMAND_TIMEOUT`| Timeout happened in interactive mode 378 | 379 | --- 380 | #### 0xa3 - User Confirm Essence 381 | 382 | Presents the validated and parsed essence in clear way on the UI and asks the user to accept or deny it. 383 | 384 | **Preconditions**: `VALIDATED_ESSENCE` 385 | 386 | **After**: `USER_CONFIRMED_ESSENCE` 387 | 388 | **Parameters**: \- 389 | 390 | **Request**: \- 391 | 392 | **Response**: \- 393 | 394 | **Errors**: 395 | 396 | | Error | Description | 397 | |-------|-------------| 398 | |`SW_COMMAND_NOT_ALLOWED`|data buffer state is not `VALIDATED_ESSENCE`.| 399 | |`SW_UNKNOWN`|some basic validation checks failed although validated prior by `Prepare Signing` 400 | |`SW_COMMAND_TIMEOUT`| Timeout happened in interactive mode 401 | |`SW_DENIED_BY_USER`| User denied the essence 402 | 403 | --- 404 | #### 0xa4 - Sign Single 405 | 406 | Signs a single input and returns the signature in the APDU buffer. 407 | 408 | 409 | **Preconditions**: `USER_CONFIRMED_ESSENCE` 410 | 411 | **After**: unchanged 412 | 413 | **Parameters**: 414 | 415 | | Parameter | Description | 416 | |-------|-------------| 417 | |`p1` | Index of input to sign 418 | 419 | **Request**: \- 420 | 421 | **Response**: 422 | 423 | | Field | Bytes | Description | 424 | |-------------------|---|---| 425 | | `data` | sizeof(`SIGNATURE_UNLOCK_BLOCK`) or
sizeof(`REFERENCE_UNLOCK_BLOCK`) | Signature | 426 | 427 | 428 | **Errors**: 429 | 430 | | Error | Description | 431 | |-------|-------------| 432 | |`SW_COMMAND_NOT_ALLOWED`|data buffer state is not `USER_CONFIRMED_ESSENCE`.| 433 | |`SW_UNKNOWN`|some basic validation checks failed although validated prior by `Prepare Signing` 434 | |`SW_COMMAND_TIMEOUT`| Timeout happened in interactive mode 435 | |`SW_DENIED_BY_USER`| User denied the essence 436 | 437 | --- 438 | #### 0xff - Reset 439 | 440 | Resets API and data buffer completly (also resets account and non-interactive flag) 441 | 442 | 443 | **Preconditions**: \- 444 | 445 | **After**: `EMPTY` 446 | 447 | **Parameters**: \- 448 | 449 | **Request**: \- 450 | 451 | **Response**: \- 452 | 453 | **Errors**: \- 454 | 455 | 456 | ### Additional API Commands in Debug Mode 457 | 458 | --- 459 | #### 0x66 - Dump Memory 460 | 461 | Dumps a 128 byte memory page. The Nano S has 4kB, the Nano X has 32kB of internal RAM. Only is available in Debug-Mode. 462 | 463 | This command can be useful to verify how much stack in the device is used. 464 | 465 | **Preconditions**: \- 466 | 467 | **After**: unchanged 468 | 469 | **Parameters**: 470 | 471 | | Parameter | Description | 472 | |-------|-------------| 473 | |`p1` | Number of page to dump 474 | 475 | **Request**: \- 476 | 477 | **Response**: 478 | 479 | | Field | Bytes | Description | 480 | |-------------------|---|---| 481 | | `data` | 128 | One memory page | 482 | 483 | **Errors**: \- 484 | 485 | --- 486 | #### 0x67 - Set Non-Interactive Mode 487 | 488 | For automatic testing, the `non-interactive` flag can be set. Generating a new remainder address and confirming an essence is accepted automatically. 489 | 490 | This command only is available in debug mode. 491 | 492 | **Preconditions**: \- 493 | 494 | **After**: unchanged 495 | 496 | **Parameters**: 497 | 498 | | Parameter | Description | 499 | |-------|-------------| 500 | |`p1` | 0: Non-Interactive Mode disabled
1: Non-Interactive Mode enabled 501 | 502 | **Request**: \- 503 | 504 | **Response**: \- 505 | 506 | **Errors**: \- 507 | -------------------------------------------------------------------------------- /fuzz/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(essence_fuzzer) 4 | 5 | set(CMAKE_C_STANDARD 99) 6 | 7 | include_directories(. ../src ../src/iota) 8 | 9 | add_definitions(-DFUZZING) 10 | 11 | add_executable(essence_fuzzer fuzztest.c ../src/iota/essence.c) 12 | add_executable(essence_fuzzer_coverage fuzztest.c ../src/iota/essence.c) 13 | 14 | target_compile_options(essence_fuzzer PRIVATE -fsanitize=fuzzer,address -g -ggdb2 -O1) 15 | target_compile_options(essence_fuzzer_coverage PRIVATE -fsanitize=fuzzer,address -g -ggdb2 -O1 -fprofile-instr-generate -fcoverage-mapping) 16 | 17 | target_link_options(essence_fuzzer PRIVATE -fsanitize=fuzzer,address) 18 | target_link_options(essence_fuzzer_coverage PRIVATE -fsanitize=fuzzer,address) -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | ## Fuzzing 2 | 3 | This fuzzer should reach a reasonable coverage (>85%) of `essence.c` with no test cases to start with and in a very short time, but this could always be improved by starting from a real testcase. 4 | 5 | ### On Linux: 6 | 7 | - `cmake -Bbuild -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` 8 | 9 | Fuzzing (from `./build/`): 10 | 11 | - `./essence_fuzzer ../corpus/` 12 | 13 | Running coverage: 14 | 15 | - `./essence_fuzzer_coverage ../corpus/*` 16 | 17 | ### On Windows: 18 | 19 | - `cmake -Bbuild -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` 20 | 21 | Fuzzing (from `./build/`): 22 | 23 | - `.\essence_fuzzer.exe ../corpus/` 24 | 25 | Running coverage: 26 | - `.\essence_fuzzer_coverage.exe $(ls ../corpus/* | % {$_.FullName})` 27 | 28 | ## Monitoring coverage 29 | 30 | ``` 31 | llvm-profdata merge -sparse *.profraw -o default.profdata 32 | llvm-cov report essence_fuzzer_coverage -instr-profile="default.profdata" 33 | llvm-cov show essence_fuzzer_coverage -instr-profile="default.profdata" --format=html > report.html 34 | ``` 35 | 36 | Will output a file `report.html` containing coverage information by line in the source file. 37 | -------------------------------------------------------------------------------- /fuzz/fuzztest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "api.h" 5 | #include "essence.h" 6 | 7 | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { 8 | if (Size >= API_BUFFER_SIZE_BYTES) { 9 | API_CTX api = {0}; 10 | memcpy(api.data.buffer, Data, API_BUFFER_SIZE_BYTES); 11 | // api.essence.has_remainder = 1; 12 | essence_parse_and_validate_chrysalis(&api); 13 | } 14 | return 0; 15 | } -------------------------------------------------------------------------------- /fuzz/os.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define os_memcpy memcpy 4 | #define os_memcmp memcmp 5 | #define os_memset memset 6 | #define explicit_bzero(addr, size) memset((addr), 0, (size)) 7 | 8 | -------------------------------------------------------------------------------- /glyphs/icon_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/icon_back.gif -------------------------------------------------------------------------------- /glyphs/icon_coggle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/icon_coggle.gif -------------------------------------------------------------------------------- /glyphs/icon_iota.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/icon_iota.gif -------------------------------------------------------------------------------- /glyphs/icon_shimmer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/icon_shimmer.gif -------------------------------------------------------------------------------- /glyphs/icon_warning.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/icon_warning.gif -------------------------------------------------------------------------------- /glyphs/x_icon_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_back.gif -------------------------------------------------------------------------------- /glyphs/x_icon_check.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_check.gif -------------------------------------------------------------------------------- /glyphs/x_icon_cross.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_cross.gif -------------------------------------------------------------------------------- /glyphs/x_icon_dash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_dash.gif -------------------------------------------------------------------------------- /glyphs/x_icon_info.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_info.gif -------------------------------------------------------------------------------- /glyphs/x_icon_left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_left.gif -------------------------------------------------------------------------------- /glyphs/x_icon_load.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_load.gif -------------------------------------------------------------------------------- /glyphs/x_icon_right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_icon_right.gif -------------------------------------------------------------------------------- /glyphs/x_iota_logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/glyphs/x_iota_logo.gif -------------------------------------------------------------------------------- /icons/nanos_app_iota.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/icons/nanos_app_iota.gif -------------------------------------------------------------------------------- /icons/nanos_app_shimmer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/icons/nanos_app_shimmer.gif -------------------------------------------------------------------------------- /icons/nanox_app_iota.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/icons/nanox_app_iota.gif -------------------------------------------------------------------------------- /icons/nanox_app_shimmer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/icons/nanox_app_shimmer.gif -------------------------------------------------------------------------------- /makefile_conf/chain/iota.mk: -------------------------------------------------------------------------------- 1 | APPNAME = "IOTA" 2 | 3 | DEFINES += APP_IOTA 4 | 5 | # IOTA BIP-path 6 | APP_LOAD_PARAMS += --path "44'/4218'" 7 | 8 | ifeq ($(TARGET_NAME),TARGET_NANOS) 9 | ICONNAME=icons/nanos_app_iota.gif 10 | else 11 | ICONNAME=icons/nanox_app_iota.gif 12 | endif 13 | 14 | -------------------------------------------------------------------------------- /makefile_conf/chain/shimmer.mk: -------------------------------------------------------------------------------- 1 | APPNAME = "Shimmer" 2 | 3 | DEFINES += APP_SHIMMER 4 | 5 | # IOTA BIP-path for claiming Shimmer from IOTA addresses 6 | APP_LOAD_PARAMS += --path "44'/4218'" 7 | 8 | # Shimmer BIP-path 9 | APP_LOAD_PARAMS += --path "44'/4219'" 10 | 11 | ifeq ($(TARGET_NAME),TARGET_NANOS) 12 | ICONNAME=icons/nanos_app_shimmer.gif 13 | else 14 | ICONNAME=icons/nanox_app_shimmer.gif 15 | endif 16 | 17 | -------------------------------------------------------------------------------- /resources/IOTA-Ledger Manager icon template.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/resources/IOTA-Ledger Manager icon template.ai -------------------------------------------------------------------------------- /resources/IOTA-Ledger Nano S icon template.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/resources/IOTA-Ledger Nano S icon template.ai -------------------------------------------------------------------------------- /src/api.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | 3 | #include 4 | 5 | #include "iota_io.h" 6 | #include "api.h" 7 | 8 | #include "macros.h" 9 | 10 | #include "ui/ui.h" 11 | 12 | #include "nv_mem.h" 13 | 14 | #include "abstraction.h" 15 | 16 | #include "iota/constants.h" 17 | #include "iota/blindsigning_stardust.h" 18 | #include "iota/signing.h" 19 | 20 | #include "ui/nano/flow_user_confirm_transaction.h" 21 | #include "ui/nano/flow_user_confirm_new_address.h" 22 | #include "ui/nano/flow_user_confirm_blindsigning.h" 23 | #include "ui/nano/flow_generating_addresses.h" 24 | #include "ui/nano/flow_signing.h" 25 | 26 | #pragma GCC diagnostic error "-Wall" 27 | #pragma GCC diagnostic error "-Wextra" 28 | #pragma GCC diagnostic error "-Wmissing-prototypes" 29 | 30 | /// global variable storing all data needed across multiple api calls 31 | API_CTX api; 32 | 33 | void api_initialize(APP_MODE_TYPE app_mode, uint32_t account_index) 34 | { 35 | // wipe all data 36 | explicit_bzero(&api, sizeof(api)); 37 | 38 | api.bip32_path[0] = 0x8000002c; 39 | 40 | // app-modes 41 | // IOTA App 42 | // 0x00: unused (formerly IOTA + Chrysalis) 43 | // 0x80: unused (formerly IOTA + Chrysalis Testnet) 44 | // 0x01: (107a) IOTA + Stardust 45 | // 0x81: (1) IOTA + Stardust Testnet 46 | 47 | // Shimmer App 48 | // 0x02: (107a) Shimmer Claiming (from IOTA) 49 | // 0x82: (1) Shimmer Claiming (from IOTA) (Testnet) 50 | // 0x03: (107b) Shimmer (default) 51 | // 0x83: (1) Shimmer Testnet 52 | 53 | switch (app_mode & 0x7f) { 54 | #if defined(APP_IOTA) 55 | case APP_MODE_IOTA_STARDUST: 56 | // iota 57 | api.bip32_path[BIP32_COIN_INDEX] = BIP32_COIN_IOTA; 58 | api.protocol = PROTOCOL_STARDUST; 59 | api.coin = COIN_IOTA; 60 | break; 61 | #elif defined(APP_SHIMMER) 62 | case APP_MODE_SHIMMER_CLAIMING: 63 | // iota 64 | api.bip32_path[BIP32_COIN_INDEX] = BIP32_COIN_IOTA; 65 | api.protocol = PROTOCOL_STARDUST; 66 | api.coin = COIN_SHIMMER; 67 | break; 68 | case APP_MODE_SHIMMER: 69 | // shimmer 70 | api.bip32_path[BIP32_COIN_INDEX] = BIP32_COIN_SHIMMER; 71 | api.protocol = PROTOCOL_STARDUST; 72 | api.coin = COIN_SHIMMER; 73 | break; 74 | #else 75 | #error unknown app 76 | #endif 77 | default: 78 | THROW(SW_ACCOUNT_NOT_VALID); 79 | } 80 | 81 | // set bip paths for testnet 82 | if (app_mode & 0x80) { 83 | api.bip32_path[BIP32_COIN_INDEX] = BIP32_COIN_TESTNET; 84 | } 85 | 86 | // set account indices 87 | // value of 0 is allowed because it tells us that no account was set 88 | // through the api and it is checked in every api function that uses 89 | // account indices. 90 | api.bip32_path[BIP32_ACCOUNT_INDEX] = account_index; 91 | 92 | api.app_mode = app_mode; 93 | } 94 | 95 | uint32_t api_reset() 96 | { 97 | // also resets the account index 98 | api_initialize(APP_MODE_INIT, 0); 99 | 100 | ui_reset(); 101 | 102 | io_send(NULL, 0, SW_OK); 103 | return 0; 104 | } 105 | 106 | 107 | void api_clear_data() 108 | { 109 | #ifdef APP_DEBUG 110 | // save the non-interactive flag if compiled in DEBUG mode 111 | uint8_t tmp_non_interactive = api.non_interactive_mode; 112 | #endif 113 | 114 | // initialize with previously set app-mode and account index 115 | api_initialize(api.app_mode, api.bip32_path[BIP32_ACCOUNT_INDEX]); 116 | 117 | #ifdef APP_DEBUG 118 | // restore non-interactive flag 119 | api.non_interactive_mode = tmp_non_interactive; 120 | #endif 121 | } 122 | 123 | uint32_t api_write_data_block(uint8_t block_number, const uint8_t *input_data, 124 | uint32_t len) 125 | { 126 | // only allow write on empty buffer 127 | if (api.data.type != EMPTY) { 128 | THROW(SW_COMMAND_NOT_ALLOWED); 129 | } 130 | // only accept payload with exactly data_block_size length 131 | if (len != DATA_BLOCK_SIZE) { 132 | THROW(SW_INCORRECT_LENGTH); 133 | } 134 | 135 | // check if chunk-number [0..MAX_NUM_DATA_BLOCKS) 136 | if (block_number >= DATA_BLOCK_COUNT) { 137 | THROW(SW_INCORRECT_P1P2); 138 | } 139 | 140 | memcpy(&api.data.buffer[block_number * DATA_BLOCK_SIZE], input_data, 141 | DATA_BLOCK_SIZE); 142 | 143 | io_send(NULL, 0, SW_OK); 144 | 145 | return 0; 146 | } 147 | 148 | uint32_t api_read_data_block(uint8_t block_number) 149 | { 150 | if (api.data.type != GENERATED_ADDRESSES && api.data.type != SIGNATURES) { 151 | THROW(SW_COMMAND_NOT_ALLOWED); 152 | } 153 | 154 | // check if chunk-number [0..MAX_NUM_DATA_BLOCKS) 155 | if (block_number >= DATA_BLOCK_COUNT) { 156 | THROW(SW_INCORRECT_P1P2); 157 | } 158 | 159 | io_send(&api.data.buffer[block_number * DATA_BLOCK_SIZE], DATA_BLOCK_SIZE, 160 | SW_OK); 161 | 162 | return 0; 163 | } 164 | 165 | uint32_t api_get_data_buffer_state() 166 | { 167 | API_GET_DATA_BUFFER_STATE_RESPONSE resp; 168 | resp.data_length = api.data.length; 169 | resp.data_type = (uint8_t)api.data.type; 170 | resp.data_block_count = DATA_BLOCK_COUNT; 171 | resp.data_block_size = DATA_BLOCK_SIZE; 172 | 173 | io_send(&resp, sizeof(resp), SW_OK); 174 | return 0; 175 | } 176 | 177 | uint32_t api_clear_data_buffer() 178 | { 179 | // wipe all including other api-flags 180 | api_clear_data(); 181 | 182 | io_send(NULL, 0, SW_OK); 183 | return 0; 184 | } 185 | 186 | // get application configuration (flags and version) 187 | uint32_t api_get_app_config(uint8_t is_locked) 188 | { 189 | API_GET_APP_CONFIG_RESPONSE resp; 190 | resp.app_version_major = APPVERSION_MAJOR; 191 | resp.app_version_minor = APPVERSION_MINOR; 192 | resp.app_version_patch = APPVERSION_PATCH; 193 | 194 | // bit 0: locked 195 | // bit 1: blindsigning enabled 196 | // bit 2: IOTA / Shimmer app 197 | resp.app_flags = !!is_locked; 198 | resp.app_flags |= !!nv_get_blindsigning() << 1; 199 | 200 | #if defined(APP_IOTA) 201 | // actually not needed because bit is cleared anyways 202 | resp.app_flags &= ~(1 << 2); 203 | #elif defined(APP_SHIMMER) 204 | resp.app_flags |= (1 << 2); 205 | #else 206 | #error unknown app 207 | #endif 208 | 209 | #if defined(TARGET_NANOX) 210 | resp.device = 1; 211 | #elif defined(TARGET_NANOS2) 212 | resp.device = 2; 213 | #else 214 | resp.device = 0; 215 | #endif 216 | 217 | #ifdef APP_DEBUG 218 | resp.debug = 1; 219 | #else 220 | resp.debug = 0; 221 | #endif 222 | 223 | io_send(&resp, sizeof(resp), SW_OK); 224 | return 0; 225 | } 226 | 227 | 228 | uint32_t api_show_flow() 229 | { 230 | io_send(NULL, 0, SW_OK); 231 | return 0; 232 | } 233 | 234 | uint32_t api_set_account(uint8_t app_mode, const uint8_t *data, uint32_t len) 235 | { 236 | // check if a uint32_t was sent as data 237 | if (len != sizeof(uint32_t)) { 238 | THROW(SW_INCORRECT_LENGTH); 239 | } 240 | 241 | if ((app_mode & 0x7f) > APP_MODE_SHIMMER) { 242 | THROW(SW_INCORRECT_P1P2); 243 | } 244 | 245 | uint32_t tmp_bip32_account; 246 | memcpy(&tmp_bip32_account, data, sizeof(uint32_t)); 247 | 248 | // valid bip32_account? MSB must be set 249 | if (!(tmp_bip32_account & 0x80000000)) { 250 | THROW(SW_COMMAND_INVALID_DATA); 251 | } 252 | 253 | // delete all data, set app_mode and account index 254 | api_initialize(app_mode, tmp_bip32_account); 255 | 256 | io_send(NULL, 0, SW_OK); 257 | 258 | return 0; 259 | } 260 | 261 | // callback for acknowledging a new (remainder) address 262 | static void api_generate_address_accepted() 263 | { 264 | // in interactive flows set data_type here, so data is not readable 265 | // before acknowleding 266 | api.data.type = GENERATED_ADDRESSES; 267 | io_send(NULL, 0, SW_OK); 268 | } 269 | 270 | // callback for timeout 271 | static void api_generate_address_timeout() 272 | { 273 | api_clear_data(); 274 | io_send(NULL, 0, SW_COMMAND_TIMEOUT); 275 | } 276 | 277 | uint32_t api_generate_address(uint8_t show_on_screen, const uint8_t *data, 278 | uint32_t len) 279 | { 280 | // don't allow command if an interactive flow already is running 281 | if (api.flow_locked) { 282 | THROW(SW_COMMAND_NOT_ALLOWED); 283 | } 284 | // was account selected? 285 | if (!(api.bip32_path[BIP32_ACCOUNT_INDEX] & 0x80000000)) { 286 | THROW(SW_ACCOUNT_NOT_VALID); 287 | } 288 | 289 | // if buffer contains data, throw exception 290 | if (api.data.type != EMPTY) { 291 | THROW(SW_COMMAND_NOT_ALLOWED); 292 | } 293 | 294 | // disable external read and write access before doing anything else 295 | api.data.type = LOCKED; 296 | 297 | if (len != sizeof(API_GENERATE_ADDRESS_REQUEST)) { 298 | THROW(SW_INCORRECT_LENGTH); 299 | } 300 | 301 | API_GENERATE_ADDRESS_REQUEST req; 302 | memcpy(&req, data, sizeof(req)); 303 | 304 | // check if too many addresses to generate 305 | if (req.count > API_GENERATE_ADDRESSES_MAX_COUNT) { 306 | THROW(SW_COMMAND_INVALID_DATA); 307 | } 308 | 309 | // if new address will be shown in a flow, only allow a single address 310 | // this mode is used for generating new remainder addresses and show 311 | // it to the user to review 312 | if (show_on_screen && req.count != 1) { 313 | THROW(SW_COMMAND_INVALID_DATA); 314 | } 315 | 316 | // check if MSBs set 317 | if (!(req.bip32_index & 0x80000000) || !(req.bip32_change & 0x80000000)) { 318 | THROW(SW_COMMAND_INVALID_DATA); 319 | } 320 | 321 | // bip32 change can be 0x80000000 or 0x80000001 322 | if (req.bip32_change & 0x7ffffffe) { 323 | THROW(SW_COMMAND_INVALID_DATA); 324 | } 325 | 326 | // check if there would be an overflow when generating addresses 327 | if (!((req.bip32_index + req.count) & 0x80000000)) { 328 | THROW(SW_COMMAND_INVALID_DATA); 329 | } 330 | 331 | // show "generating addresses ..." 332 | if (!show_on_screen) { 333 | flow_generating_addresses(); 334 | } 335 | 336 | api.bip32_path[BIP32_ADDRESS_INDEX] = req.bip32_index; 337 | api.bip32_path[BIP32_CHANGE_INDEX] = req.bip32_change; 338 | 339 | // wipe all data before buffer is used again 340 | memset(api.data.buffer, 0, API_BUFFER_SIZE_BYTES); 341 | for (uint32_t i = 0; i < req.count; i++) { 342 | // with address_type 343 | uint8_t ret = address_generate( 344 | api.bip32_path, BIP32_PATH_LEN, 345 | &api.data.buffer[i * ADDRESS_WITH_TYPE_SIZE_BYTES]); 346 | 347 | if (!ret) { 348 | THROW(SW_UNKNOWN); 349 | } 350 | // generate next address 351 | api.bip32_path[BIP32_ADDRESS_INDEX]++; 352 | } 353 | 354 | api.data.length = req.count * ADDRESS_WITH_TYPE_SIZE_BYTES; 355 | 356 | if (!show_on_screen || 357 | #ifdef APP_DEBUG 358 | api.non_interactive_mode 359 | #else 360 | false 361 | #endif 362 | ) { 363 | api.data.type = GENERATED_ADDRESSES; 364 | io_send(NULL, 0, SW_OK); 365 | return 0; 366 | } 367 | 368 | api.essence.outputs_count = 1; 369 | api.essence.remainder_bip32.bip32_index = req.bip32_index; 370 | api.essence.remainder_bip32.bip32_change = req.bip32_change; 371 | api.essence.remainder_index = 0; 372 | 373 | // reset the address index to the original value 374 | api.bip32_path[BIP32_ADDRESS_INDEX] = req.bip32_index; 375 | api.bip32_path[BIP32_CHANGE_INDEX] = req.bip32_change; 376 | 377 | api.flow_locked = 1; // mark flow locked 378 | 379 | flow_start_new_address(&api, api_generate_address_accepted, 380 | api_generate_address_timeout); 381 | return IO_ASYNCH_REPLY; 382 | } 383 | 384 | uint32_t api_prepare_signing(uint8_t has_remainder, const uint8_t *data, 385 | uint32_t len) 386 | { 387 | // when calling validation the buffer still is marked as empty 388 | if (api.data.type != EMPTY) { 389 | THROW(SW_COMMAND_NOT_ALLOWED); 390 | } 391 | 392 | // disable external read and write access before doing anything else 393 | api.data.type = LOCKED; 394 | 395 | // was account selected? 396 | if (!(api.bip32_path[BIP32_ACCOUNT_INDEX] & 0x80000000)) { 397 | THROW(SW_ACCOUNT_NOT_VALID); 398 | } 399 | 400 | if (len != sizeof(API_PREPARE_SIGNING_REQUEST)) { 401 | THROW(SW_INCORRECT_LENGTH); 402 | } 403 | 404 | // if essence has an remainder, store the information about 405 | if (!!has_remainder) { 406 | // no remainder for claiming shimmer allowed 407 | if (api.app_mode == APP_MODE_SHIMMER_CLAIMING) { 408 | THROW(SW_COMMAND_INVALID_DATA); 409 | } 410 | 411 | API_PREPARE_SIGNING_REQUEST req; 412 | memcpy(&req, data, sizeof(req)); 413 | 414 | if (!(req.remainder_bip32_change & 0x80000000) || 415 | !(req.remainder_bip32_index & 0x80000000)) { 416 | THROW(SW_COMMAND_INVALID_DATA); 417 | } 418 | 419 | if ((api.protocol == PROTOCOL_STARDUST && 420 | req.remainder_index >= OUTPUTS_MAX_COUNT_STARDUST)) { 421 | THROW(SW_COMMAND_INVALID_DATA); 422 | } 423 | 424 | api.essence.has_remainder = 1; 425 | api.essence.remainder_index = req.remainder_index; 426 | api.essence.remainder_bip32.bip32_index = req.remainder_bip32_index; 427 | api.essence.remainder_bip32.bip32_change = req.remainder_bip32_change; 428 | } 429 | else { 430 | api.essence.has_remainder = 0; 431 | } 432 | 433 | // no blindsigning 434 | api.essence.blindsigning = 0; 435 | 436 | if (!essence_parse_and_validate(&api)) { 437 | THROW(SW_COMMAND_INVALID_DATA); 438 | } 439 | 440 | api.data.type = VALIDATED_ESSENCE; 441 | 442 | io_send(NULL, 0, SW_OK); 443 | return 0; 444 | } 445 | 446 | uint32_t api_prepare_blindsigning() 447 | { 448 | // when calling validation the buffer still is marked as empty 449 | if (api.data.type != EMPTY) { 450 | THROW(SW_COMMAND_NOT_ALLOWED); 451 | } 452 | 453 | // blindsigning only allowed with stardust protocol but not SMR claiming 454 | if (api.protocol != PROTOCOL_STARDUST || 455 | api.app_mode == APP_MODE_SHIMMER_CLAIMING) { 456 | THROW(SW_COMMAND_NOT_ALLOWED); 457 | } 458 | 459 | // disable external read and write access before doing anything else 460 | api.data.type = LOCKED; 461 | 462 | // was account selected? 463 | if (!(api.bip32_path[BIP32_ACCOUNT_INDEX] & 0x80000000)) { 464 | THROW(SW_ACCOUNT_NOT_VALID); 465 | } 466 | 467 | // set flag for blindsigning 468 | api.essence.blindsigning = 1; 469 | 470 | // we allow to prepare without blindsigning enabled but the user will only 471 | // get an error message that blindsigning is not enabled on the Nano when 472 | // trying to sign what is the most consistent behaviour because the outcome 473 | // is the same as rejecting the signing (the flow only has a reject button 474 | // in this case and accepting is not possible) and we don't have to cope 475 | // with additional errors. 476 | if (!parse_and_validate_blindsigning(&api)) { 477 | THROW(SW_COMMAND_INVALID_DATA); 478 | } 479 | 480 | api.data.type = VALIDATED_ESSENCE; 481 | 482 | io_send(NULL, 0, SW_OK); 483 | return 0; 484 | } 485 | 486 | // callback for accept transaction 487 | static void api_user_confirm_essence_accepted() 488 | { 489 | api.data.type = USER_CONFIRMED_ESSENCE; 490 | io_send(NULL, 0, SW_OK); 491 | } 492 | 493 | // callback for rejected transaction 494 | static void api_user_confirm_essence_rejected() 495 | { 496 | api_clear_data(); 497 | io_send(NULL, 0, SW_DENIED_BY_USER); 498 | } 499 | 500 | // callback for timeout 501 | static void api_user_confirm_essence_timeout() 502 | { 503 | api_clear_data(); 504 | io_send(NULL, 0, SW_COMMAND_TIMEOUT); 505 | } 506 | 507 | uint32_t api_user_confirm_essence() 508 | { 509 | // don't allow command if an interactive flow already is running 510 | if (api.flow_locked) { 511 | THROW(SW_COMMAND_NOT_ALLOWED); 512 | } 513 | 514 | if (api.data.type != VALIDATED_ESSENCE) { 515 | THROW(SW_COMMAND_NOT_ALLOWED); 516 | } 517 | 518 | // was account selected? 519 | if (!(api.bip32_path[BIP32_ACCOUNT_INDEX] & 0x80000000)) { 520 | THROW(SW_ACCOUNT_NOT_VALID); 521 | } 522 | 523 | if (!api.essence.blindsigning) { 524 | // normal flow without blindsigning 525 | 526 | // some basic checks - actually data should be 100% validated 527 | // already 528 | if (api.data.length >= API_BUFFER_SIZE_BYTES || 529 | api.essence.length >= API_BUFFER_SIZE_BYTES || 530 | api.data.length < api.essence.length || 531 | api.essence.inputs_count < INPUTS_MIN_COUNT || 532 | (api.protocol == PROTOCOL_STARDUST && 533 | api.essence.inputs_count > INPUTS_MAX_COUNT_STARDUST)) { 534 | THROW(SW_UNKNOWN); 535 | } 536 | 537 | // set correct bip32 path for showing the remainder address on the 538 | // UI 539 | api.bip32_path[BIP32_ADDRESS_INDEX] = 540 | api.essence.remainder_bip32.bip32_index; 541 | api.bip32_path[BIP32_CHANGE_INDEX] = 542 | api.essence.remainder_bip32.bip32_change; 543 | 544 | #ifdef APP_DEBUG 545 | if (api.non_interactive_mode) { 546 | api.data.type = USER_CONFIRMED_ESSENCE; 547 | 548 | io_send(NULL, 0, SW_OK); 549 | return 0; 550 | } 551 | #endif 552 | api.flow_locked = 1; // mark flow locked 553 | 554 | flow_start_user_confirm_transaction(&api, 555 | &api_user_confirm_essence_accepted, 556 | &api_user_confirm_essence_rejected, 557 | &api_user_confirm_essence_timeout); 558 | } 559 | else { 560 | // start flow for blindsigning 561 | #ifdef APP_DEBUG 562 | if (api.non_interactive_mode) { 563 | api.data.type = USER_CONFIRMED_ESSENCE; 564 | 565 | io_send(NULL, 0, SW_OK); 566 | return 0; 567 | } 568 | #endif 569 | 570 | api.flow_locked = 1; // mark flow locked 571 | 572 | // we can use the same callbacks 573 | flow_start_blindsigning(&api, &api_user_confirm_essence_accepted, 574 | &api_user_confirm_essence_rejected, 575 | &api_user_confirm_essence_timeout); 576 | } 577 | return IO_ASYNCH_REPLY; 578 | } 579 | 580 | // prefered signing methond on the nano-s 581 | // it needs as many calls as there are inputs but needs 582 | // no additional memory in the buffer for signatures 583 | uint32_t api_sign(uint8_t p1) 584 | { 585 | if (api.data.type != USER_CONFIRMED_ESSENCE) { 586 | THROW(SW_COMMAND_NOT_ALLOWED); 587 | } 588 | 589 | // some basic checks - actually data is 100% validated already 590 | if (api.data.length >= API_BUFFER_SIZE_BYTES || 591 | api.essence.length >= API_BUFFER_SIZE_BYTES || 592 | api.data.length < api.essence.length || 593 | api.essence.inputs_count < INPUTS_MIN_COUNT || 594 | (api.protocol == PROTOCOL_STARDUST && 595 | api.essence.inputs_count > INPUTS_MAX_COUNT_STARDUST)) { 596 | THROW(SW_UNKNOWN); 597 | } 598 | 599 | if (p1 >= api.essence.inputs_count) { 600 | THROW(SW_INCORRECT_P1P2); 601 | } 602 | 603 | // check the buffer size 604 | if (IO_APDU_BUFFER_SIZE < sizeof(SIGNATURE_UNLOCK_BLOCK)) { 605 | THROW(SW_UNKNOWN); 606 | } 607 | 608 | uint32_t signature_idx = p1; 609 | 610 | // show "signing ..." 611 | flow_signing(); 612 | 613 | uint8_t *output = io_get_buffer(); 614 | uint16_t signature_size_bytes = sign(&api, output, signature_idx); 615 | 616 | if (!signature_size_bytes) { 617 | THROW(SW_UNKNOWN); 618 | } 619 | 620 | io_send(output, signature_size_bytes, SW_OK); 621 | 622 | return 0; 623 | } 624 | 625 | #ifdef APP_DEBUG 626 | // function to verify the stack never grows into the bss segment 627 | // by dumping the whole memory and having a look at the stack section 628 | // in the dump. Also useful for figuring out how much empty memory after 629 | // the bss section is left. RAM is initialized by BOLOS with 0xa5. So 630 | // it's easy to see what memory never was written to 631 | // DON'T enable it in production 632 | uint32_t api_dump_memory(uint8_t pagenr) 633 | { 634 | #if defined(TARGET_NANOX) || defined(TARGET_NANOS2) 635 | // same size and location on Nano X and Nano S+ 636 | if (pagenr >= 30 * 1024 / 128) { 637 | THROW(SW_INCORRECT_P1P2); 638 | } 639 | uint32_t *p = (uint32_t *)(0xda7a0000 + pagenr * 128); 640 | #else 641 | // works for firmware 2.0.0 642 | if (pagenr >= (4096 + 512) / 128) { 643 | THROW(SW_INCORRECT_P1P2); 644 | } 645 | uint32_t *p = (uint32_t *)(0x20000200 + pagenr * 128); 646 | #endif 647 | io_send(p, 128, SW_OK); 648 | return 0; 649 | } 650 | 651 | // set non-interactive-mode for automatic testing via speculos 652 | uint32_t api_set_non_interactive_mode(uint8_t mode) 653 | { 654 | if (mode > 1) { 655 | THROW(SW_INCORRECT_P1P2); 656 | } 657 | api.non_interactive_mode = mode; 658 | 659 | io_send(NULL, 0, SW_OK); 660 | return 0; 661 | } 662 | 663 | #endif 664 | -------------------------------------------------------------------------------- /src/api.h: -------------------------------------------------------------------------------- 1 | #ifndef API_H 2 | #define API_H 3 | 4 | #include 5 | #include 6 | 7 | #include "os.h" 8 | 9 | #include "iota_io.h" 10 | 11 | #include "iota/address.h" 12 | 13 | #define IO_STRUCT struct __attribute__((packed, may_alias)) 14 | 15 | typedef enum { 16 | EMPTY = 0, 17 | GENERATED_ADDRESSES = 1, 18 | VALIDATED_ESSENCE = 2, 19 | USER_CONFIRMED_ESSENCE = 3, 20 | SIGNATURES = 4, 21 | LOCKED = 5, 22 | } DATA_TYPE; 23 | 24 | typedef enum { 25 | APP_MODE_IOTA_STARDUST = 1, 26 | APP_MODE_SHIMMER_CLAIMING = 2, 27 | APP_MODE_SHIMMER = 3 28 | } APP_MODE_TYPE; 29 | 30 | typedef enum { PROTOCOL_STARDUST = 1 } PROTOCOL_TYPE; 31 | 32 | typedef enum { COIN_IOTA = 0, COIN_SHIMMER = 1 } COIN_TYPE; 33 | 34 | typedef IO_STRUCT 35 | { 36 | uint8_t input_type; 37 | uint8_t transaction_id[TRANSACTION_ID_SIZE_BYTES]; 38 | uint16_t transaction_output_id; 39 | } 40 | UTXO_INPUT; 41 | 42 | typedef IO_STRUCT 43 | { 44 | // 0 enforced in validation 45 | uint8_t output_type; 46 | 47 | // 0 enorced in validation 48 | uint8_t address_type; 49 | 50 | uint8_t address[ADDRESS_SIZE_BYTES]; 51 | 52 | uint64_t amount; 53 | } 54 | SIG_LOCKED_SINGLE_OUTPUT; 55 | 56 | typedef IO_STRUCT 57 | { 58 | // 3 enforced in validation 59 | uint8_t output_type; 60 | 61 | uint64_t amount; 62 | 63 | // 0 enforced in validation 64 | uint8_t native_tokens_count; 65 | 66 | // "none" enforced in validation 67 | // NATIVE_TOKENS native_tokens 68 | 69 | // 1 enforced in validation 70 | uint8_t unlock_conditions_count; 71 | 72 | // 0 enforced in validation 73 | // (Address Unlock Condition) 74 | uint8_t unlock_condition_type; 75 | 76 | // 0 enforced in validation 77 | // (Ed25519 Address) 78 | uint8_t address_type; 79 | 80 | uint8_t pubkey_hash[ADDRESS_SIZE_BYTES]; 81 | 82 | // 0 enforced in validation 83 | uint8_t blocks_count; 84 | 85 | // "none" enforced in validation 86 | // BLOCKS blocks; 87 | } 88 | BASIC_OUTPUT; 89 | 90 | // used for blindisning 91 | typedef IO_STRUCT 92 | { 93 | uint8_t public_key[32]; 94 | uint8_t signature[64]; 95 | } 96 | SIGNATURE_BLOCK; 97 | 98 | typedef IO_STRUCT 99 | { 100 | uint8_t unlock_type; 101 | uint8_t signature_type; 102 | SIGNATURE_BLOCK signature; 103 | } 104 | SIGNATURE_UNLOCK_BLOCK; 105 | 106 | typedef IO_STRUCT 107 | { 108 | uint8_t unlock_type; 109 | uint16_t reference; 110 | } 111 | REFERENCE_UNLOCK_BLOCK; 112 | 113 | // --- request and response structures --- 114 | typedef IO_STRUCT 115 | { 116 | uint32_t bip32_index; 117 | uint32_t bip32_change; 118 | uint32_t count; 119 | } 120 | API_GENERATE_ADDRESS_REQUEST; 121 | 122 | 123 | typedef IO_STRUCT 124 | { 125 | uint8_t app_version_major; 126 | uint8_t app_version_minor; 127 | uint8_t app_version_patch; 128 | uint8_t app_flags; 129 | uint8_t device; 130 | uint8_t debug; 131 | } 132 | API_GET_APP_CONFIG_RESPONSE; 133 | 134 | typedef IO_STRUCT 135 | { 136 | uint16_t data_length; 137 | uint8_t data_type; 138 | uint8_t data_block_size; 139 | uint8_t data_block_count; 140 | } 141 | API_GET_DATA_BUFFER_STATE_RESPONSE; 142 | 143 | typedef IO_STRUCT 144 | { 145 | uint16_t remainder_index; 146 | uint32_t remainder_bip32_index; 147 | uint32_t remainder_bip32_change; 148 | } 149 | API_PREPARE_SIGNING_REQUEST; 150 | 151 | typedef IO_STRUCT 152 | { 153 | uint32_t bip32_index; 154 | uint32_t bip32_change; 155 | } 156 | API_INPUT_BIP32_INDEX; 157 | 158 | // same struct with different name 159 | typedef API_INPUT_BIP32_INDEX API_REMAINDER_BIP32_INDEX; 160 | 161 | // --- api-struct --- 162 | typedef struct { 163 | // number of valid inputs 164 | uint16_t inputs_count; 165 | 166 | // size of usable data in bytes 167 | uint16_t length; 168 | 169 | // pointer to inputs 170 | // don't use this directly because data is unaligned to save space 171 | UTXO_INPUT *inputs; 172 | 173 | // number of valid outputs 174 | uint16_t outputs_count; 175 | 176 | // pointer to outputs 177 | // don't use this directly because data is unaligned to save space 178 | uint8_t *outputs; 179 | 180 | // pointer to BIP32 array for input addresses 181 | // don't use this directly because data is unaligned to save space 182 | API_INPUT_BIP32_INDEX *inputs_bip32_index; 183 | 184 | // flag for blindsigning 185 | uint8_t blindsigning; 186 | 187 | uint16_t remainder_index; 188 | API_REMAINDER_BIP32_INDEX remainder_bip32; 189 | 190 | // flag that signals if essence has a remainder 191 | uint8_t has_remainder; 192 | 193 | // flag that signals that it's a sweeping transaction 194 | uint8_t is_internal_transfer; 195 | 196 | // hash of the essence 197 | uint8_t hash[BLAKE2B_SIZE_BYTES]; 198 | } ESSENCE; 199 | 200 | typedef struct { 201 | // block of input data 202 | uint8_t buffer[API_BUFFER_SIZE_BYTES]; 203 | 204 | // type of data in buffer 205 | DATA_TYPE type; 206 | 207 | // length of data in buffer 208 | uint16_t length; 209 | } API_DATA; 210 | 211 | typedef struct { 212 | /// primary BIP32 path used for seed derivation 213 | uint32_t bip32_path[BIP32_PATH_LEN]; 214 | 215 | // app_mode 216 | APP_MODE_TYPE app_mode; 217 | 218 | // protocol version 219 | PROTOCOL_TYPE protocol; 220 | 221 | // coin type 222 | COIN_TYPE coin; 223 | 224 | // buffer for api 225 | API_DATA data; 226 | 227 | // parsed essence 228 | ESSENCE essence; 229 | 230 | // 0 = interactive flows may be started 231 | // 1 = flows may not be started until reset 232 | // flag gets set when starting a flow and reset when the databuffer 233 | // is cleared 234 | int8_t flow_locked; 235 | 236 | #ifdef APP_DEBUG 237 | uint8_t non_interactive_mode; 238 | #endif 239 | 240 | } API_CTX; 241 | 242 | /// global context with everything related to the current api state 243 | // extern API_CTX api; 244 | 245 | /** @brief Clear and initialize the entire API context. */ 246 | void api_initialize(APP_MODE_TYPE app_mode, uint32_t account_index); 247 | 248 | // get application configuration (flags and version) 249 | uint32_t api_get_app_config(uint8_t is_locked); 250 | 251 | // set account index 252 | uint32_t api_set_account(uint8_t app_mode, const uint8_t *data, uint32_t len); 253 | 254 | // reset api 255 | uint32_t api_reset(void); 256 | 257 | uint32_t api_show_flow(void); 258 | 259 | // write block of data to device memory 260 | uint32_t api_write_data_block(uint8_t block_number, const uint8_t *input_data, 261 | uint32_t len); 262 | 263 | // read block of data from device memory 264 | uint32_t api_read_data_block(uint8_t block_number); 265 | 266 | // get the state of the data buffeer 267 | uint32_t api_get_data_buffer_state(void); 268 | 269 | // clear data 270 | void api_clear_data(void); 271 | 272 | // clear data buffer 273 | uint32_t api_clear_data_buffer(void); 274 | 275 | uint32_t api_prepare_signing(uint8_t has_remainder, const uint8_t *data, 276 | uint32_t len); 277 | 278 | uint32_t api_prepare_blindsigning(void); 279 | 280 | uint32_t api_user_confirm_essence(void); 281 | 282 | uint32_t api_sign(uint8_t p1); 283 | 284 | uint32_t api_generate_address(uint8_t show_on_screen, const uint8_t *data, 285 | uint32_t len); 286 | 287 | #ifdef APP_DEBUG 288 | uint32_t api_dump_memory(uint8_t pagenr); 289 | uint32_t api_set_non_interactive_mode(uint8_t mode); 290 | #endif 291 | 292 | 293 | #endif // API_H 294 | -------------------------------------------------------------------------------- /src/debugprintf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * debugprintf.c 3 | * 4 | * Created on: 15.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include "os.h" 12 | 13 | #include "debugprintf.h" 14 | 15 | 16 | #ifdef APP_DEBUG 17 | 18 | 19 | char G_debug_print_buf[DEBUG_BUFFER_SIZE]; 20 | 21 | #define max(a, b) (a > b) ? a : b 22 | #define min(a, b) (a < b) ? a : b 23 | 24 | void debug_write(char *buf) 25 | { 26 | asm volatile("movs r0, #0x04\n" 27 | "movs r1, %0\n" 28 | "svc 0xab\n" ::"r"(buf) 29 | : "r0", "r1"); 30 | } 31 | 32 | 33 | static void _bin2hex(uint32_t v, char *hex, int n) 34 | { 35 | if (n < 1) { 36 | return; 37 | } 38 | for (int i = 0; i < n; i++) { 39 | uint32_t c = v & 0xf; 40 | if (c >= 0 && c <= 9) { 41 | // digit 42 | hex[((n - 1) - i)] = '0' + c; 43 | } 44 | else { 45 | hex[((n - 1) - i)] = 'a' + c - 10; 46 | // hex 47 | } 48 | v >>= 4; 49 | } 50 | } 51 | 52 | static void bin2hex(uint8_t bin, char *hex) 53 | { 54 | _bin2hex(bin, hex, 2); 55 | } 56 | 57 | static void *fnGetSP(void) 58 | { 59 | volatile unsigned long var = 0; 60 | return (void *)((unsigned long)&var + 4); 61 | } 62 | 63 | void debug_print_sp() 64 | { 65 | uint32_t sp = (uint32_t)fnGetSP(); 66 | memset(G_debug_print_buf, 0, sizeof(G_debug_print_buf)); 67 | _bin2hex(sp, G_debug_print_buf, 8); 68 | debug_write(G_debug_print_buf); 69 | } 70 | 71 | void debug_print_hex(const uint8_t *data, int size, int b) 72 | { 73 | int chunks = size / b; 74 | if (size % b) { 75 | chunks++; 76 | } 77 | for (int i = 0; i < chunks; i++) { 78 | int idx = 0; 79 | memset(G_debug_print_buf, 0, sizeof(G_debug_print_buf)); 80 | 81 | int chunk = min(size, b); 82 | for (int j = 0; j < chunk; j++) { 83 | if (idx >= (int)sizeof(G_debug_print_buf)) { 84 | return; 85 | } 86 | bin2hex((uint32_t)*data++, &G_debug_print_buf[idx]); 87 | idx += 2; 88 | if (j != chunk - 1) { 89 | G_debug_print_buf[idx++] = ' '; 90 | } 91 | } 92 | debug_write(G_debug_print_buf); 93 | size -= chunk; 94 | } 95 | } 96 | #endif 97 | -------------------------------------------------------------------------------- /src/debugprintf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * debugprintf.h 3 | * 4 | * Created on: 15.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #ifndef SRC_DEBUGPRINTF_H_ 9 | #define SRC_DEBUGPRINTF_H_ 10 | 11 | //#define APP_DEBUG 12 | 13 | #ifdef APP_DEBUG 14 | 15 | #include 16 | 17 | #define DEBUG_BUFFER_SIZE 50 18 | extern char G_debug_print_buf[DEBUG_BUFFER_SIZE]; 19 | 20 | void debug_write(char *buf); 21 | 22 | void debug_print_sp(void); 23 | 24 | // use define to avoid vsnprintf (would need _sbrk what is dynamic memory 25 | // allocation) 26 | #define debug_printf(fmt, ...) \ 27 | { \ 28 | snprintf(G_debug_print_buf, sizeof(G_debug_print_buf) - 3, fmt, \ 29 | ##__VA_ARGS__); \ 30 | debug_write(G_debug_print_buf); \ 31 | } 32 | 33 | void debug_print_hex(const uint8_t *data, int size, int b); 34 | 35 | #endif 36 | #endif /* SRC_DEBUGPRINTF_H_ */ 37 | -------------------------------------------------------------------------------- /src/iota/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | -------------------------------------------------------------------------------- /src/iota/abstraction.c: -------------------------------------------------------------------------------- 1 | // methods that abstract from the used protocol 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "os.h" 8 | 9 | #include "essence_stardust.h" 10 | #include "abstraction.h" 11 | #include "ui_common.h" 12 | 13 | #include "macros.h" 14 | #include "api.h" 15 | #include "iota_io.h" 16 | #include "iota/address.h" 17 | 18 | #pragma GCC diagnostic error "-Wall" 19 | #pragma GCC diagnostic error "-Wextra" 20 | #pragma GCC diagnostic error "-Wmissing-prototypes" 21 | 22 | const uint8_t *get_output_address_ptr(const API_CTX *api, uint8_t index) 23 | { 24 | MUST(index < api->essence.outputs_count); 25 | 26 | uint8_t *ret = 0; 27 | 28 | switch (api->protocol) { 29 | case PROTOCOL_STARDUST: { 30 | BASIC_OUTPUT *tmp = (BASIC_OUTPUT *)api->essence.outputs; 31 | // address follows the address_type in a pact struct 32 | ret = &tmp[index].address_type; 33 | break; 34 | } 35 | default: 36 | THROW(SW_UNKNOWN); 37 | break; 38 | } 39 | return ret; 40 | } 41 | 42 | uint64_t get_output_amount(const API_CTX *api, uint8_t index) 43 | { 44 | MUST(index < api->essence.outputs_count); 45 | 46 | uint64_t amount = 0; 47 | 48 | switch (api->protocol) { 49 | case PROTOCOL_STARDUST: { 50 | BASIC_OUTPUT *tmp = (BASIC_OUTPUT *)api->essence.outputs; 51 | memcpy(&amount, &tmp[index].amount, sizeof(uint64_t)); 52 | break; 53 | } 54 | default: 55 | THROW(SW_UNKNOWN); 56 | break; 57 | } 58 | return amount; 59 | } 60 | 61 | uint8_t address_encode_bech32(const API_CTX *api, const uint8_t *addr_with_type, 62 | char *bech32, uint32_t bech32_max_length) 63 | { 64 | switch (api->coin) { 65 | case COIN_IOTA: { 66 | MUST(address_encode_bech32_hrp( 67 | addr_with_type, bech32, bech32_max_length, 68 | (api->app_mode & 0x80) ? COIN_HRP_IOTA_TESTNET : COIN_HRP_IOTA, 69 | strlen(COIN_HRP_IOTA))); // strlen valid because HRP has the same 70 | // length in testnet 71 | break; 72 | } 73 | case COIN_SHIMMER: { 74 | MUST(address_encode_bech32_hrp( 75 | addr_with_type, bech32, bech32_max_length, 76 | (api->app_mode & 0x80) ? COIN_HRP_SHIMMER_TESTNET 77 | : COIN_HRP_SHIMMER, 78 | strlen(COIN_HRP_SHIMMER))); // strlen valid because HRP has the same 79 | // length in testnet 80 | break; 81 | } 82 | default: 83 | THROW(SW_UNKNOWN); 84 | break; 85 | } 86 | return 1; 87 | } 88 | 89 | 90 | uint8_t essence_parse_and_validate(API_CTX *api) 91 | { 92 | switch (api->protocol) { 93 | case PROTOCOL_STARDUST: { 94 | MUST(essence_parse_and_validate_stardust(api)); 95 | break; 96 | } 97 | default: 98 | THROW(SW_UNKNOWN); 99 | break; 100 | } 101 | return 1; 102 | } 103 | 104 | uint8_t get_amount(const API_CTX *api, int index, char *dst, size_t dst_len) 105 | { 106 | uint64_t amount; 107 | 108 | // amount > 0 enforced by validation 109 | MUST(amount = get_output_amount(api, index)); 110 | 111 | switch (api->coin) { 112 | case COIN_IOTA: { 113 | format_value_full_decimals(dst, dst_len, amount); 114 | break; 115 | } 116 | case COIN_SHIMMER: { 117 | format_value_full_decimals(dst, dst_len, amount); 118 | break; 119 | } 120 | default: 121 | THROW(SW_UNKNOWN); 122 | } 123 | return 1; 124 | } 125 | -------------------------------------------------------------------------------- /src/iota/abstraction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "api.h" 6 | 7 | const uint8_t *get_output_address_ptr(const API_CTX *api, uint8_t index); 8 | 9 | uint64_t get_output_amount(const API_CTX *api, uint8_t index); 10 | 11 | uint8_t address_encode_bech32(const API_CTX *api, const uint8_t *addr_with_type, 12 | char *bech32, uint32_t bech32_max_length); 13 | 14 | uint8_t essence_parse_and_validate(API_CTX *api); 15 | 16 | uint8_t get_amount(const API_CTX *api, int index, char *dst, size_t dst_len); 17 | -------------------------------------------------------------------------------- /src/iota/address.c: -------------------------------------------------------------------------------- 1 | /* 2 | * address.c 3 | * 4 | * Created on: 16.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #include 9 | 10 | #include "os.h" 11 | #include "cx.h" 12 | #include "api.h" 13 | #include "bech32.h" 14 | #include "address.h" 15 | #include "ed25519.h" 16 | #include "macros.h" 17 | #include "lib_standard_app/crypto_helpers.h" 18 | 19 | #pragma GCC diagnostic error "-Wall" 20 | #pragma GCC diagnostic error "-Wextra" 21 | #pragma GCC diagnostic error "-Wmissing-prototypes" 22 | 23 | //#include "debugprintf.h" 24 | 25 | uint8_t address_encode_bech32_hrp(const uint8_t *addr_with_type, char *bech32, 26 | uint32_t bech32_max_length, const char *hrp, 27 | const size_t hrp_len) 28 | { 29 | // at least this space is needed - bech32_encode adds a zero-terminator 30 | // byte! 31 | if (bech32_max_length < ADDRESS_SIZE_BECH32_MAX + 1) 32 | return 0; 33 | 34 | uint32_t base32_length = ADDRESS_SIZE_BASE32; 35 | 36 | // encode address bytes to base32 37 | uint8_t base32[ADDRESS_SIZE_BASE32]; 38 | 39 | uint8_t ret = base32_encode(base32, &base32_length, addr_with_type, 40 | 1 + ADDRESS_SIZE_BYTES); 41 | MUST(ret); 42 | 43 | // and encode base32 to bech32 44 | uint32_t bech32_length = bech32_max_length; 45 | ret = bech32_encode(bech32, &bech32_length, hrp, hrp_len, base32, 46 | base32_length); 47 | MUST(ret); 48 | 49 | return 1; 50 | } 51 | 52 | uint8_t address_generate(uint32_t *bip32_path, uint32_t bip32_path_length, 53 | uint8_t *addr) 54 | { 55 | uint8_t raw_pubkey[65]; 56 | 57 | MUST(bip32_derive_with_seed_get_pubkey_256( 58 | HDW_ED25519_SLIP10, CX_CURVE_Ed25519, bip32_path, 59 | bip32_path_length, raw_pubkey, NULL, CX_SHA512, NULL, 0) == CX_OK); 60 | 61 | // convert Ledger pubkey to pubkey bytes 62 | uint8_t pubkey_bytes[PUBKEY_SIZE_BYTES]; 63 | 64 | MUST(ed25519_public_key_to_bytes(raw_pubkey, pubkey_bytes)); 65 | 66 | // debug_print_hex(pubkey_bytes, 32, 16); 67 | 68 | // set ed25519 address_type 69 | addr[0] = ADDRESS_TYPE_ED25519; 70 | 71 | cx_blake2b_t blake2b; 72 | 73 | MUST(cx_blake2b_init_no_throw(&blake2b, BLAKE2B_SIZE_BYTES * 8) == CX_OK); 74 | 75 | MUST(cx_hash_no_throw(&blake2b.header, CX_LAST, pubkey_bytes, 76 | PUBKEY_SIZE_BYTES, &addr[1], 77 | ADDRESS_SIZE_BYTES) == CX_OK); 78 | return 1; 79 | } 80 | -------------------------------------------------------------------------------- /src/iota/address.h: -------------------------------------------------------------------------------- 1 | /* 2 | * address.h 3 | * 4 | * Created on: 16.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | uint8_t address_encode_bech32_hrp(const uint8_t *addr, char *bech32, 13 | uint32_t bech32_max_length, const char *hrp, 14 | const size_t hrp_len); 15 | 16 | uint8_t address_generate(uint32_t *bip32_path, uint32_t bip32_path_length, 17 | uint8_t *addr); 18 | -------------------------------------------------------------------------------- /src/iota/bech32.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017 Pieter Wuille 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "bech32.h" 27 | 28 | #pragma GCC diagnostic error "-Wall" 29 | #pragma GCC diagnostic error "-Wextra" 30 | #pragma GCC diagnostic error "-Wmissing-prototypes" 31 | 32 | 33 | static uint32_t bech32_polymod_step(uint32_t pre) 34 | { 35 | uint8_t b = pre >> 25; 36 | return ((pre & 0x1FFFFFF) << 5) ^ (-((b >> 0) & 1) & 0x3b6a57b2UL) ^ 37 | (-((b >> 1) & 1) & 0x26508e6dUL) ^ (-((b >> 2) & 1) & 0x1ea119faUL) ^ 38 | (-((b >> 3) & 1) & 0x3d4233ddUL) ^ (-((b >> 4) & 1) & 0x2a1462b3UL); 39 | } 40 | 41 | #define charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 42 | 43 | int bech32_encode(char *const output, size_t *const out_len, 44 | const char *const hrp, const size_t hrp_len, 45 | const uint8_t *const data, const size_t data_len) 46 | { 47 | uint32_t chk = 1; 48 | size_t out_off = 0; 49 | const size_t out_len_max = *out_len; 50 | // hrp '1' data checksum 51 | const size_t final_out_len = hrp_len + data_len + 7; 52 | if (final_out_len > 108) 53 | return 0; 54 | // Note we want <=, to account for the null at the end of the string 55 | // i.e. equivalent to out_len_max < final_out_len + 1 56 | if (output == NULL || out_len_max <= final_out_len) 57 | return 0; 58 | if (hrp == NULL || hrp_len <= 0) 59 | return 0; 60 | if (data == NULL || data_len <= 0) 61 | return 0; 62 | for (size_t i = 0; i < hrp_len; ++i) { 63 | char ch = hrp[i]; 64 | if (!(33 <= ch && ch <= 126)) 65 | return 0; 66 | chk = bech32_polymod_step(chk) ^ (ch >> 5); 67 | } 68 | chk = bech32_polymod_step(chk); 69 | for (size_t i = 0; i < hrp_len; ++i) { 70 | char ch = hrp[i]; 71 | chk = bech32_polymod_step(chk) ^ (ch & 0x1f); 72 | output[out_off++] = ch; 73 | } 74 | output[out_off++] = '1'; 75 | for (size_t i = 0; i < data_len; ++i) { 76 | if (data[i] >> 5) 77 | return 0; 78 | chk = bech32_polymod_step(chk) ^ data[i]; 79 | output[out_off++] = charset[data[i]]; 80 | } 81 | for (size_t i = 0; i < 6; ++i) { 82 | chk = bech32_polymod_step(chk); 83 | } 84 | chk ^= 1; 85 | for (size_t i = 0; i < 6; ++i) { 86 | output[out_off++] = charset[(chk >> ((5 - i) * 5)) & 0x1f]; 87 | } 88 | output[out_off] = 0; 89 | *out_len = out_off; 90 | return (out_off == final_out_len); 91 | } 92 | 93 | int base32_encode(uint8_t *const out, size_t *out_len, const uint8_t *const in, 94 | const size_t in_len) 95 | { 96 | const int outbits = 5; 97 | const int inbits = 8; 98 | const int pad = 1; 99 | uint32_t val = 0; 100 | int bits = 0; 101 | size_t out_idx = 0; 102 | const size_t out_len_max = *out_len; 103 | const uint32_t maxv = (((uint32_t)1) << outbits) - 1; 104 | for (size_t inx_idx = 0; inx_idx < in_len; ++inx_idx) { 105 | val = (val << inbits) | in[inx_idx]; 106 | bits += inbits; 107 | while (bits >= outbits) { 108 | bits -= outbits; 109 | if (out_idx >= out_len_max) 110 | return 0; 111 | out[out_idx++] = (val >> bits) & maxv; 112 | } 113 | } 114 | if (pad) { 115 | if (bits) { 116 | if (out_idx >= out_len_max) 117 | return 0; 118 | out[out_idx++] = (val << (outbits - bits)) & maxv; 119 | } 120 | } 121 | else if (((val << (outbits - bits)) & maxv) || bits >= inbits) { 122 | return 0; 123 | } 124 | // Set out index 125 | *out_len = out_idx; 126 | return 1; 127 | } 128 | -------------------------------------------------------------------------------- /src/iota/bech32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bech32.h 3 | * 4 | * Created on: 16.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | int bech32_encode(char *const output, size_t *const out_len, 13 | const char *const hrp, const size_t hrp_len, 14 | const uint8_t *const data, const size_t data_len); 15 | 16 | int base32_encode(uint8_t *const out, size_t *out_len, const uint8_t *const in, 17 | const size_t in_len); 18 | -------------------------------------------------------------------------------- /src/iota/blindsigning_stardust.c: -------------------------------------------------------------------------------- 1 | /* 2 | * blindsigning.c 3 | * 4 | * Created on: 01.05.2022 5 | * Author: thomas 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include "os.h" 12 | #include "cx.h" 13 | #include "api.h" 14 | 15 | #include "macros.h" 16 | #include "blindsigning_stardust.h" 17 | 18 | #pragma GCC diagnostic error "-Wall" 19 | #pragma GCC diagnostic error "-Wextra" 20 | #pragma GCC diagnostic error "-Wmissing-prototypes" 21 | 22 | static inline uint8_t get_uint16(const uint8_t *data, uint32_t *idx, 23 | uint16_t *v) 24 | { 25 | MUST(*idx + sizeof(uint16_t) < API_BUFFER_SIZE_BYTES); 26 | memcpy(v, &data[*idx], 27 | sizeof(uint16_t)); // copy to avoid unaligned access 28 | *idx = *idx + sizeof(uint16_t); 29 | return 1; 30 | } 31 | 32 | 33 | // validate essence hash 34 | static uint8_t 35 | validate_essence_hash(const uint8_t *data, uint32_t *idx, 36 | uint8_t essence_hash[ESSENCE_HASH_SIZE_BYTES]) 37 | { 38 | MUST(*idx + ESSENCE_HASH_SIZE_BYTES < API_BUFFER_SIZE_BYTES); 39 | 40 | // not much we can validate here since an hash are arbitrary bytes 41 | // copy from buffer to essence 42 | memcpy(essence_hash, &data[*idx], ESSENCE_HASH_SIZE_BYTES); 43 | 44 | *idx = *idx + ESSENCE_HASH_SIZE_BYTES; 45 | return 1; 46 | } 47 | 48 | // validate if there are enough bip32 fragments 49 | static uint8_t 50 | validate_inputs_bip32(const uint8_t *data, uint32_t *idx, uint16_t inputs_count, 51 | API_INPUT_BIP32_INDEX **inputs_bip32_indices) 52 | { 53 | *inputs_bip32_indices = (API_INPUT_BIP32_INDEX *)&data[*idx]; 54 | // check if there are as many bip32-paths as inputs 55 | for (uint32_t i = 0; i < inputs_count; i++) { 56 | MUST(*idx + sizeof(API_INPUT_BIP32_INDEX) < API_BUFFER_SIZE_BYTES); 57 | 58 | API_INPUT_BIP32_INDEX tmp; 59 | memcpy(&tmp, &data[*idx], 60 | sizeof(API_INPUT_BIP32_INDEX)); // copy to avoid unaligned access 61 | 62 | // check is MSBs set 63 | MUST(tmp.bip32_index & 0x80000000); 64 | MUST(tmp.bip32_change & 0x80000000); 65 | 66 | *idx = *idx + sizeof(API_INPUT_BIP32_INDEX); 67 | } 68 | return 1; 69 | } 70 | 71 | 72 | uint8_t parse_and_validate_blindsigning(API_CTX *api) 73 | { 74 | uint32_t idx = 0; 75 | 76 | // parse data 77 | MUST(validate_essence_hash(api->data.buffer, &idx, api->essence.hash)); 78 | 79 | // save essence length 80 | api->essence.length = idx; 81 | 82 | MUST(get_uint16(api->data.buffer, &idx, &api->essence.inputs_count)); 83 | 84 | // Inputs Count must be 0 < x <= 128. 85 | // At least one input must be specified. 86 | MUST(api->essence.inputs_count >= INPUTS_MIN_COUNT && 87 | api->essence.inputs_count <= INPUTS_MAX_COUNT_STARDUST); 88 | 89 | // bip32 indices don't belong to the essence 90 | MUST(validate_inputs_bip32(api->data.buffer, &idx, 91 | api->essence.inputs_count, 92 | &api->essence.inputs_bip32_index)); 93 | 94 | // save data length 95 | api->data.length = idx; 96 | 97 | return 1; 98 | } 99 | -------------------------------------------------------------------------------- /src/iota/blindsigning_stardust.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "api.h" 4 | 5 | uint8_t parse_and_validate_blindsigning(API_CTX *api); -------------------------------------------------------------------------------- /src/iota/constants.h: -------------------------------------------------------------------------------- 1 | /* 2 | * constants.h 3 | * 4 | * Created on: 17.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "os.h" 11 | 12 | //----------------------------------------------------------------------------- 13 | 14 | #define NETWORK_MAINNET 1 15 | #define NETWORK_TESTNET 0 16 | 17 | 18 | #define TOTAL_AMOUNT_MAX 2779530283277761ull 19 | 20 | #define INPUTS_MAX_COUNT_STARDUST 128 21 | #define INPUTS_MIN_COUNT 1 22 | 23 | #define OUTPUTS_MAX_COUNT_STARDUST 128 24 | #define OUTPUTS_MIN_COUNT 1 25 | 26 | #define DATA_BLOCK_SIZE 251 // cla + ins + p1 + p2 + p3 + data max 256 bytes 27 | 28 | #if defined(TARGET_NANOX) || defined(TARGET_NANOS2) 29 | #define DATA_BLOCK_COUNT 32 // approx 8kB 30 | #else 31 | #define DATA_BLOCK_COUNT 6 // approx 1.5kB 32 | #endif 33 | 34 | #define API_BUFFER_SIZE_BYTES (DATA_BLOCK_COUNT * DATA_BLOCK_SIZE) 35 | 36 | #define BIP32_PATH_LEN 5 // fixed 37 | #define BIP32_COIN_INDEX 1 38 | #define BIP32_ACCOUNT_INDEX 2 39 | #define BIP32_CHANGE_INDEX 3 40 | #define BIP32_ADDRESS_INDEX 4 41 | 42 | #define BIP32_COIN_IOTA 0x8000107a 43 | #define BIP32_COIN_SHIMMER 0x8000107b 44 | #define BIP32_COIN_TESTNET 0x80000001 45 | 46 | #if defined(APP_IOTA) 47 | #define APP_MODE_INIT APP_MODE_IOTA_STARDUST 48 | #elif defined(APP_SHIMMER) 49 | #define APP_MODE_INIT APP_MODE_SHIMMER 50 | #else 51 | #error unknown app 52 | #endif 53 | 54 | 55 | // the address is the ed25519 pub-key 56 | #define ADDRESS_SIZE_BYTES 32 57 | #define ADDRESS_WITH_TYPE_SIZE_BYTES 33 58 | 59 | // the transaction_id is the blake2b hash of an address (without address-type) 60 | #define TRANSACTION_ID_SIZE_BYTES 32 61 | 62 | // size of pubkey 63 | #define PUBKEY_SIZE_BYTES 32 64 | 65 | // size of signature in bytes 66 | #define SIGNATURE_SIZE_BYTES 64 67 | 68 | // size of hash 69 | #define BLAKE2B_SIZE_BYTES 32 70 | #define ESSENCE_HASH_SIZE_BYTES 32 71 | 72 | // address type of ED25519 addresses 73 | #define ADDRESS_TYPE_ED25519 0 74 | 75 | // signature type of ED25519 signature 76 | #define SIGNATURE_TYPE_ED25519 0 77 | 78 | // unlock-block types 79 | #define UNLOCK_TYPE_SIGNATURE 0 80 | #define UNLOCK_TYPE_REFERENCE 1 81 | 82 | // stardust unlock types 83 | #define ADDRESS_UNLOCK_CONDITION 0 84 | 85 | // input types 86 | #define INPUT_TYPE_UTXO 0 87 | 88 | // output types 89 | #define OUTPUT_TYPE_SIGLOCKEDSINGLEOUTPUT 0 90 | #define OUTPUT_TYPE_BASICOUTPUT 3 91 | 92 | 93 | #define TRANSACTION_ESSENCE_TYPE_STARDUST 1 94 | 95 | // following constants are valid with bech32 encoding (address_type included) 96 | #define ADDRESS_SIZE_BASE32 ((ADDRESS_WITH_TYPE_SIZE_BYTES * 8 + 4) / 5) 97 | 98 | #define COIN_HRP_IOTA "iota" 99 | #define COIN_HRP_IOTA_TESTNET "atoi" 100 | #define COIN_HRP_SHIMMER "smr" 101 | #define COIN_HRP_SHIMMER_TESTNET "rms" 102 | 103 | #define ADDRESS_HRP_LENGTH_MAX 4 104 | 105 | // Ed25519-based addresses will result in a Bech32 string of 62/63 characters. 106 | #define ADDRESS_SIZE_BECH32_MAX \ 107 | (ADDRESS_HRP_LENGTH_MAX + 1 + ADDRESS_SIZE_BASE32 + 6) 108 | 109 | 110 | // API-constants 111 | #define API_GENERATE_ADDRESSES_MAX_COUNT \ 112 | (API_BUFFER_SIZE_BYTES / ADDRESS_WITH_TYPE_SIZE_BYTES) 113 | 114 | // very coarse estimation how many inputs the essence could have 115 | // we can safely assume that we need at least 32 bytes per input 116 | // this allows us to safe 100+ bytes in the essence struct 117 | #define API_MAX_SIGNATURE_TYPES (API_BUFFER_SIZE_BYTES / BLAKE2B_SIZE_BYTES) 118 | -------------------------------------------------------------------------------- /src/iota/ed25519.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "os.h" 4 | #include "cx.h" 5 | 6 | #include "macros.h" 7 | #include "ed25519.h" 8 | 9 | #include "constants.h" 10 | 11 | #include "debugprintf.h" 12 | 13 | #pragma GCC diagnostic error "-Wall" 14 | #pragma GCC diagnostic error "-Wextra" 15 | #pragma GCC diagnostic error "-Wmissing-prototypes" 16 | 17 | // reversing the public key and changing the last byte 18 | uint8_t ed25519_public_key_to_bytes(const uint8_t raw_pubkey[65], 19 | uint8_t output[32]) 20 | { 21 | for (int i = 0; i < 32; i++) { 22 | output[i] = raw_pubkey[64 - i]; 23 | } 24 | if (raw_pubkey[32] & 1) { 25 | output[31] |= 0x80; 26 | } 27 | return 1; 28 | } 29 | -------------------------------------------------------------------------------- /src/iota/ed25519.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "os.h" 4 | 5 | uint8_t ed25519_public_key_to_bytes(const uint8_t raw_pubkey[65], 6 | uint8_t output[32]); 7 | -------------------------------------------------------------------------------- /src/iota/essence_stardust.c: -------------------------------------------------------------------------------- 1 | /* 2 | * message_validator.c 3 | * 4 | * Created on: 15.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | // validation based on: 9 | // https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md 10 | 11 | #include 12 | #include 13 | 14 | #include "os.h" 15 | #include "cx.h" 16 | #include "api.h" 17 | 18 | #include "macros.h" 19 | #include "essence_stardust.h" 20 | #include "internal_transfer.h" 21 | 22 | #ifndef FUZZING 23 | #include "iota_io.h" 24 | #include "iota/ed25519.h" 25 | #endif 26 | 27 | #pragma GCC diagnostic error "-Wall" 28 | #pragma GCC diagnostic error "-Wextra" 29 | #pragma GCC diagnostic error "-Wmissing-prototypes" 30 | 31 | static inline uint8_t get_uint32(const uint8_t *data, uint32_t *idx, 32 | uint32_t *v) 33 | { 34 | MUST(*idx + sizeof(uint32_t) < API_BUFFER_SIZE_BYTES); 35 | memcpy(v, &data[*idx], 36 | sizeof(uint32_t)); // copy to avoid unaligned access 37 | *idx = *idx + sizeof(uint32_t); 38 | return 1; 39 | } 40 | static inline uint8_t get_uint16(const uint8_t *data, uint32_t *idx, 41 | uint16_t *v) 42 | { 43 | MUST(*idx + sizeof(uint16_t) < API_BUFFER_SIZE_BYTES); 44 | memcpy(v, &data[*idx], 45 | sizeof(uint16_t)); // copy to avoid unaligned access 46 | *idx = *idx + sizeof(uint16_t); 47 | return 1; 48 | } 49 | 50 | static inline uint8_t get_uint8(const uint8_t *data, uint32_t *idx, uint8_t *v) 51 | { 52 | MUST(*idx + sizeof(uint8_t) < API_BUFFER_SIZE_BYTES); 53 | *v = data[*idx]; 54 | *idx = *idx + sizeof(uint8_t); 55 | return 1; 56 | } 57 | 58 | 59 | // --- validate inputs --- 60 | static uint8_t validate_inputs(const uint8_t *data, uint32_t *idx, 61 | UTXO_INPUT **inputs_ptr, uint16_t *inputs_count) 62 | { 63 | // uses safe getter macro that returns an error in case of invalid access 64 | MUST(get_uint16(data, idx, inputs_count)); 65 | 66 | // Inputs Count must be 0 < x ≤ 128. 67 | // At least one input must be specified. 68 | MUST(*inputs_count >= INPUTS_MIN_COUNT && 69 | *inputs_count <= INPUTS_MAX_COUNT_STARDUST); 70 | 71 | *inputs_ptr = (UTXO_INPUT *)&data[*idx]; 72 | 73 | for (uint32_t i = 0; i < *inputs_count; i++) { 74 | MUST(*idx + sizeof(UTXO_INPUT) < API_BUFFER_SIZE_BYTES); 75 | 76 | UTXO_INPUT tmp; 77 | memcpy(&tmp, &data[*idx], 78 | sizeof(UTXO_INPUT)); // copy to avoid unaligned access 79 | 80 | // Input Type value must be 0, denoting an UTXO Input. 81 | MUST(tmp.input_type == INPUT_TYPE_UTXO); 82 | 83 | // Transaction Output Index must be 0 ≤ x < 128. 84 | MUST(/*tmp.transaction_output_id >= 0 &&*/ tmp.transaction_output_id < 85 | OUTPUTS_MAX_COUNT_STARDUST); 86 | 87 | *idx = *idx + sizeof(UTXO_INPUT); 88 | } 89 | return 1; 90 | } 91 | 92 | 93 | // --- validate outputs --- 94 | static uint8_t validate_outputs(const uint8_t *data, uint32_t *idx, 95 | BASIC_OUTPUT **outputs_ptr, 96 | uint16_t *outputs_count) 97 | { 98 | // uses safe getter macro that returns an error in case of invalid access 99 | MUST(get_uint16(data, idx, outputs_count)); 100 | 101 | // Outputs Count must be 0 < x ≤ 128. 102 | // At least one output must be specified. 103 | MUST(*outputs_count >= OUTPUTS_MIN_COUNT && 104 | *outputs_count <= OUTPUTS_MAX_COUNT_STARDUST); 105 | 106 | *outputs_ptr = (BASIC_OUTPUT *)&data[*idx]; 107 | uint64_t total_amount = 0ull; 108 | 109 | for (uint32_t i = 0; i < *outputs_count; i++) { 110 | MUST(*idx + sizeof(BASIC_OUTPUT) < API_BUFFER_SIZE_BYTES); 111 | 112 | BASIC_OUTPUT tmp; 113 | memcpy(&tmp, &data[*idx], 114 | sizeof(BASIC_OUTPUT)); // copy to avoid unaligned access 115 | 116 | // Output Type must be 0, denoting a SigLockedSingleOutput. 117 | MUST(tmp.output_type == OUTPUT_TYPE_BASICOUTPUT); 118 | 119 | // Amount must be > 0. 120 | MUST(tmp.amount > 0); 121 | 122 | // Native Tokens Count must be 0 123 | MUST(tmp.native_tokens_count == 0); 124 | 125 | // always 1 condition 126 | MUST(tmp.unlock_conditions_count == 1); 127 | 128 | // always 0 for "Address Unlock Condition" 129 | MUST(tmp.unlock_condition_type == ADDRESS_UNLOCK_CONDITION); 130 | 131 | // always 0 for ED25519 addres 132 | MUST(tmp.address_type == ADDRESS_TYPE_ED25519); 133 | 134 | // no extra blocks 135 | MUST(tmp.blocks_count == 0); 136 | 137 | total_amount += tmp.amount; 138 | 139 | // detect overflows 140 | MUST(total_amount >= tmp.amount); 141 | 142 | *idx = *idx + sizeof(BASIC_OUTPUT); 143 | } 144 | 145 | return 1; 146 | } 147 | 148 | 149 | // validate payload 150 | static uint8_t validate_payload(const uint8_t *data, uint32_t *idx) 151 | { 152 | // uses safe getter macro that returns an error in case of invalid access 153 | uint32_t payload_length; 154 | MUST(get_uint32(data, idx, &payload_length)); 155 | 156 | // wrap-around-safe 157 | MUST_SUM_LOWER_THAN(*idx, payload_length, API_BUFFER_SIZE_BYTES); 158 | 159 | *idx = *idx + payload_length; 160 | return 1; 161 | } 162 | 163 | // skip bytes 164 | // we can't validate the content 165 | static uint8_t validate_skip_bytes(uint32_t *idx, uint16_t bytes_to_skip) 166 | { 167 | MUST(*idx + bytes_to_skip < API_BUFFER_SIZE_BYTES); 168 | 169 | *idx = *idx + bytes_to_skip; 170 | return 1; 171 | } 172 | 173 | // validate if there are enough bip32 fragments 174 | static uint8_t 175 | validate_inputs_bip32(const uint8_t *data, uint32_t *idx, uint16_t inputs_count, 176 | API_INPUT_BIP32_INDEX **inputs_bip32_indices) 177 | { 178 | *inputs_bip32_indices = (API_INPUT_BIP32_INDEX *)&data[*idx]; 179 | // check if there are as many bip32-paths as inputs 180 | for (uint32_t i = 0; i < inputs_count; i++) { 181 | MUST(*idx + sizeof(API_INPUT_BIP32_INDEX) < API_BUFFER_SIZE_BYTES); 182 | 183 | API_INPUT_BIP32_INDEX tmp; 184 | memcpy(&tmp, &data[*idx], 185 | sizeof(API_INPUT_BIP32_INDEX)); // copy to avoid unaligned access 186 | 187 | // check is MSBs set 188 | MUST(tmp.bip32_index & 0x80000000); 189 | MUST(tmp.bip32_change & 0x80000000); 190 | 191 | *idx = *idx + sizeof(API_INPUT_BIP32_INDEX); 192 | } 193 | return 1; 194 | } 195 | 196 | // --- CHECK INPUTS FOR DUPLICATES --- 197 | static uint8_t validate_inputs_duplicates(const UTXO_INPUT *inputs, 198 | uint16_t inputs_count) 199 | { 200 | // at least 2 needed for check 201 | if (inputs_count < 2) { 202 | return 1; 203 | } 204 | 205 | // Every combination of Transaction ID + Transaction Output Index must be 206 | // unique in the inputs set. 207 | for (uint32_t i = 0; i < inputs_count; i++) { 208 | for (uint32_t j = i + 1; j < inputs_count; j++) { 209 | // we can check all bytes because first is the input_type that must 210 | // always be 0 211 | if (!memcmp(&inputs[i], &inputs[j], sizeof(UTXO_INPUT))) { 212 | return 0; 213 | } 214 | } 215 | } 216 | return 1; 217 | } 218 | 219 | static uint8_t essence_verify_remainder_address( 220 | uint32_t *bip32_path, BASIC_OUTPUT *outputs, uint32_t outputs_count, 221 | uint16_t remainder_index, API_REMAINDER_BIP32_INDEX *remainder_bip32) 222 | { 223 | // check remainder_index 224 | MUST(remainder_index < outputs_count); 225 | 226 | // check bip32 index 227 | MUST(remainder_bip32->bip32_change & 0x80000000); 228 | MUST(remainder_bip32->bip32_index & 0x80000000); 229 | 230 | BASIC_OUTPUT tmp; 231 | 232 | explicit_bzero(&tmp, sizeof(BASIC_OUTPUT)); 233 | 234 | // set bip32 index 235 | bip32_path[BIP32_ADDRESS_INDEX] = remainder_bip32->bip32_index; 236 | bip32_path[BIP32_CHANGE_INDEX] = remainder_bip32->bip32_change; 237 | 238 | // Block below cannot be fuzzed without going through crypto APIs 239 | #ifndef FUZZING 240 | // address generate generates with address 241 | MUST(address_generate(bip32_path, BIP32_PATH_LEN, &tmp.address_type)); 242 | 243 | // verify, the address is the same 244 | // relies on packed struct 245 | MUST(!memcmp(&outputs[remainder_index].address_type, &tmp.address_type, 246 | ADDRESS_WITH_TYPE_SIZE_BYTES)); 247 | #else 248 | (void)outputs; 249 | #endif 250 | return 1; 251 | } 252 | 253 | static uint8_t essence_hash(API_CTX *api) 254 | { 255 | // Block below cannot be fuzzed without going through crypto APIs 256 | #ifndef FUZZING 257 | cx_blake2b_t blake2b; 258 | MUST(cx_blake2b_init_no_throw(&blake2b, BLAKE2B_SIZE_BYTES * 8) == CX_OK); 259 | MUST(cx_hash_no_throw(&blake2b.header, CX_LAST, api->data.buffer, 260 | api->essence.length, api->essence.hash, 261 | ADDRESS_SIZE_BYTES) == CX_OK); 262 | #else 263 | (void)api; 264 | #endif 265 | return 1; 266 | } 267 | 268 | uint8_t essence_parse_and_validate_stardust(API_CTX *api) 269 | { 270 | uint32_t idx = 0; 271 | 272 | // Transaction Essence Type value must be 0, denoting an Transaction 273 | // Essence. uses safe getter macro that returns an error in case of invalid 274 | // access 275 | uint8_t transaction_essence_type; 276 | MUST(get_uint8(api->data.buffer, &idx, &transaction_essence_type)); 277 | 278 | MUST(transaction_essence_type == TRANSACTION_ESSENCE_TYPE_STARDUST); 279 | // network id 280 | MUST(validate_skip_bytes(&idx, sizeof(uint64_t))); 281 | 282 | // parse data 283 | MUST(validate_inputs(api->data.buffer, &idx, &api->essence.inputs, 284 | &api->essence.inputs_count)); 285 | 286 | // inputs commitment hash 287 | MUST(validate_skip_bytes(&idx, BLAKE2B_SIZE_BYTES)); 288 | 289 | MUST(validate_outputs(api->data.buffer, &idx, 290 | (BASIC_OUTPUT **)&api->essence.outputs, 291 | &api->essence.outputs_count)); 292 | 293 | MUST(validate_payload(api->data.buffer, &idx)); 294 | 295 | // save essence length 296 | api->essence.length = idx; 297 | 298 | // bip32 indices don't belong to the essence 299 | MUST(validate_inputs_bip32(api->data.buffer, &idx, 300 | api->essence.inputs_count, 301 | &api->essence.inputs_bip32_index)); 302 | 303 | // save data length 304 | api->data.length = idx; 305 | 306 | // if remainder output, check the address 307 | if (api->essence.has_remainder) { 308 | MUST(essence_verify_remainder_address( 309 | api->bip32_path, (BASIC_OUTPUT *)api->essence.outputs, 310 | api->essence.outputs_count, api->essence.remainder_index, 311 | &api->essence.remainder_bip32)); 312 | #if 0 313 | // technically, this is valid ... so don't block it but keep this notice for documentation 314 | // essence with only remainder and no other output 315 | MUST(api->essence.outputs_count > 1); 316 | #endif 317 | } 318 | 319 | // additional validation steps of parsed data 320 | MUST(validate_inputs_duplicates(api->essence.inputs, 321 | api->essence.inputs_count)); 322 | 323 | // everything fine - calculate the hash 324 | MUST(essence_hash(api)); 325 | 326 | // check if it's a sweeping transaction 327 | if (check_for_internal_transfer(api)) { 328 | api->essence.is_internal_transfer = 1; 329 | } 330 | 331 | return 1; 332 | } 333 | -------------------------------------------------------------------------------- /src/iota/essence_stardust.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "api.h" 4 | 5 | uint8_t essence_parse_and_validate_stardust(API_CTX *api); -------------------------------------------------------------------------------- /src/iota/internal_transfer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "os.h" 5 | 6 | #include "macros.h" 7 | #include "iota/constants.h" 8 | #include "api.h" 9 | #include "abstraction.h" 10 | #include "internal_transfer.h" 11 | 12 | #pragma GCC diagnostic error "-Wall" 13 | #pragma GCC diagnostic error "-Wextra" 14 | #pragma GCC diagnostic error "-Wmissing-prototypes" 15 | 16 | // checks if all coins remain on the wallet 17 | // generate or each input bip32-path the address and compare it 18 | // with the output address 19 | // if a match is found, no coins are leaving the wallet 20 | uint8_t check_for_internal_transfer(const API_CTX *api) 21 | { 22 | // for sweeping only a single output and no remainder 23 | MUST(api->essence.outputs_count == 1 && !api->essence.has_remainder); 24 | 25 | // no internal transfer in SMR claiming 26 | MUST(api->app_mode != APP_MODE_SHIMMER_CLAIMING); 27 | 28 | // get the first (and only) output address 29 | const uint8_t *output = get_output_address_ptr(api, 0); 30 | 31 | API_INPUT_BIP32_INDEX *input_indices = 32 | (API_INPUT_BIP32_INDEX *)api->essence.inputs_bip32_index; 33 | 34 | uint32_t bip32_tmp_path[BIP32_PATH_LEN]; 35 | memcpy(bip32_tmp_path, api->bip32_path, sizeof(bip32_tmp_path)); 36 | 37 | for (uint16_t i = 0; i < api->essence.inputs_count; i++) { 38 | 39 | // set bip32 index 40 | // avoid unalighed access 41 | memcpy(&bip32_tmp_path[BIP32_ADDRESS_INDEX], 42 | &input_indices[i].bip32_index, sizeof(uint32_t)); 43 | memcpy(&bip32_tmp_path[BIP32_CHANGE_INDEX], 44 | &input_indices[i].bip32_change, sizeof(uint32_t)); 45 | 46 | uint8_t input[ADDRESS_WITH_TYPE_SIZE_BYTES]; 47 | 48 | // address generate generates with address 49 | MUST(address_generate(bip32_tmp_path, BIP32_PATH_LEN, input)); 50 | 51 | // check if input and output addresses match 52 | if (!memcmp(output, input, ADDRESS_WITH_TYPE_SIZE_BYTES)) { 53 | return 1; 54 | } 55 | } 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /src/iota/internal_transfer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "api.h" 5 | 6 | uint8_t check_for_internal_transfer(const API_CTX *api); -------------------------------------------------------------------------------- /src/iota/signing.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "os.h" 5 | #include "cx.h" 6 | #include "api.h" 7 | #include "lib_standard_app/crypto_helpers.h" 8 | 9 | #include "macros.h" 10 | #include "signing.h" 11 | 12 | #ifndef FUZZING 13 | #include "iota_io.h" 14 | #include "iota/ed25519.h" 15 | #endif 16 | 17 | #pragma GCC diagnostic error "-Wall" 18 | #pragma GCC diagnostic error "-Wextra" 19 | #pragma GCC diagnostic error "-Wmissing-prototypes" 20 | 21 | static uint16_t sign_signature(SIGNATURE_BLOCK *pBlock, 22 | const uint8_t *essence_hash, 23 | uint32_t *bip32_signing_path, 24 | API_INPUT_BIP32_INDEX *input_bip32_index) 25 | { 26 | 27 | // overwrite bip32_signing_path 28 | bip32_signing_path[BIP32_ADDRESS_INDEX] = input_bip32_index->bip32_index; 29 | bip32_signing_path[BIP32_CHANGE_INDEX] = input_bip32_index->bip32_change; 30 | 31 | size_t signature_length = CX_SHA512_SIZE; 32 | 33 | MUST(bip32_derive_with_seed_eddsa_sign_hash_256( 34 | HDW_ED25519_SLIP10, CX_CURVE_Ed25519, bip32_signing_path, 35 | BIP32_PATH_LEN, CX_SHA512, essence_hash, BLAKE2B_SIZE_BYTES, 36 | pBlock->signature, &signature_length, NULL, 0) == CX_OK); 37 | 38 | MUST(signature_length == SIGNATURE_SIZE_BYTES); 39 | 40 | // get pubkey 41 | uint8_t raw_pubkey[65]; 42 | 43 | MUST(bip32_derive_with_seed_get_pubkey_256( 44 | HDW_ED25519_SLIP10, CX_CURVE_Ed25519, bip32_signing_path, 45 | BIP32_PATH_LEN, raw_pubkey, NULL, CX_SHA512, NULL, 0) == CX_OK); 46 | 47 | // convert Ledger pubkey to pubkey bytes 48 | MUST(ed25519_public_key_to_bytes(raw_pubkey, pBlock->public_key)); 49 | 50 | return (uint16_t)sizeof(SIGNATURE_BLOCK); 51 | } 52 | 53 | static uint16_t sign_signature_unlock_block( 54 | SIGNATURE_UNLOCK_BLOCK *pBlock, const uint8_t *essence_hash, 55 | uint32_t *bip32_signing_path, API_INPUT_BIP32_INDEX *input_bip32_index) 56 | { 57 | pBlock->signature_type = SIGNATURE_TYPE_ED25519; // ED25519 58 | pBlock->unlock_type = UNLOCK_TYPE_SIGNATURE; // signature 59 | 60 | MUST(sign_signature(&pBlock->signature, essence_hash, bip32_signing_path, 61 | input_bip32_index)); 62 | 63 | return (uint16_t)sizeof(SIGNATURE_UNLOCK_BLOCK); 64 | } 65 | 66 | static uint16_t sign_reference_unlock_block(REFERENCE_UNLOCK_BLOCK *pBlock, 67 | uint8_t reference_index) 68 | { 69 | pBlock->reference = (uint16_t)reference_index; 70 | pBlock->unlock_type = UNLOCK_TYPE_REFERENCE; // reference 71 | 72 | return (uint16_t)sizeof(REFERENCE_UNLOCK_BLOCK); 73 | } 74 | 75 | static uint8_t sign_get_reference_index(API_CTX *api, uint32_t signature_index) 76 | { 77 | API_INPUT_BIP32_INDEX *essence_inputs = 78 | (API_INPUT_BIP32_INDEX *)api->essence.inputs_bip32_index; 79 | 80 | // reference unlock block is only possible if signature_index != 0 and 81 | // it's not in blind_signing mode 82 | if (signature_index) { 83 | // check if it is a reference unlock block 84 | for (uint32_t i = 0; i < signature_index; i++) { 85 | // if there is a match, we found the reference block index 86 | if (!memcmp(&essence_inputs[signature_index], &essence_inputs[i], 87 | sizeof(API_INPUT_BIP32_INDEX))) { 88 | return i; 89 | } 90 | } 91 | } 92 | return 0xff; 93 | } 94 | 95 | uint16_t sign(API_CTX *api, uint8_t *output, uint32_t signature_index) 96 | { 97 | MUST(signature_index < api->essence.inputs_count); 98 | 99 | API_INPUT_BIP32_INDEX input_bip32_index; 100 | API_INPUT_BIP32_INDEX *essence_inputs = 101 | (API_INPUT_BIP32_INDEX *)api->essence.inputs_bip32_index; 102 | 103 | memcpy(&input_bip32_index, &essence_inputs[signature_index], 104 | sizeof(API_INPUT_BIP32_INDEX)); // avoid unaligned access 105 | 106 | uint8_t reference_index = 0xff; 107 | 108 | // no reference index in blindsigning mode 109 | if (!api->essence.blindsigning) { 110 | reference_index = sign_get_reference_index(api, signature_index); 111 | } 112 | 113 | uint16_t signature_size = 0; 114 | 115 | // 0xff if not a reference index block 116 | if (reference_index == 0xff) { 117 | signature_size = sign_signature_unlock_block( 118 | (SIGNATURE_UNLOCK_BLOCK *)output, api->essence.hash, 119 | api->bip32_path, &input_bip32_index); 120 | } 121 | else { 122 | signature_size = sign_reference_unlock_block( 123 | (REFERENCE_UNLOCK_BLOCK *)output, reference_index); 124 | } 125 | 126 | MUST(signature_size); 127 | 128 | return signature_size; 129 | } 130 | -------------------------------------------------------------------------------- /src/iota/signing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "api.h" 4 | 5 | uint16_t sign(API_CTX *api, uint8_t *output, uint32_t signature_index); 6 | -------------------------------------------------------------------------------- /src/iota_io.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "os.h" 4 | #include "macros.h" 5 | 6 | #include "iota_io.h" 7 | #include "api.h" 8 | #include "iota/constants.h" 9 | 10 | #include "ui/nano/flow_user_confirm.h" 11 | 12 | #pragma GCC diagnostic error "-Wall" 13 | #pragma GCC diagnostic error "-Wextra" 14 | #pragma GCC diagnostic error "-Wmissing-prototypes" 15 | 16 | extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; 17 | 18 | void io_initialize() 19 | { 20 | memset(G_io_apdu_buffer, 0, IO_APDU_BUFFER_SIZE); 21 | api_initialize(APP_MODE_INIT, 0); 22 | } 23 | 24 | void io_send(const void *ptr, unsigned int length, unsigned short sw) 25 | { 26 | if (length > IO_APDU_BUFFER_SIZE - 2) { 27 | THROW(SW_UNKNOWN); 28 | } 29 | 30 | if ((uint32_t)G_io_apdu_buffer != (uint32_t)ptr) { 31 | memcpy(G_io_apdu_buffer, ptr, length); 32 | } 33 | 34 | G_io_apdu_buffer[length++] = sw >> 8; 35 | G_io_apdu_buffer[length++] = sw >> 0; 36 | 37 | // just send, the response is handled in the main loop 38 | io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length); 39 | } 40 | 41 | uint8_t *io_get_buffer() 42 | { 43 | return G_io_apdu_buffer; 44 | } 45 | 46 | unsigned int iota_dispatch(const uint8_t ins, const uint8_t p1, 47 | const uint8_t p2, const uint8_t len, 48 | const unsigned char *input_data, uint8_t is_locked) 49 | { 50 | // commands that can be executed on a locked ledger 51 | // this is useful for initializing the ledger without errors 52 | if (is_locked && !(ins == INS_NONE || ins == INS_GET_APP_CONFIG || 53 | ins == INS_SET_ACCOUNT || ins == INS_RESET || 54 | ins == INS_CLEAR_DATA_BUFFER)) { 55 | THROW(SW_DEVICE_IS_LOCKED); 56 | } 57 | 58 | // check second byte for instruction 59 | switch (ins) { 60 | case INS_NONE: 61 | return 0; 62 | 63 | case INS_GET_APP_CONFIG: 64 | return api_get_app_config(is_locked); 65 | 66 | case INS_SET_ACCOUNT: 67 | return api_set_account(p1, input_data, len); 68 | 69 | case INS_RESET: 70 | return api_reset(); 71 | 72 | case INS_WRITE_DATA_BLOCK: 73 | return api_write_data_block(p1, input_data, len); 74 | 75 | case INS_PREPARE_SIGNING: 76 | return api_prepare_signing(p2, input_data, len); 77 | 78 | case INS_PREPARE_BLINDSIGNING: 79 | return api_prepare_blindsigning(); 80 | 81 | case INS_GENERATE_ADDRESS: 82 | return api_generate_address(p1, input_data, len); 83 | 84 | case INS_GET_DATA_BUFFER_STATE: 85 | return api_get_data_buffer_state(); 86 | 87 | case INS_CLEAR_DATA_BUFFER: 88 | return api_clear_data_buffer(); 89 | 90 | case INS_USER_CONFIRM_ESSENCE: 91 | return api_user_confirm_essence(); 92 | 93 | case INS_SIGN_SINGLE: 94 | return api_sign(p1); 95 | 96 | case INS_READ_DATA_BLOCK: 97 | return api_read_data_block(p1); 98 | 99 | case INS_SHOW_FLOW: 100 | return api_show_flow(); 101 | 102 | #ifdef APP_DEBUG 103 | case INS_DUMP_MEMORY: 104 | return api_dump_memory(p1); 105 | case INS_SET_NON_INTERACTIVE_MODE: 106 | return api_set_non_interactive_mode(p1); 107 | #endif 108 | 109 | // unknown command ?? 110 | default: 111 | THROW(SW_INS_NOT_SUPPORTED); 112 | } 113 | return 0; // to satisfy the compiler 114 | } 115 | -------------------------------------------------------------------------------- /src/iota_io.h: -------------------------------------------------------------------------------- 1 | #ifndef IOTA_IO_H 2 | #define IOTA_IO_H 3 | 4 | #include 5 | #include 6 | 7 | #include "os.h" 8 | 9 | #include "iota/constants.h" 10 | 11 | void io_initialize(void); 12 | void io_send(const void *ptr, unsigned int length, unsigned short sw); 13 | 14 | uint8_t *io_get_buffer(void); 15 | 16 | 17 | unsigned int iota_dispatch(uint8_t ins, uint8_t p1, uint8_t p2, uint8_t len, 18 | const unsigned char *input_data, uint8_t is_locked); 19 | 20 | 21 | /* --- CLA --- */ 22 | 23 | #define CLA 0x7B 24 | 25 | /* --- INS --- */ 26 | 27 | enum { 28 | INS_NONE = 0x00, 29 | 30 | INS_GET_APP_CONFIG = 0x10, 31 | INS_SET_ACCOUNT = 0x11, 32 | 33 | INS_GET_DATA_BUFFER_STATE = 0x80, 34 | INS_WRITE_DATA_BLOCK = 0x81, 35 | INS_READ_DATA_BLOCK = 0x82, 36 | INS_CLEAR_DATA_BUFFER = 0x83, 37 | 38 | INS_SHOW_FLOW = 0x90, 39 | 40 | INS_PREPARE_BLINDSIGNING = 0x91, 41 | 42 | INS_PREPARE_SIGNING = 0xa0, 43 | INS_GENERATE_ADDRESS = 0xa1, 44 | INS_SIGN = 0xa2, 45 | INS_USER_CONFIRM_ESSENCE = 0xa3, 46 | 47 | INS_SIGN_SINGLE = 0xa4, 48 | 49 | #ifdef APP_DEBUG 50 | INS_DUMP_MEMORY = 0x66, 51 | INS_SET_NON_INTERACTIVE_MODE = 0x67, 52 | #endif 53 | 54 | INS_RESET = 0xff, 55 | }; 56 | 57 | enum { 58 | SW_OK = 0x9000, 59 | SW_INCORRECT_LENGTH = 0x6700, 60 | SW_COMMAND_INVALID_DATA = 0x6a80, 61 | SW_INCORRECT_P1P2 = 0x6b00, 62 | SW_INCORRECT_LENGTH_P3 = 0x6c00, 63 | SW_INS_NOT_SUPPORTED = 0x6d00, 64 | SW_CLA_NOT_SUPPORTED = 0x6e00, 65 | 66 | SW_ACCOUNT_NOT_VALID = 0x6388, 67 | 68 | SW_COMMAND_NOT_ALLOWED = 0x6900, 69 | SW_FEATURE_NOT_SUPPORTED = 0x6901, 70 | SW_SECURITY_STATUS_NOT_SATISFIED = 0x6982, 71 | SW_CONDITIONS_OF_USE_NOT_SATISFIED = 0x6985, 72 | 73 | SW_COMMAND_TIMEOUT = 0x6401, 74 | SW_UNKNOWN = 0x69a0, 75 | 76 | SW_DENIED_BY_USER = SW_CONDITIONS_OF_USE_NOT_SATISFIED, 77 | SW_DEVICE_IS_LOCKED = SW_SECURITY_STATUS_NOT_SATISFIED, 78 | }; 79 | 80 | #endif // IOTA_IO_H 81 | -------------------------------------------------------------------------------- /src/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef MACROS_H 2 | #define MACROS_H 3 | 4 | #include "os.h" 5 | 6 | // throws if expression is not true 7 | #define MUST_THROW(c) \ 8 | { \ 9 | if (!(c)) { \ 10 | THROW(SW_UNKNOWN); \ 11 | } \ 12 | } 13 | 14 | // return 0 if expression is not true 15 | #define MUST(c) \ 16 | { \ 17 | if (!(c)) { \ 18 | return 0; \ 19 | } \ 20 | } 21 | 22 | // wrap-around safe check for addition 23 | #define MUST_SUM_LOWER_THAN(a, b, sum) \ 24 | { \ 25 | if (!((a < sum) && (b < sum) && ((a + b) < sum))) { \ 26 | return 0; \ 27 | } \ 28 | } 29 | 30 | /// Throws provided error, if condition does not apply 31 | #define VALIDATE(cond, error) \ 32 | { \ 33 | if (!(cond)) { \ 34 | THROW(error); \ 35 | } \ 36 | } 37 | 38 | //#define UNUSED __attribute__((unused)) 39 | 40 | #endif // MACROS_H 41 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "api.h" 5 | #include "iota_io.h" 6 | #include "macros.h" 7 | #include "os.h" 8 | #include "os_apilevel.h" 9 | #include "os_io_seproxyhal.h" 10 | #include "seproxyhal_protocol.h" 11 | #include "ux.h" 12 | #include "ui/ui.h" 13 | #include "nv_mem.h" 14 | #include "debugprintf.h" 15 | #include "ui/nano/flow_user_confirm.h" 16 | 17 | #pragma GCC diagnostic error "-Wall" 18 | #pragma GCC diagnostic error "-Wextra" 19 | #pragma GCC diagnostic error "-Wmissing-prototypes" 20 | 21 | // define global SDK variables 22 | unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; 23 | 24 | 25 | uint32_t timer_events; 26 | 27 | // APDU header present in every input 28 | typedef IO_STRUCT APDU_HEADER 29 | { 30 | uint8_t cla; 31 | uint8_t ins; 32 | uint8_t p1; 33 | uint8_t p2; 34 | uint8_t p3; 35 | } 36 | APDU_HEADER; 37 | 38 | /// Returns true, if the device is not locked 39 | static bool device_is_unlocked(void) 40 | { 41 | // speculos doesn't support locking 42 | #ifdef SPECULOS 43 | return true; 44 | #else 45 | #if CX_APILEVEL >= 9 46 | return os_global_pin_is_validated() == BOLOS_UX_OK; 47 | #else 48 | return os_global_pin_is_validated(); 49 | #endif 50 | #endif 51 | } 52 | 53 | static void IOTA_main() 54 | { 55 | volatile unsigned int flags = 0; 56 | 57 | timer_events = 0; 58 | 59 | ui_init(); 60 | io_initialize(); 61 | 62 | // Exchange APDUs until EXCEPTION_IO_RESET is thrown 63 | for (;;) { 64 | BEGIN_TRY 65 | { 66 | TRY 67 | { 68 | // data is always sent separatly 69 | const unsigned int rx = io_exchange(CHANNEL_APDU | flags, 0); 70 | 71 | // when no APDU received, trigger a reset 72 | if (rx == 0) { 73 | THROW(EXCEPTION_IO_RESET); 74 | } 75 | 76 | // check header validity 77 | VALIDATE(rx >= sizeof(APDU_HEADER), SW_INCORRECT_LENGTH); 78 | 79 | const APDU_HEADER *header = (APDU_HEADER *)G_io_apdu_buffer; 80 | VALIDATE(header->cla == CLA, SW_CLA_NOT_SUPPORTED); 81 | VALIDATE(rx == header->p3 + sizeof(APDU_HEADER), 82 | SW_INCORRECT_LENGTH_P3); 83 | 84 | unsigned char *data = G_io_apdu_buffer + sizeof(APDU_HEADER); 85 | 86 | // handle iota apdu commands 87 | flags = iota_dispatch(header->ins, header->p1, header->p2, 88 | header->p3, data, !device_is_unlocked()); 89 | } 90 | CATCH(EXCEPTION_IO_RESET) 91 | { 92 | THROW(EXCEPTION_IO_RESET); 93 | } 94 | CATCH_OTHER(e) 95 | { 96 | unsigned short sw; 97 | 98 | // expected errors start with 0x6 99 | if ((e & 0xF000) == 0x6000) { 100 | sw = e; 101 | } 102 | else { 103 | sw = SW_UNKNOWN | (e & 0x0FF); 104 | } 105 | 106 | switch (sw) { 107 | case SW_DEVICE_IS_LOCKED: 108 | case SW_CLA_NOT_SUPPORTED: 109 | // do not reset anything 110 | break; 111 | default: 112 | // reset states and UI 113 | api_initialize(APP_MODE_INIT, 0); 114 | ui_reset(); 115 | } 116 | 117 | // send the error code 118 | io_send(NULL, 0, sw); 119 | 120 | flags = 0; 121 | } 122 | FINALLY 123 | { 124 | } 125 | } 126 | END_TRY; 127 | } 128 | } 129 | 130 | // seems to be called from io_exchange 131 | unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) 132 | { 133 | switch (channel & ~(IO_FLAGS)) { 134 | case CHANNEL_KEYBOARD: 135 | break; 136 | 137 | // multiplexed io exchange over a SPI channel and TLV encapsulated protocol 138 | case CHANNEL_SPI: 139 | if (tx_len) { 140 | io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len); 141 | 142 | if (channel & IO_RESET_AFTER_REPLIED) { 143 | reset(); 144 | } 145 | // nothing received from the master so far (it's a tx transaction) 146 | return 0; 147 | } 148 | else { 149 | return io_seproxyhal_spi_recv(G_io_apdu_buffer, 150 | sizeof(G_io_apdu_buffer), 0); 151 | } 152 | 153 | default: 154 | THROW(INVALID_PARAMETER); 155 | } 156 | return 0; 157 | } 158 | 159 | void io_seproxyhal_display(const bagl_element_t *element) 160 | { 161 | io_seproxyhal_display_default((bagl_element_t *)element); 162 | } 163 | 164 | unsigned char io_event(unsigned char channel) 165 | { 166 | UNUSED(channel); 167 | // nothing done with the event, throw an error on the transport layer if 168 | // needed 169 | 170 | // can't have more than one tag in the reply, not supported yet. 171 | switch (G_io_seproxyhal_spi_buffer[0]) { 172 | case SEPROXYHAL_TAG_FINGER_EVENT: 173 | UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); 174 | break; 175 | 176 | case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: // for Nano S 177 | UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); 178 | break; 179 | 180 | case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: 181 | UX_DISPLAYED_EVENT({}); 182 | break; 183 | 184 | case SEPROXYHAL_TAG_TICKER_EVENT: 185 | UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, {}); 186 | 187 | timer_events++; 188 | ui_timer_event(); 189 | break; 190 | 191 | default: 192 | UX_DEFAULT_EVENT(); 193 | break; 194 | } 195 | 196 | // close the event if not done previously (by a display or whatever) 197 | if (!io_seproxyhal_spi_is_status_sent()) { 198 | io_seproxyhal_general_status(); 199 | } 200 | 201 | // command has been processed, DO NOT reset the current APDU transport 202 | return 1; 203 | } 204 | 205 | static void app_exit(void) 206 | { 207 | BEGIN_TRY_L(exit) 208 | { 209 | TRY_L(exit) 210 | { 211 | os_sched_exit(-1); 212 | } 213 | FINALLY_L(exit) 214 | { 215 | } 216 | } 217 | END_TRY_L(exit); 218 | } 219 | 220 | __attribute__((section(".boot"))) int main(void) 221 | { 222 | // exit critical section 223 | __asm volatile("cpsie i"); 224 | 225 | // ensure exception will work as planned 226 | os_boot(); 227 | 228 | for (;;) { 229 | UX_INIT(); 230 | 231 | BEGIN_TRY 232 | { 233 | TRY 234 | { 235 | io_seproxyhal_init(); 236 | 237 | nv_init(); 238 | 239 | // deactivate usb before activating 240 | USB_power(false); 241 | USB_power(true); 242 | 243 | #ifdef HAVE_BLE 244 | BLE_power(false, NULL); 245 | BLE_power(true, "Nano X"); 246 | #endif // HAVE_BLE 247 | 248 | IOTA_main(); 249 | } 250 | CATCH(EXCEPTION_IO_RESET) 251 | { 252 | // reset IO and UX 253 | continue; 254 | } 255 | CATCH_ALL 256 | { 257 | // exit on all other exceptions 258 | break; 259 | } 260 | FINALLY 261 | { 262 | } 263 | } 264 | END_TRY; 265 | } 266 | app_exit(); 267 | } 268 | -------------------------------------------------------------------------------- /src/nv_mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "os.h" 3 | #include "os_pic.h" 4 | #include "os_nvm.h" 5 | #include "nv_mem.h" 6 | 7 | #define N_nv_mem (*(volatile nv_mem_t *)PIC(&N_nv_mem_real)) 8 | 9 | typedef struct nv_mem_t { 10 | uint8_t blindsigning; 11 | uint8_t initialized; 12 | } nv_mem_t; 13 | 14 | const nv_mem_t N_nv_mem_real; 15 | 16 | void nv_init() 17 | { 18 | // initialize non volatile memory 19 | if (N_nv_mem.initialized != 0xa5) { 20 | nv_mem_t nv; 21 | nv.blindsigning = 0x00; // blind signing not allowed per default 22 | nv.initialized = 0xa5; 23 | nvm_write((void *)&N_nv_mem, (void *)&nv, sizeof(nv_mem_t)); 24 | } 25 | } 26 | 27 | uint8_t nv_get_blindsigning() 28 | { 29 | return N_nv_mem.blindsigning; 30 | } 31 | 32 | void nv_toggle_blindsigning() 33 | { 34 | uint8_t value = 1 - N_nv_mem.blindsigning; 35 | nvm_write((void *)&N_nv_mem.blindsigning, (void *)&value, sizeof(uint8_t)); 36 | } 37 | -------------------------------------------------------------------------------- /src/nv_mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void nv_init(); 6 | 7 | uint8_t nv_get_blindsigning(); 8 | 9 | void nv_toggle_blindsigning(); -------------------------------------------------------------------------------- /src/ui/nano/flow_generating_addresses.c: -------------------------------------------------------------------------------- 1 | /* 2 | * generic_error.c 3 | * 4 | * Created on: 26.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #include "os.h" 9 | #include "ux.h" 10 | #include "glyphs.h" 11 | 12 | #include "os_io_seproxyhal.h" 13 | 14 | #include "flow_main_menu.h" 15 | 16 | #pragma GCC diagnostic error "-Wall" 17 | #pragma GCC diagnostic error "-Wextra" 18 | 19 | extern const ux_flow_step_t *const ux_main_menu; 20 | 21 | // clang-format off 22 | UX_STEP_TIMEOUT( 23 | ux_generating_addresses, 24 | pbb, 25 | 1000, // show main menu after 1s 26 | &ux_main_menu, 27 | { 28 | &C_x_icon_load, 29 | "Generating", 30 | "Addresses ..." 31 | } 32 | ); 33 | 34 | UX_FLOW( 35 | ux_flow_generating_addresses, 36 | &ux_generating_addresses, 37 | FLOW_END_STEP 38 | ); 39 | // clang-format on 40 | 41 | void flow_generating_addresses() 42 | { 43 | ux_flow_init(0, ux_flow_generating_addresses, NULL); 44 | 45 | // show flow immediately 46 | UX_WAIT_DISPLAYED(); 47 | } 48 | -------------------------------------------------------------------------------- /src/ui/nano/flow_generating_addresses.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Created on: 26.10.2020 4 | * Author: thomas 5 | */ 6 | 7 | #pragma once 8 | 9 | void flow_generating_addresses(void); 10 | -------------------------------------------------------------------------------- /src/ui/nano/flow_main_menu.c: -------------------------------------------------------------------------------- 1 | /* 2 | * flow_main_menu.c 3 | * 4 | * Created on: 26.10.2020 5 | * Author: thomas 6 | */ 7 | #include "os.h" 8 | #include "ux.h" 9 | 10 | #include "flow_user_confirm.h" 11 | 12 | #include "glyphs.h" 13 | 14 | #include "api.h" 15 | #include "nv_mem.h" 16 | 17 | #include "iota/constants.h" 18 | #include "ui_common.h" 19 | 20 | // enabled 21 | // disabled 22 | 23 | static char switch_state[9]; 24 | 25 | #pragma GCC diagnostic error "-Wall" 26 | #pragma GCC diagnostic error "-Wextra" 27 | 28 | void cb_settings_preinit(); 29 | void cb_settings_enter(); 30 | void cb_settings_back(); 31 | void cb_toggle_blindsigning(); 32 | 33 | // clang-format off 34 | #if defined(APP_IOTA) 35 | UX_STEP_NOCB( 36 | ux_idle_flow_1_step, 37 | pb, 38 | { 39 | &C_icon_iota, 40 | "IOTA", 41 | } 42 | ); 43 | #elif defined(APP_SHIMMER) 44 | UX_STEP_NOCB( 45 | ux_idle_flow_1_step, 46 | pb, 47 | { 48 | &C_icon_shimmer, 49 | "Shimmer", 50 | } 51 | ); 52 | #else 53 | #error unknown app 54 | #endif 55 | 56 | UX_STEP_NOCB( 57 | ux_idle_flow_2_step, 58 | bn, 59 | { 60 | "Version", 61 | APPVERSION, 62 | } 63 | ); 64 | 65 | UX_STEP_CB( 66 | ux_settings, 67 | pb, 68 | cb_settings_enter(), 69 | { 70 | &C_icon_coggle, "Settings", 71 | } 72 | ); 73 | 74 | UX_STEP_CB( 75 | ux_idle_flow_3_step, 76 | pb, 77 | os_sched_exit(-1), 78 | { 79 | &C_x_icon_dash, "Quit", 80 | } 81 | ); 82 | 83 | UX_FLOW(ux_main_menu, 84 | &ux_idle_flow_1_step, 85 | &ux_idle_flow_2_step, 86 | &ux_settings, 87 | &ux_idle_flow_3_step, 88 | FLOW_LOOP 89 | ); 90 | 91 | // settings menu 92 | 93 | 94 | UX_STEP_CB_INIT( 95 | ux_step_toggle_blindsigning, 96 | bn, 97 | cb_settings_preinit(), 98 | cb_toggle_blindsigning(), 99 | { 100 | "Blind Signing", 101 | (const char*) switch_state 102 | } 103 | ); 104 | 105 | UX_STEP_CB( 106 | ux_step_settings_back, 107 | pb, 108 | cb_settings_back(), 109 | { 110 | &C_x_icon_back, "Back" 111 | } 112 | ); 113 | 114 | UX_FLOW( 115 | ux_flow_settings, 116 | &ux_step_toggle_blindsigning, 117 | &ux_step_settings_back, 118 | FLOW_LOOP 119 | ); 120 | 121 | // clang-format on 122 | 123 | void cb_settings_enter() 124 | { 125 | ux_flow_init(0, ux_flow_settings, &ux_step_toggle_blindsigning); 126 | } 127 | 128 | void cb_settings_preinit() 129 | { 130 | if (nv_get_blindsigning()) { 131 | strcpy(switch_state, "enabled"); 132 | } 133 | else { 134 | strcpy(switch_state, "disabled"); 135 | } 136 | } 137 | 138 | void cb_toggle_blindsigning() 139 | { 140 | nv_toggle_blindsigning(); 141 | ux_flow_init(0, ux_flow_settings, &ux_step_toggle_blindsigning); 142 | } 143 | 144 | void cb_settings_back() 145 | { 146 | ux_flow_init(0, ux_main_menu, &ux_settings); 147 | } 148 | 149 | void flow_main_menu() 150 | { 151 | // reserve a display stack slot if none yet 152 | if (G_ux.stack_count == 0) { 153 | ux_stack_push(); 154 | } 155 | ux_flow_init(0, ux_main_menu, NULL); 156 | } 157 | -------------------------------------------------------------------------------- /src/ui/nano/flow_main_menu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * flow_main_menu.h 3 | * 4 | * Created on: 26.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #pragma once 9 | 10 | void flow_main_menu(void); 11 | -------------------------------------------------------------------------------- /src/ui/nano/flow_signed_successfully.c: -------------------------------------------------------------------------------- 1 | /* 2 | * generic_error.c 3 | * 4 | * Created on: 26.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #include "os.h" 9 | #include "ux.h" 10 | #include "glyphs.h" 11 | 12 | #include "os_io_seproxyhal.h" 13 | 14 | #include "flow_main_menu.h" 15 | 16 | #pragma GCC diagnostic error "-Wall" 17 | #pragma GCC diagnostic error "-Wextra" 18 | 19 | extern const ux_flow_step_t *const ux_main_menu; 20 | 21 | // clang-format off 22 | UX_STEP_TIMEOUT( 23 | ux_signed_successfully, 24 | pbb, 25 | 2000, 26 | &ux_main_menu, 27 | { 28 | &C_x_icon_check, 29 | "Signed", 30 | "Successfully" 31 | } 32 | ); 33 | 34 | UX_FLOW( 35 | ux_flow_signed_successfully, 36 | &ux_signed_successfully, 37 | FLOW_END_STEP 38 | ); 39 | // clang-format on 40 | 41 | void flow_signed_successfully() 42 | { 43 | ux_flow_init(0, ux_flow_signed_successfully, NULL); 44 | 45 | // show flow immediately 46 | UX_WAIT_DISPLAYED(); 47 | } 48 | -------------------------------------------------------------------------------- /src/ui/nano/flow_signed_successfully.h: -------------------------------------------------------------------------------- 1 | /* 2 | * flow_signed_successfully.h 3 | * 4 | * Created on: 28.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #pragma once 9 | 10 | void flow_signed_successfully(void); 11 | -------------------------------------------------------------------------------- /src/ui/nano/flow_signing.c: -------------------------------------------------------------------------------- 1 | /* 2 | * generic_error.c 3 | * 4 | * Created on: 26.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #include "os.h" 9 | #include "ux.h" 10 | #include "glyphs.h" 11 | 12 | #include "os_io_seproxyhal.h" 13 | 14 | #include "flow_main_menu.h" 15 | 16 | #pragma GCC diagnostic error "-Wall" 17 | #pragma GCC diagnostic error "-Wextra" 18 | 19 | extern const ux_flow_step_t *const ux_main_menu; 20 | 21 | // clang-format off 22 | UX_STEP_TIMEOUT( 23 | ux_signing, 24 | pb, 25 | 2000, // show main menu after 2s 26 | &ux_main_menu, 27 | { 28 | &C_x_icon_load, 29 | "Signing ..." 30 | } 31 | ); 32 | 33 | UX_FLOW( 34 | ux_flow_signing, 35 | &ux_signing, 36 | FLOW_END_STEP 37 | ); 38 | // clang-format on 39 | 40 | void flow_signing() 41 | { 42 | ux_flow_init(0, ux_flow_signing, NULL); 43 | 44 | // show flow immediately 45 | UX_WAIT_DISPLAYED(); 46 | } 47 | -------------------------------------------------------------------------------- /src/ui/nano/flow_signing.h: -------------------------------------------------------------------------------- 1 | /* 2 | * flow_signing.h 3 | * 4 | * Created on: 28.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #pragma once 9 | 10 | void flow_signing(void); 11 | -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * flow.c 3 | * 4 | * Created on: 21.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #include "os.h" 9 | #include "flow_user_confirm_transaction.h" 10 | #include "abstraction.h" 11 | #include "ux.h" 12 | 13 | #include "glyphs.h" 14 | 15 | #include "api.h" 16 | 17 | #include "iota/constants.h" 18 | #include "ui_common.h" 19 | 20 | #pragma GCC diagnostic error "-Wall" 21 | #pragma GCC diagnostic error "-Wextra" 22 | 23 | ux_state_t G_ux; 24 | bolos_ux_params_t G_ux_params; 25 | 26 | flowdata_t flow_data; 27 | 28 | extern uint32_t timer_events; 29 | 30 | void flow_init() 31 | { 32 | memset(&flow_data, 0, sizeof(flow_data)); 33 | } 34 | 35 | void flow_stop() 36 | { 37 | flow_init(); 38 | flow_main_menu(); 39 | } 40 | 41 | void flow_start_user_confirm(const API_CTX *api, accept_cb_t accept_cb, 42 | reject_cb_t reject_cb, timeout_cb_t timeout_cb) 43 | { 44 | flow_init(); 45 | 46 | flow_data.api = api; 47 | 48 | flow_data.accept_cb = accept_cb; 49 | flow_data.reject_cb = reject_cb; 50 | flow_data.timeout_cb = timeout_cb; 51 | 52 | flow_data.flow_timer_start = timer_events; 53 | 54 | flow_data.flow_active = 1; 55 | 56 | flow_data.amount_toggle = 0; 57 | } 58 | 59 | void flow_timer_event() 60 | { 61 | if (flow_data.flow_active) { 62 | if ((timer_events - flow_data.flow_timer_start) >= 1000 /* 100s */) { 63 | if (flow_data.timeout_cb) { 64 | flow_data.timeout_cb(); 65 | } 66 | flow_stop(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * flow.h 3 | * 4 | * Created on: 21.10.2020 5 | * Author: thomas 6 | */ 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "api.h" 12 | 13 | // bech32: 62/63 bytes + 3 line breaks = 65/66 14 | // essence hash: 66 bytes + 3 line breaks = 69 15 | #define TMP_DATA_SIZE 80 16 | 17 | // typedef unsigned int (*ux_callback_cb_t)(const bagl_element_t *e); 18 | typedef void (*ux_fetch_data)(); 19 | 20 | typedef void (*accept_cb_t)(); 21 | typedef void (*reject_cb_t)(); 22 | typedef void (*timeout_cb_t)(); 23 | 24 | // struct that contains the data to be displayed on the screen 25 | // during confirming the outputs 26 | typedef struct { 27 | const API_CTX *api; 28 | 29 | // current index of output displayed 30 | short flow_outputs_index_current; 31 | 32 | // callbacks 33 | accept_cb_t accept_cb; 34 | reject_cb_t reject_cb; 35 | timeout_cb_t timeout_cb; 36 | 37 | int read_index; 38 | int num_non_remainder_outputs; 39 | 40 | // buffer for renderings of bech32 addresses, hashs, as temporary buffer, 41 | // ... 42 | char scratch[2][TMP_DATA_SIZE + 1]; // +1 zero terminator 43 | 44 | // total number of lines 45 | uint8_t number_of_lines; 46 | 47 | // toggles between short and full format of amount 48 | uint8_t amount_toggle; 49 | 50 | // for UI-100s-timeout 51 | uint32_t flow_timer_start; 52 | 53 | // flag that indicates the flow is active 54 | uint8_t flow_active; 55 | } flowdata_t; 56 | 57 | void flow_main_menu(void); 58 | 59 | void flow_start_user_confirm(const API_CTX *api, accept_cb_t accept_cb, 60 | reject_cb_t reject_cb, timeout_cb_t timeout_cb); 61 | 62 | 63 | void flow_init(void); 64 | 65 | void flow_stop(void); 66 | 67 | void flow_timer_event(void); 68 | -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm_blindsigning.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | #include "ui_common.h" 3 | 4 | #include "ux.h" 5 | #include "glyphs.h" 6 | 7 | #include "nv_mem.h" 8 | 9 | #include "flow_user_confirm.h" 10 | #include "flow_user_confirm_blindsigning.h" 11 | 12 | #pragma GCC diagnostic error "-Wall" 13 | #pragma GCC diagnostic error "-Wextra" 14 | 15 | extern flowdata_t flow_data; 16 | 17 | static void cb_hash_preinit(); 18 | 19 | static void cb_bs_accept(); 20 | static void cb_bs_reject(); 21 | 22 | static void cb_hash_preinit(); 23 | 24 | // clang-format off 25 | 26 | UX_STEP_NOCB_INIT( 27 | ux_step_hash, 28 | bn_paging, 29 | cb_hash_preinit(), 30 | { 31 | "Blind Signing", (const char*) flow_data.scratch[0] 32 | } 33 | ); 34 | 35 | UX_STEP_CB( 36 | ux_step_bs_accept, 37 | pb, 38 | cb_bs_accept(), 39 | { 40 | &C_x_icon_check, 41 | "Accept" 42 | } 43 | ); 44 | 45 | UX_STEP_CB( 46 | ux_step_bs_reject, 47 | pb, 48 | cb_bs_reject(), 49 | { 50 | &C_x_icon_cross, 51 | "Reject" 52 | } 53 | ); 54 | 55 | 56 | UX_FLOW( 57 | ux_flow_blindsigning, 58 | &ux_step_hash, 59 | &ux_step_bs_accept, 60 | &ux_step_bs_reject, 61 | FLOW_LOOP 62 | ); 63 | 64 | UX_STEP_CB( 65 | ux_step_bs_not_enabled, 66 | pbb, 67 | cb_bs_reject(), 68 | { 69 | &C_x_icon_cross, "Blind Signing", "not enabled!" 70 | } 71 | ); 72 | 73 | 74 | 75 | UX_FLOW( 76 | ux_flow_bs_not_enabled, 77 | &ux_step_bs_not_enabled, 78 | &ux_step_bs_reject, 79 | FLOW_LOOP 80 | ); 81 | 82 | 83 | // clang-format on 84 | 85 | 86 | void cb_hash_preinit() 87 | { 88 | const char *hex = "0123456789ABCDEF"; 89 | 90 | // generate hash 91 | memset(flow_data.scratch[0], 0, sizeof(flow_data.scratch[0])); 92 | 93 | const char *src = (const char *)flow_data.api->essence.hash; 94 | char *dst = flow_data.scratch[0]; 95 | 96 | *dst++ = '0'; 97 | *dst++ = 'x'; 98 | for (uint8_t i = 0; i < 32; i++) { 99 | *dst++ = hex[*src >> 4]; 100 | *dst++ = hex[*src++ & 0x0f]; 101 | } 102 | *dst = 0; 103 | } 104 | 105 | static void cb_bs_accept() 106 | { 107 | if (flow_data.accept_cb) { 108 | flow_data.accept_cb(); 109 | } 110 | flow_stop(); 111 | } 112 | 113 | static void cb_bs_reject() 114 | { 115 | if (flow_data.reject_cb) { 116 | flow_data.reject_cb(); 117 | } 118 | flow_stop(); 119 | } 120 | 121 | void flow_start_blindsigning(const API_CTX *api, accept_cb_t accept_cb, 122 | reject_cb_t reject_cb, timeout_cb_t timeout_cb) 123 | { 124 | flow_start_user_confirm(api, accept_cb, reject_cb, timeout_cb); 125 | 126 | if (!nv_get_blindsigning()) { 127 | ux_flow_init(0, ux_flow_bs_not_enabled, &ux_step_bs_not_enabled); 128 | } 129 | else { 130 | ux_flow_init(0, ux_flow_blindsigning, &ux_step_hash); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm_blindsigning.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "api.h" 4 | 5 | void flow_start_blindsigning(const API_CTX *api, accept_cb_t accept_cb, 6 | reject_cb_t reject_cb, timeout_cb_t timeout_cb); -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm_new_address.c: -------------------------------------------------------------------------------- 1 | #include "os.h" 2 | #include "ux.h" 3 | #include "glyphs.h" 4 | 5 | #include "ui_common.h" 6 | #include "flow_user_confirm.h" 7 | #include "flow_user_confirm_new_address.h" 8 | #include "abstraction.h" 9 | 10 | extern flowdata_t flow_data; 11 | 12 | #pragma GCC diagnostic error "-Wall" 13 | #pragma GCC diagnostic error "-Wextra" 14 | 15 | static void cb_address_preinit(); 16 | static void cb_bip32_preinit(); 17 | 18 | static void cb_accept(); 19 | 20 | // clang-format off 21 | UX_STEP_NOCB_INIT( 22 | ux_step_new_address, 23 | bn_paging, 24 | cb_address_preinit(), 25 | { 26 | // in paging mode, "New Remainder" doesn't fit without 27 | // wrapping in the next line 28 | (const char*) flow_data.scratch[1], (const char*) flow_data.scratch[0] 29 | } 30 | ); 31 | 32 | #ifdef TARGET_NANOS 33 | UX_STEP_NOCB_INIT( 34 | ux_step_na_bip32, 35 | bn_paging, 36 | cb_bip32_preinit(), 37 | { 38 | "BIP32 Path", (const char*) flow_data.scratch[0] 39 | } 40 | ); 41 | #else 42 | UX_STEP_NOCB_INIT( 43 | ux_step_na_bip32, 44 | bn, 45 | cb_bip32_preinit(), 46 | { 47 | "BIP32 Path", (const char*) flow_data.scratch[0] 48 | } 49 | ); 50 | #endif 51 | 52 | UX_STEP_CB( 53 | ux_step_ok, 54 | pb, 55 | cb_accept(), 56 | { 57 | &C_x_icon_check, 58 | "Ok" 59 | } 60 | ); 61 | 62 | // if paging flow is the first step then page 2 is shown when 63 | // jumping to the first step via FLOW_LOOP (on nanosplus and nanox). 64 | // Using the OK step as first step and starting 65 | // with the ux_step_new_address step fixes the issue on all devices 66 | UX_FLOW( 67 | ux_flow_new_address, 68 | &ux_step_ok, 69 | &ux_step_new_address, 70 | &ux_step_na_bip32, 71 | FLOW_LOOP 72 | ); 73 | 74 | // clang-format on 75 | 76 | static void cb_address_preinit() 77 | { 78 | // clear buffer 79 | memset(flow_data.scratch[0], 0, sizeof(flow_data.scratch[0])); 80 | memset(flow_data.scratch[1], 0, sizeof(flow_data.scratch[1])); 81 | 82 | // header 83 | if (flow_data.api->bip32_path[BIP32_CHANGE_INDEX] & 0x1) { 84 | #ifdef TARGET_NANOS 85 | strncpy(flow_data.scratch[1], "Remainder", 86 | sizeof(flow_data.scratch[1]) - 1); 87 | #else 88 | strncpy(flow_data.scratch[1], "New Remainder", 89 | sizeof(flow_data.scratch[1]) - 1); 90 | #endif 91 | } 92 | else { 93 | #ifdef TARGET_NANOS 94 | strncpy(flow_data.scratch[1], "Address", 95 | sizeof(flow_data.scratch[1]) - 1); 96 | #else 97 | strncpy(flow_data.scratch[1], "Receive Address", 98 | sizeof(flow_data.scratch[1]) - 1); 99 | #endif 100 | } 101 | 102 | // generate bech32 address including the address_type 103 | // we only have a single address in the buffer starting at index 0 104 | address_encode_bech32(flow_data.api, flow_data.api->data.buffer, 105 | flow_data.scratch[0], sizeof(flow_data.scratch[0])); 106 | } 107 | 108 | static void cb_bip32_preinit() 109 | { 110 | // clear buffer 111 | memset(flow_data.scratch[0], 0, sizeof(flow_data.scratch[0])); 112 | 113 | format_bip32_with_line_breaks(flow_data.api->bip32_path, 114 | flow_data.scratch[0], 115 | sizeof(flow_data.scratch[0])); 116 | } 117 | 118 | static void cb_accept() 119 | { 120 | if (flow_data.accept_cb) { 121 | flow_data.accept_cb(); 122 | } 123 | flow_stop(); 124 | } 125 | 126 | void flow_start_new_address(const API_CTX *api, accept_cb_t accept_cb, 127 | timeout_cb_t timeout_cb) 128 | { 129 | flow_start_user_confirm(api, accept_cb, 0, timeout_cb); 130 | 131 | ux_flow_init(0, ux_flow_new_address, &ux_step_new_address); 132 | } 133 | -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm_new_address.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void flow_start_new_address(const API_CTX *api, accept_cb_t accept_cb, 4 | timeout_cb_t timeout_cb); 5 | -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm_transaction.c: -------------------------------------------------------------------------------- 1 | /* 2 | * flow.c 3 | * 4 | * Created on: 21.10.2020 5 | * Author: thomas 6 | */ 7 | 8 | #include "os.h" 9 | #include "ux.h" 10 | 11 | #include "glyphs.h" 12 | 13 | #include "api.h" 14 | #include "macros.h" 15 | #include "flow_user_confirm_transaction.h" 16 | 17 | #include "abstraction.h" 18 | 19 | #include "iota/constants.h" 20 | #include "ui_common.h" 21 | 22 | #pragma GCC diagnostic error "-Wall" 23 | #pragma GCC diagnostic error "-Wextra" 24 | 25 | #define UNKNOWN 0 26 | #define NEW_ADDRESS 1 27 | #define REMAINDER 2 28 | #define OUTPUT 3 29 | 30 | 31 | /** 32 | * Define a flow step with autovalidation after a given timeout (in ms) 33 | * with additional pre-init. Used for toggeling the IOTA amounts between short 34 | * and full format 35 | */ 36 | #define UX_STEP_TIMEOUT_INIT(stepname, layoutkind, preinit, timeout_ms, \ 37 | validate_flow, ...) \ 38 | void stepname##_init(unsigned int stack_slot) \ 39 | { \ 40 | preinit; \ 41 | ux_layout_##layoutkind##_init(stack_slot); \ 42 | ux_layout_set_timeout(stack_slot, timeout_ms); \ 43 | } \ 44 | const ux_layout_##layoutkind##_params_t stepname##_val = __VA_ARGS__; \ 45 | const ux_flow_step_t stepname = { \ 46 | stepname##_init, \ 47 | &stepname##_val, \ 48 | validate_flow, \ 49 | NULL, \ 50 | } 51 | 52 | 53 | extern flowdata_t flow_data; 54 | 55 | static void cb_accept(); 56 | static void cb_reject(); 57 | static void cb_continue_claiming(); 58 | 59 | static void cb_address_preinit(); 60 | static void cb_amount_preinit(); 61 | static void cb_output_preinit(); 62 | static void cb_bip32_preinit(); 63 | 64 | static void cb_switch(); 65 | static void cb_back(); 66 | 67 | static void cb_next_dataset(); 68 | static void cb_prev_dataset(); 69 | 70 | static void get_read_index(); 71 | 72 | 73 | //------------------------------------------------------------------------ 74 | // clang-format off 75 | 76 | // review output [...] 77 | UX_STEP_NOCB_INIT( 78 | ux_step_review, 79 | bb, 80 | cb_output_preinit(), 81 | { 82 | (const char*) flow_data.scratch[1], (const char*) flow_data.scratch[0] 83 | } 84 | ); 85 | 86 | UX_STEP_NOCB_INIT( 87 | ux_step_amount, 88 | bn, 89 | cb_amount_preinit(), 90 | { 91 | (const char*) flow_data.scratch[1], (const char*) flow_data.scratch[0] 92 | 93 | } 94 | ); 95 | 96 | UX_STEP_NOCB_INIT( 97 | ux_step_address, 98 | bn_paging, 99 | cb_address_preinit(), 100 | { 101 | "Address", (const char*) flow_data.scratch[0] 102 | } 103 | ); 104 | 105 | #ifdef TARGET_NANOS 106 | UX_STEP_NOCB_INIT( 107 | ux_step_remainder, 108 | bn_paging, 109 | cb_bip32_preinit(), 110 | { 111 | "BIP32 Path", (const char*) flow_data.scratch[0] 112 | } 113 | ); 114 | #else 115 | UX_STEP_NOCB_INIT( 116 | ux_step_remainder, 117 | bn, 118 | cb_bip32_preinit(), 119 | { 120 | "BIP32 Path", (const char*) flow_data.scratch[0] 121 | } 122 | ); 123 | #endif 124 | 125 | UX_STEP_INIT( 126 | ux_step_switch, 127 | NULL, 128 | NULL, 129 | cb_switch() 130 | ); 131 | 132 | UX_STEP_INIT( 133 | ux_step_data_next, 134 | NULL, 135 | NULL, 136 | cb_next_dataset() 137 | ); 138 | 139 | UX_STEP_INIT( 140 | ux_step_data_prev, 141 | NULL, 142 | NULL, 143 | cb_prev_dataset() 144 | ); 145 | 146 | UX_STEP_INIT( 147 | ux_step_back, 148 | NULL, 149 | NULL, 150 | cb_back() 151 | ); 152 | //------------------------------------------------------------------------ 153 | 154 | UX_STEP_CB( 155 | ux_step_accept, 156 | pb, 157 | cb_accept(), 158 | { 159 | &C_x_icon_check, 160 | "Accept" 161 | } 162 | ); 163 | 164 | UX_STEP_CB( 165 | ux_step_reject, 166 | pb, 167 | cb_reject(), 168 | { 169 | &C_x_icon_cross, 170 | "Reject" 171 | } 172 | ); 173 | 174 | UX_FLOW( 175 | ux_flow_base, 176 | &ux_step_data_prev, 177 | &ux_step_review, 178 | &ux_step_address, 179 | &ux_step_amount, 180 | &ux_step_switch, 181 | &ux_step_data_next, 182 | FLOW_LOOP 183 | ); 184 | 185 | UX_FLOW( 186 | ux_flow_has_accept_reject, 187 | &ux_step_back, 188 | &ux_step_accept, 189 | &ux_step_reject, 190 | &ux_step_data_next, 191 | FLOW_LOOP 192 | ); 193 | 194 | //-------------------------------------------- 195 | // SMR claiming 196 | 197 | UX_STEP_NOCB( 198 | ux_step_srm_claiming_start, 199 | pb, 200 | { 201 | &C_icon_warning, 202 | "Claim SMR" 203 | } 204 | ); 205 | 206 | UX_STEP_NOCB( 207 | ux_step_smr_claiming_message, 208 | bn_paging, 209 | { 210 | "Claim SMR", "In order to claim SMR coins, you are now " 211 | "signing with IOTA private keys instead of SMR " 212 | "private keys. " 213 | "Are you really sure you want to proceed?" 214 | } 215 | ); 216 | 217 | UX_STEP_CB( 218 | ux_step_continue, 219 | pb, 220 | cb_continue_claiming(), 221 | { 222 | &C_x_icon_check, 223 | "Continue" 224 | } 225 | ); 226 | 227 | UX_STEP_CB( 228 | ux_step_cancel, 229 | pb, 230 | cb_reject(), 231 | { 232 | &C_x_icon_cross, 233 | "Cancel" 234 | } 235 | ); 236 | 237 | UX_FLOW( 238 | ux_flow_smr_claiming_start, 239 | &ux_step_srm_claiming_start, 240 | &ux_step_smr_claiming_message, 241 | &ux_step_continue, 242 | &ux_step_cancel, 243 | FLOW_LOOP 244 | ); 245 | 246 | //-------------------------------------------- 247 | // internal_transfer Transaction 248 | 249 | UX_STEP_NOCB( 250 | ux_step_internal_transfer_start, 251 | pbb, 252 | { 253 | &C_x_icon_info, 254 | "Internal", 255 | "Transfer" 256 | } 257 | ); 258 | 259 | UX_STEP_NOCB( 260 | ux_step_internal_transfer_info, 261 | nn, 262 | { 263 | "Info: All coins ", "remain on the wallet" 264 | } 265 | ); 266 | 267 | UX_FLOW( 268 | ux_flow_internal_transfer, 269 | &ux_step_internal_transfer_start, 270 | &ux_step_internal_transfer_info, 271 | &ux_step_accept, 272 | &ux_step_reject, 273 | FLOW_LOOP 274 | ); 275 | // clang-format on 276 | 277 | static void cb_switch() 278 | { 279 | if (flow_data.flow_outputs_index_current == 280 | flow_data.num_non_remainder_outputs - 1) { 281 | ux_flow_init(0, ux_flow_has_accept_reject, &ux_step_accept); 282 | } 283 | else { 284 | ux_flow_init(0, ux_flow_base, &ux_step_data_next); 285 | } 286 | } 287 | 288 | static void cb_next_dataset() 289 | { 290 | flow_data.flow_outputs_index_current++; 291 | if (flow_data.flow_outputs_index_current >= 292 | flow_data.num_non_remainder_outputs) { 293 | flow_data.flow_outputs_index_current = 0; 294 | } 295 | get_read_index(); 296 | 297 | ux_flow_init(0, ux_flow_base, &ux_step_review); 298 | } 299 | 300 | static void cb_prev_dataset() 301 | { 302 | flow_data.flow_outputs_index_current--; 303 | if (flow_data.flow_outputs_index_current < 0) { 304 | flow_data.flow_outputs_index_current = 305 | flow_data.num_non_remainder_outputs - 1; 306 | } 307 | get_read_index(); 308 | 309 | if (flow_data.flow_outputs_index_current == 310 | flow_data.num_non_remainder_outputs - 1) { 311 | ux_flow_init(0, ux_flow_has_accept_reject, &ux_step_reject); 312 | } 313 | else { 314 | ux_flow_init(0, ux_flow_base, &ux_step_amount); 315 | } 316 | } 317 | 318 | static void cb_back() 319 | { 320 | ux_flow_init(0, ux_flow_base, &ux_step_amount); 321 | } 322 | 323 | static void cb_accept() 324 | { 325 | if (flow_data.accept_cb) { 326 | flow_data.accept_cb(); 327 | } 328 | flow_stop(); 329 | } 330 | 331 | static void cb_reject() 332 | { 333 | if (flow_data.reject_cb) { 334 | flow_data.reject_cb(); 335 | } 336 | flow_stop(); 337 | } 338 | 339 | static void cb_continue_claiming() 340 | { 341 | // user acknodlwedged to continue 342 | // now start the actual transaction confirming flow 343 | ux_flow_init(0, ux_flow_base, &ux_step_review); 344 | } 345 | 346 | static void cb_amount_preinit() 347 | { 348 | // clear buffer 349 | memset(flow_data.scratch[0], 0, sizeof(flow_data.scratch[0])); 350 | 351 | MUST_THROW(get_amount(flow_data.api, flow_data.read_index, 352 | flow_data.scratch[0], sizeof(flow_data.scratch[0]))); 353 | 354 | // copy header after writing amount 355 | if (flow_data.api->coin == COIN_SHIMMER) { 356 | strncpy(flow_data.scratch[1], "Amount SMR", 357 | sizeof(flow_data.scratch[1])); 358 | } 359 | else { 360 | strncpy(flow_data.scratch[1], "Amount IOTA", 361 | sizeof(flow_data.scratch[1])); 362 | } 363 | } 364 | 365 | static void cb_bip32_preinit() 366 | { 367 | // clear buffer 368 | memset(flow_data.scratch[0], 0, sizeof(flow_data.scratch[0])); 369 | 370 | format_bip32_with_line_breaks(flow_data.api->bip32_path, 371 | flow_data.scratch[0], 372 | sizeof(flow_data.scratch[0])); 373 | } 374 | 375 | static void cb_address_preinit() 376 | { 377 | // clear buffer 378 | memset(flow_data.scratch[0], 0, sizeof(flow_data.scratch[0])); 379 | memset(flow_data.scratch[1], 0, sizeof(flow_data.scratch[1])); 380 | 381 | const uint8_t *address_with_type_ptr = 0; 382 | 383 | MUST_THROW(address_with_type_ptr = 384 | get_output_address_ptr(flow_data.api, flow_data.read_index)); 385 | 386 | 387 | // generate bech32 address including the address_type 388 | // since the struct is packed, the address follows directly the address_type 389 | address_encode_bech32(flow_data.api, address_with_type_ptr, 390 | flow_data.scratch[0], sizeof(flow_data.scratch[0])); 391 | } 392 | 393 | static void cb_output_preinit() 394 | { 395 | // clear buffer 396 | memset(flow_data.scratch[0], 0, sizeof(flow_data.scratch[0])); 397 | memset(flow_data.scratch[1], 0, sizeof(flow_data.scratch[1])); 398 | 399 | if (flow_data.api->app_mode == APP_MODE_SHIMMER_CLAIMING) { 400 | strcpy(flow_data.scratch[1], "Claim SMR"); 401 | } 402 | else { 403 | strcpy(flow_data.scratch[1], "Review"); 404 | } 405 | 406 | // more than one? Show with numbers on the UI 407 | if (flow_data.num_non_remainder_outputs > 1) { 408 | snprintf(flow_data.scratch[0], sizeof(flow_data.scratch[0]) - 1, 409 | "Output [%d]", flow_data.flow_outputs_index_current + 1); 410 | } 411 | else { 412 | strcpy(flow_data.scratch[0], "Output"); 413 | } 414 | } 415 | 416 | static void get_read_index() 417 | { 418 | flow_data.read_index = flow_data.flow_outputs_index_current; 419 | 420 | // no remainder, we are finished here 421 | if (!flow_data.api->essence.has_remainder) { 422 | return; 423 | } 424 | 425 | // yes, we have to skip it 426 | if (flow_data.read_index >= flow_data.api->essence.remainder_index) { 427 | flow_data.read_index++; 428 | } 429 | } 430 | 431 | void flow_start_user_confirm_transaction(const API_CTX *api, 432 | accept_cb_t accept_cb, 433 | reject_cb_t reject_cb, 434 | timeout_cb_t timeout_cb) 435 | { 436 | flow_start_user_confirm(api, accept_cb, reject_cb, timeout_cb); 437 | 438 | // how many non-remainder outputs are there? 439 | // this is safe because essence with only one remainder is covered by 440 | // "internal transfer" flow 441 | flow_data.flow_outputs_index_current = 0; 442 | flow_data.num_non_remainder_outputs = 443 | flow_data.api->essence.outputs_count - 444 | !!flow_data.api->essence.has_remainder; 445 | 446 | // internal transfer means that no coins leave the wallet. 447 | // - essence with a single output address that matches one of the input 448 | // addresses or 449 | // - essence with only one single remainder output (special case because no 450 | // IOTA library generates such essences, but it is valid) 451 | if (api->essence.is_internal_transfer || 452 | (api->essence.has_remainder && api->essence.outputs_count == 1)) { 453 | // there is no security risk because coins remain on the wallet 454 | ux_flow_init(0, ux_flow_internal_transfer, 455 | &ux_step_internal_transfer_start); 456 | return; 457 | } 458 | 459 | get_read_index(); 460 | 461 | if (api->app_mode == APP_MODE_SHIMMER_CLAIMING) { 462 | // show claiming smr message before starting regular flow 463 | ux_flow_init(0, ux_flow_smr_claiming_start, 464 | &ux_step_srm_claiming_start); 465 | return; 466 | } 467 | 468 | // start regular flow 469 | ux_flow_init(0, ux_flow_base, &ux_step_review); 470 | } 471 | -------------------------------------------------------------------------------- /src/ui/nano/flow_user_confirm_transaction.h: -------------------------------------------------------------------------------- 1 | /* 2 | * flow.h 3 | * 4 | * Created on: 21.10.2020 5 | * Author: thomas 6 | */ 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "api.h" 12 | 13 | #include "flow_user_confirm.h" 14 | 15 | void flow_start_user_confirm_transaction(const API_CTX *api, 16 | accept_cb_t accept_cb, 17 | reject_cb_t reject_cb, 18 | timeout_cb_t timeout_cb); 19 | -------------------------------------------------------------------------------- /src/ui/ui.c: -------------------------------------------------------------------------------- 1 | 2 | #include "ui.h" 3 | 4 | #include "nano/flow_user_confirm.h" 5 | #include "nano/flow_main_menu.h" 6 | 7 | #pragma GCC diagnostic error "-Wall" 8 | #pragma GCC diagnostic error "-Wextra" 9 | #pragma GCC diagnostic error "-Wmissing-prototypes" 10 | 11 | void ui_reset() 12 | { 13 | } 14 | 15 | void ui_init() 16 | { 17 | flow_init(); 18 | flow_main_menu(); 19 | } 20 | 21 | void ui_timer_event() 22 | { 23 | flow_timer_event(); 24 | } 25 | -------------------------------------------------------------------------------- /src/ui/ui.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_H 2 | #define UI_H 3 | 4 | #include 5 | 6 | void ui_init(void); 7 | void ui_reset(void); 8 | void ui_user_confirm_essence(void); 9 | 10 | void ui_timer_event(void); 11 | 12 | uint8_t ui_show(uint8_t flow); 13 | 14 | #endif // UI_H 15 | -------------------------------------------------------------------------------- /src/ui/ui_common.c: -------------------------------------------------------------------------------- 1 | #include "ui/ui_common.h" 2 | #include 3 | #include 4 | #include "api.h" 5 | #include "macros.h" 6 | #include "os.h" 7 | 8 | #define BIP32_LINE_LENGTH 22 9 | 10 | #pragma GCC diagnostic error "-Wall" 11 | #pragma GCC diagnostic error "-Wextra" 12 | #pragma GCC diagnostic error "-Wmissing-prototypes" 13 | 14 | /// the largest power of 10 that still fits into int32 15 | #define MAX_INT_DEC UINT64_C(1000000000) 16 | 17 | // the max number of iota units 18 | #define MAX_IOTA_UNIT 6 19 | 20 | /// the different IOTA units 21 | static const char IOTA_UNITS[MAX_IOTA_UNIT][3] = {"i", "Ki", "Mi", 22 | "Gi", "Ti", "Pi"}; 23 | 24 | /// Groups the string by adding a comma every 3 chars from the right. 25 | static size_t str_add_commas(char *dst, const char *src, size_t num_len) 26 | { 27 | char *p_dst = dst; 28 | const char *p_src = src; 29 | 30 | // ignore leading minus 31 | if (*p_src == '-') { 32 | *p_dst++ = *p_src++; 33 | num_len--; 34 | } 35 | for (int commas = 2 - num_len % 3; *p_src; commas = (commas + 1) % 3) { 36 | *p_dst++ = *p_src++; 37 | if (commas == 1) { 38 | *p_dst++ = ','; 39 | } 40 | } 41 | // remove the last comma and zero-terminate 42 | *--p_dst = '\0'; 43 | 44 | return (p_dst - dst); 45 | } 46 | 47 | /** @brief Writes signed integer to string. 48 | * @return the number of chars that have been written 49 | */ 50 | static size_t format_u64(char *s, const size_t n, const uint64_t val) 51 | { 52 | // we cannot display the full range of uint64 with this function 53 | if (val >= MAX_INT_DEC * MAX_INT_DEC) { 54 | THROW(INVALID_PARAMETER); 55 | } 56 | 57 | if (val < MAX_INT_DEC) { 58 | snprintf(s, n, "%u", (uint32_t)val); 59 | } 60 | else { 61 | // emulate printing of integers larger than 32 bit 62 | snprintf(s, n, "%u%09u", (uint32_t)(val / MAX_INT_DEC), 63 | (int)(val % MAX_INT_DEC)); 64 | } 65 | return strnlen(s, n); 66 | } 67 | 68 | void format_value_full(char *s, const unsigned int n, const uint64_t val) 69 | { 70 | // longest u64 string in buffer can be: "18446744073709551615\0" 71 | char buffer[21]; 72 | 73 | const size_t num_len = format_u64(buffer, sizeof(buffer), val); 74 | const size_t num_len_comma = num_len + (num_len - 1) / 3; 75 | 76 | // if the length with commas plus the unit does not fit 77 | // +3 = max unit length (i, Mi, Gi, Ti) 78 | if (num_len_comma + 3 > n) { 79 | snprintf(s, n, "%s %s", buffer, IOTA_UNITS[0]); 80 | } 81 | else { 82 | const size_t chars_written = str_add_commas(s, buffer, num_len); 83 | snprintf(s + chars_written, n - chars_written, " %s", IOTA_UNITS[0]); 84 | } 85 | } 86 | 87 | void format_value_full_decimals(char *s, const unsigned int n, 88 | const uint64_t val) 89 | { 90 | // longest u64 string in buffer can be: "18446744073709551615\0" 91 | char buffer[21]; 92 | 93 | const size_t num_len = format_u64(buffer, sizeof(buffer), val); 94 | 95 | // not enough space 96 | if (n < num_len + 2) { 97 | THROW(SW_UNKNOWN); 98 | } 99 | 100 | // format 0,xxxxxx 101 | if (val < 1000000ull) { 102 | snprintf(s, n, "0.%06u", (uint32_t)val); 103 | return; 104 | } 105 | 106 | // format yyyy,xxxxxx 107 | // insert comma at the right spot during copying 108 | char *src = buffer; 109 | char *dst = s; 110 | for (size_t i = 0; i < num_len; i++) { 111 | if (i == num_len - 6) { 112 | *dst++ = '.'; 113 | } 114 | *dst++ = *src++; 115 | } 116 | *dst = 0; 117 | } 118 | 119 | 120 | void format_value_short(char *s, const unsigned int n, uint64_t val) 121 | { 122 | if (val < 1000) { 123 | snprintf(s, n, "%u %s", (uint32_t)(val), IOTA_UNITS[0]); 124 | return; 125 | } 126 | 127 | unsigned int base = 1; 128 | while (val >= 1000 * 1000) { 129 | val /= 1000; 130 | base++; 131 | } 132 | if (base >= MAX_IOTA_UNIT) { 133 | THROW(INVALID_PARAMETER); 134 | } 135 | 136 | snprintf(s, n, "%u.%03u %s", (uint32_t)(val / 1000), (uint32_t)(val % 1000), 137 | IOTA_UNITS[base]); 138 | } 139 | 140 | // returns the length of hex string 141 | static int hex_len(uint32_t v) 142 | { 143 | int len = 8; 144 | while (v) { 145 | if ((v & 0xf0000000)) { 146 | return len; 147 | } 148 | v <<= 4; 149 | len -= 1; 150 | } 151 | return 1; 152 | } 153 | 154 | // format bip path to string 155 | // doesn't use a local buffer but generates the string 156 | // fitting in LINE_WIDTH characters directly 157 | static int format_bip32(const uint32_t *b32, int linenr, char *out, 158 | uint32_t out_max_len) 159 | { 160 | int len[BIP32_PATH_LEN] = {0}; 161 | for (int i = 0; i < BIP32_PATH_LEN; i++) { 162 | len[i] = hex_len(b32[i] & 0x7fffffff); 163 | if (i != BIP32_PATH_LEN - 1) { 164 | len[i] += 2; // gets added: '/ 165 | } 166 | else { 167 | len[i] += 1; // gets added: ' 168 | } 169 | } 170 | 171 | int ofs = 0; 172 | int curlen = 0; 173 | int lines = 0; 174 | 175 | if (out) { 176 | out[0] = 0; 177 | } 178 | for (int i = 0; i < BIP32_PATH_LEN; i++) { 179 | if (curlen + len[i] > BIP32_LINE_LENGTH) { 180 | curlen = 0; 181 | lines++; 182 | } 183 | if (lines == linenr) { 184 | if (out) { 185 | snprintf(&out[ofs], out_max_len, 186 | (i != BIP32_PATH_LEN - 1) ? "%x'/" : "%x'", 187 | b32[i] & 0x7fffffff); 188 | } 189 | ofs += len[i]; 190 | out_max_len -= len[i]; 191 | } 192 | curlen += len[i]; 193 | } 194 | return ofs; 195 | } 196 | 197 | int format_bip32_with_line_breaks(const uint32_t *b32, char *out, 198 | int out_max_len) 199 | { 200 | int ofs = 0; 201 | int written = 0; 202 | int last_zero = 0; 203 | 204 | // maximum of 3 lines 205 | for (int i = 0; i < 3; i++) { 206 | written = format_bip32(b32, i, &out[ofs], out_max_len); 207 | 208 | if (!written) { 209 | break; 210 | } 211 | if (last_zero) { 212 | // if previously something was written, we have to replace \0 with 213 | // \n 214 | out[last_zero] = '\n'; 215 | } 216 | ofs += written; 217 | out_max_len -= written; 218 | 219 | last_zero = ofs; 220 | 221 | // skip \0 222 | ofs++; 223 | out_max_len--; 224 | } 225 | out[ofs] = 0; 226 | return ofs; 227 | } 228 | 229 | int string_insert_chars_each(const char *src, uint32_t src_size, char *dst, 230 | uint32_t dst_size, int insert_after, int count, 231 | char c) 232 | { 233 | uint32_t src_len = strnlen(src, src_size); 234 | 235 | // enough space? 236 | if (dst_size < src_len + (src_len / insert_after) + 1) { 237 | return 0; 238 | } 239 | 240 | int ctr = 0; 241 | for (uint32_t i = 0; i < src_len; i++) { 242 | *dst++ = *src++; 243 | ctr++; 244 | if (count > 0 && ctr == insert_after) { 245 | ctr = 0; 246 | count--; 247 | *dst++ = c; 248 | } 249 | } 250 | *dst = 0; 251 | return 1; 252 | } 253 | -------------------------------------------------------------------------------- /src/ui/ui_common.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_COMMON_H 2 | #define UI_COMMON_H 3 | 4 | #include 5 | 6 | /** @brief Returns the tx index corresponding to the current menu entry. 7 | * Meta transactions, i.e. transactions with 0-value, are skipped. 8 | */ 9 | unsigned int ui_state_get_tx_index(void); 10 | 11 | /** @brief Writes formatted value in base iotas without commas to string. 12 | * Ex. 3040981551 i 13 | * @param s Pointer to a buffer where the resulting C-string is stored 14 | * @param n Maximum number of bytes to be used in the buffer 15 | * @param val Signed value to be formated 16 | */ 17 | void format_value_full(char *s, unsigned int n, uint64_t val); 18 | 19 | /** @brief Writes formatted value in short form with units. 20 | * Ex. 3.040 Gi 21 | * @param s Pointer to a buffer where the resulting C-string is stored 22 | * @param n Maximum number of bytes to be used in the buffer 23 | * @param val Signed value to be formated 24 | */ 25 | void format_value_short(char *s, unsigned int n, uint64_t val); 26 | 27 | void format_value_full_decimals(char *s, const unsigned int n, 28 | const uint64_t val); 29 | 30 | int format_bip32_with_line_breaks(const uint32_t *b32, char *out, 31 | int out_max_len); 32 | 33 | int string_insert_chars_each(const char *src, uint32_t src_size, char *dst, 34 | uint32_t dst_size, int insert_after, int count, 35 | char c); 36 | 37 | #endif // UI_COMMON_H 38 | -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM wollac/ledger-bolos AS test-app 2 | 3 | ENV DEVICE=nanos 4 | 5 | # switch to non-interactive 6 | RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 7 | 8 | RUN apt-get update && \ 9 | apt-get install -y --no-install-recommends \ 10 | cmake qemu-user-static \ 11 | python3-construct python3-jsonschema python3-mnemonic python3-pyelftools \ 12 | gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gdb-multiarch pkg-config \ 13 | libssl-dev protobuf-compiler lld-7 14 | 15 | COPY . /root/git/ledger-iota-app/ 16 | 17 | WORKDIR /usr/bin 18 | RUN ln -s clang-7 clang 19 | 20 | # build speculos simulator 21 | WORKDIR /root/git/ledger-iota-app/dev/speculos 22 | RUN cmake -Bbuild -H. && make -C build/ 23 | 24 | WORKDIR /root/git/ledger-iota-app 25 | 26 | RUN echo "#!/bin/bash\n" \ 27 | "export DEVICE=nanos\n" \ 28 | "export BOLOS_SDK=/root/git/ledger-iota-app/dev/sdk/nanos-secure-sdk\n" \ 29 | "unset BOLOS_ENV\n" \ 30 | "export CLANGPATH=/usr/bin/\n" \ 31 | "export GCCPATH=/usr/bin/\n" \ 32 | "echo nanos > device.txt\n" > env_nanos.sh 33 | 34 | RUN sed 's|nanos|nanox|g' env_nanos.sh > env_nanox.sh 35 | 36 | WORKDIR /root/git/ledger-iota-app/tests 37 | RUN gcc tests.c -o tests 38 | 39 | #EXPOSE 9999 40 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Testdata 2 | 3 | The test data was generated using a reference application that records all APDU commands 4 | and answers from the IOTA app running in a simulator. 5 | 6 | Generating test data needs a running Speculos simulator. [Here](https://github.com/iotaledger/ledger-iota-app/docker) are 7 | instructions how to setup one for the Ledger Nano S or X. 8 | 9 | Afterwards, you can build and execute the reference test programm: 10 | 11 | ``` 12 | $ git clone https://github.com/iotaledger/ledger.rs 13 | $ cargo build --examples 14 | $ ./target/debug/examples/cli -s -n -r reference.bin -f bin 15 | ``` 16 | 17 | The generated file can be replayed by using the mini C test program. The test program doesn't have any special requirements or dependencies. It should just compile fine by simply building it with `gcc tests.c -o tests` . 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/build_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR=$(dirname "$0") 3 | if [ $SCRIPT_DIR == "./docker" ]; then 4 | DOCKERFILE_PATH="docker/Dockerfile" 5 | CONTEXT_PATH="." 6 | else 7 | DOCKERFILE_PATH="Dockerfile" 8 | CONTEXT_PATH=".." 9 | fi 10 | docker build -t test-app $CONTEXT_PATH -f $DOCKERFILE_PATH 11 | -------------------------------------------------------------------------------- /tests/chain/iota/reference_nanos.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/tests/chain/iota/reference_nanos.bin -------------------------------------------------------------------------------- /tests/chain/iota/reference_nanosplus.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/tests/chain/iota/reference_nanosplus.bin -------------------------------------------------------------------------------- /tests/chain/iota/reference_nanox.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/tests/chain/iota/reference_nanox.bin -------------------------------------------------------------------------------- /tests/chain/shimmer/reference_nanos.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/tests/chain/shimmer/reference_nanos.bin -------------------------------------------------------------------------------- /tests/chain/shimmer/reference_nanosplus.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/tests/chain/shimmer/reference_nanosplus.bin -------------------------------------------------------------------------------- /tests/chain/shimmer/reference_nanox.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotaledger/ledger-iota-app/cb0be0089a7791dba2a0a669983469f40964f81e/tests/chain/shimmer/reference_nanox.bin -------------------------------------------------------------------------------- /tests/run_tests_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo 3 | echo "testing NANO S" 4 | docker run -it test-app bash -c "cd /root/git/ledger-iota-app/tests; chmod a+x test_headless.sh; ./test_headless.sh -m nanos" || exit 1 5 | echo 6 | echo "testing NANO X" 7 | docker run -it test-app bash -c "cd /root/git/ledger-iota-app/tests; chmod a+x test_headless.sh; ./test_headless.sh -m nanox" || exit 1 8 | -------------------------------------------------------------------------------- /tests/test_headless.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | function error { 3 | echo "error: $1" 4 | exit 1 5 | } 6 | 7 | function usage { 8 | echo "usage: $0 [-h] [-m|--model (nanos|nanox)]" 9 | exit 1 10 | } 11 | 12 | # default 13 | device="nanos" 14 | realpath="$( dirname $( readlink -f $0 ) )" 15 | 16 | 17 | FLAGS="" 18 | while (($# > 0)) 19 | do 20 | case "$1" in 21 | "-h" | "--help") 22 | usage 23 | ;; 24 | "-m" | "--model") 25 | shift 26 | device="$1" 27 | ;; 28 | *) 29 | error "unknown parameter" 30 | ;; 31 | esac 32 | shift 33 | 34 | done 35 | 36 | [[ "$device" != "nanos" && "$device" != "nanox" ]] && { 37 | error "unknown device" 38 | } 39 | 40 | [[ "$device" == "nanos" ]] && { 41 | sdk="2.0" 42 | } 43 | 44 | [[ "$device" == "nanox" ]] && { 45 | sdk="1.2" 46 | } 47 | 48 | 49 | echo "device $device selected" 50 | 51 | cd $realpath 52 | 53 | # recompile the app 54 | cd .. 55 | source env_${device}.sh 56 | make clean 57 | SPECULOS=1 make 58 | 59 | # start speculos in headless mode in the background 60 | cd ./dev/speculos 61 | python3.8 speculos.py --sdk $sdk --display headless -m ${device} ../../bin/app.elf &>/dev/null & 62 | speculos_pid=$! 63 | cd - 64 | 65 | # wait for speculos to be ready 66 | sleep 10 67 | #netstat -nlp 68 | 69 | # now compile test code and run test 70 | cd tests 71 | gcc tests.c -o tests 72 | ./tests reference_${device}.bin || error "testing failed for $device" 73 | 74 | # kill speculos 75 | kill $speculos_pid 76 | 77 | cd - 78 | 79 | exit 0 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /tests/tests.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Thomas Pototschnig 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software distributed 11 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | specific language governing permissions and limitations under the License. 14 | 15 | 16 | 17 | You need a running simulator for the tests. Instructions e.g. here: 18 | 19 | https://gitlab.com/ledger-iota-chrysalis/ledger-iota-app-docker 20 | 21 | */ 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | typedef struct { 32 | uint8_t *data; 33 | size_t data_len; 34 | size_t read_index; 35 | } stream_t; 36 | 37 | static stream_t data_stream; 38 | 39 | void get_data(uint8_t *dst, size_t dst_len, size_t max_len) 40 | { 41 | if (data_stream.read_index + dst_len > data_stream.data_len) { 42 | printf("invalid copy!\n"); 43 | exit(1); 44 | } 45 | if (dst_len > max_len) { 46 | printf("destination too small\n"); 47 | exit(1); 48 | } 49 | memcpy(dst, &data_stream.data[data_stream.read_index], dst_len); 50 | data_stream.read_index += dst_len; 51 | } 52 | 53 | 54 | uint32_t le2be(uint32_t v) 55 | { 56 | return (v & 0xff000000) >> 24 | (v & 0x00ff0000) >> 8 | 57 | (v & 0x0000ff00) << 8 | (v & 0x000000ff) << 24; 58 | } 59 | 60 | uint32_t be2le(uint32_t v) 61 | { 62 | return le2be(v); 63 | } 64 | 65 | void dump(uint8_t *data, size_t len) 66 | { 67 | for (size_t i = 0; i < len; i++) { 68 | if (!(i % 16) && i != 0) { 69 | printf("\n"); 70 | } 71 | printf("%02x ", data[i]); 72 | } 73 | printf("\n"); 74 | } 75 | 76 | // patch the version in the reference bin files 77 | // this is done to be able to use older testfiles with newer compilations to 78 | // find degradations in the code. When generating new testfiles after compiling, 79 | // testfiles could be wrong and this possibly wouldn't be noticed. 80 | void patch_app_version(uint8_t *command, uint8_t command_len, uint8_t *answer, uint8_t answer_len, uint8_t* buffer) { 81 | const uint8_t cmd_get_app_config[5] = {0x7b, 0x10, 0x00, 0x00, 0x00}; 82 | 83 | if (command_len == 5 && answer_len == 8) { 84 | if (!memcmp(command, cmd_get_app_config, command_len)) { 85 | // accept any app version 86 | buffer[0] = answer[0]; 87 | buffer[1] = answer[1]; 88 | buffer[2] = answer[2]; 89 | buffer[3] = answer[3] = 0; // flags that is unused; was uninitialized before app 0.6.3 90 | } 91 | } 92 | } 93 | 94 | void replay(int sockfd, uint8_t *data, size_t data_len) 95 | { 96 | int lastperc = 0; 97 | 98 | data_stream.data = data; 99 | data_stream.data_len = data_len; 100 | data_stream.read_index = 0; 101 | 102 | while (data_stream.data_len - data_stream.read_index) { 103 | uint8_t command_buffer[256] = {0}; 104 | uint8_t answer_buffer[256] = {0}; 105 | uint8_t buffer[256] = {0}; 106 | 107 | uint32_t command_len; 108 | get_data((uint8_t *)&command_len, sizeof(uint32_t), sizeof(uint32_t)); 109 | get_data(command_buffer, command_len, sizeof(command_buffer)); 110 | 111 | uint32_t answer_len; 112 | get_data((uint8_t *)&answer_len, sizeof(uint32_t), sizeof(uint32_t)); 113 | if (answer_len > sizeof(answer_buffer)) { 114 | printf("answer buffer too small\n"); 115 | exit(1); 116 | } 117 | get_data(answer_buffer, answer_len, sizeof(answer_buffer)); 118 | 119 | uint32_t speculos_len = le2be(command_len); 120 | write(sockfd, &speculos_len, sizeof(uint32_t)); 121 | write(sockfd, command_buffer, command_len); 122 | 123 | // dump((uint8_t*) &speculos_len, sizeof(uint32_t)); 124 | // dump((uint8_t*) command_buffer, command_len); 125 | 126 | read(sockfd, &speculos_len, sizeof(uint32_t)); 127 | // dump((uint8_t*) &speculos_len, sizeof(uint32_t)); 128 | 129 | speculos_len = be2le(speculos_len) + 2; 130 | read(sockfd, buffer, speculos_len); 131 | 132 | if (speculos_len != answer_len) { 133 | printf("answer size mismatch!\n"); 134 | exit(1); 135 | } 136 | 137 | // dump((uint8_t*) buffer, answer_len); 138 | 139 | patch_app_version(command_buffer, command_len, answer_buffer, answer_len, buffer); 140 | 141 | if (memcmp(buffer, answer_buffer, answer_len)) { 142 | printf("data mismatch!\n"); 143 | printf("request: "); dump((uint8_t*) command_buffer, command_len); 144 | printf("response: "); dump((uint8_t*) answer_buffer, answer_len); 145 | printf("is: "); dump((uint8_t*) buffer, answer_len); 146 | 147 | exit(1); 148 | } 149 | 150 | float perc = (float)data_stream.read_index / 151 | (float)data_stream.data_len * 100.0f; 152 | if ((int)perc != lastperc) { 153 | printf("%d%% done ...\n", (int)perc); 154 | lastperc = (int)perc; 155 | } 156 | } 157 | } 158 | 159 | uint8_t *read_file(const char *filename, size_t *filesize) 160 | { 161 | FILE *f = fopen(filename, "rb"); 162 | if (!f) { 163 | printf("error opening file %s\n", filename); 164 | exit(1); 165 | } 166 | 167 | fseek(f, 0l, SEEK_END); 168 | *filesize = ftell(f); 169 | rewind(f); 170 | 171 | uint8_t *data = (uint8_t *)malloc(*filesize); 172 | size_t read = fread(data, sizeof(uint8_t), *filesize, f); 173 | if (read != *filesize) { 174 | printf("error reading from file!\n"); 175 | exit(1); 176 | } 177 | return data; 178 | } 179 | 180 | int main(int argc, char **argv) 181 | { 182 | int sockfd; 183 | struct sockaddr_in servaddr; 184 | 185 | if (argc != 2) { 186 | printf("usage: %s filename.bin\n", argv[0]); 187 | exit(1); 188 | } 189 | 190 | const char *filename = argv[1]; 191 | size_t filesize; 192 | uint8_t *data = read_file(filename, &filesize); 193 | 194 | 195 | // socket create and varification 196 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 197 | if (sockfd == -1) { 198 | printf("socket creation failed...\n"); 199 | exit(1); 200 | } 201 | else { 202 | printf("Socket successfully created..\n"); 203 | } 204 | bzero(&servaddr, sizeof(servaddr)); 205 | 206 | // assign IP, PORT 207 | servaddr.sin_family = AF_INET; 208 | servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 209 | servaddr.sin_port = htons(9999); 210 | 211 | // connect the client socket to server socket 212 | if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) { 213 | printf("connection with the server failed...\n"); 214 | exit(1); 215 | } 216 | else { 217 | printf("connected to the server..\n"); 218 | } 219 | 220 | // function for chat 221 | replay(sockfd, data, filesize); 222 | 223 | // close the socket 224 | close(sockfd); 225 | 226 | free(data); 227 | return 0; 228 | } --------------------------------------------------------------------------------