├── .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 | [](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 |
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 | 
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 | 
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 | }
--------------------------------------------------------------------------------