diff --git a/.gitmodules b/.gitmodules index 1feebc04b2..5d8c985b13 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,3 +26,22 @@ [submodule "crypto/tests/wycheproof"] path = crypto/tests/wycheproof url = https://github.com/google/wycheproof +[submodule "legacy/trezor-crypto"] + path = legacy/vendor/trezor-crypto + url = https://github.com/trezor/trezor-crypto.git +[submodule "legacy/trezor-common"] + path = legacy/vendor/trezor-common + url = https://github.com/trezor/trezor-common.git +[submodule "legacy/libopencm3"] + path = legacy/vendor/libopencm3 + url = https://github.com/libopencm3/libopencm3.git +[submodule "legacy/vendor/nanopb"] + path = legacy/vendor/nanopb + url = https://github.com/nanopb/nanopb.git +[submodule "legacy/vendor/trezor-storage"] + path = legacy/vendor/trezor-storage + url = https://github.com/trezor/trezor-storage.git +[submodule "legacy/vendor/QR-Code-generator"] + path = legacy/vendor/QR-Code-generator + url = https://github.com/nayuki/QR-Code-generator.git + ignore = untracked diff --git a/legacy/.clang-format b/legacy/.clang-format new file mode 100644 index 0000000000..58d4b3b682 --- /dev/null +++ b/legacy/.clang-format @@ -0,0 +1,2 @@ +--- +BasedOnStyle: Google diff --git a/legacy/.dockerignore b/legacy/.dockerignore new file mode 100644 index 0000000000..50f751212c --- /dev/null +++ b/legacy/.dockerignore @@ -0,0 +1 @@ +_attic/ diff --git a/legacy/.gitignore b/legacy/.gitignore new file mode 100644 index 0000000000..ddd05a1951 --- /dev/null +++ b/legacy/.gitignore @@ -0,0 +1,14 @@ +.cache/ +.vscode/ +_attic/ +build/ +*.o +*.a +*.d +*.bin +*.elf +*.hex +*.img +*.list +*.srec +*.log diff --git a/legacy/.travis.yml b/legacy/.travis.yml new file mode 100644 index 0000000000..39adb5429c --- /dev/null +++ b/legacy/.travis.yml @@ -0,0 +1,63 @@ +sudo: false +dist: trusty +language: c + +addons: + apt: + sources: + - deadsnakes + packages: + - build-essential + - python3.6 + - python3.6-dev + - python3.6-venv + +env: + global: + - MAKEFLAGS=-j2 + - PYTHON=python3.6 + - PROTOBUF_VERSION=3.4.0 + - TOOLCHAIN_SHORTVER=8-2018q4 + - TOOLCHAIN_LONGVER=gcc-arm-none-eabi-8-2018-q4-major + matrix: + - DEBUG_LINK=0 + - DEBUG_LINK=1 + +matrix: + include: + - name: "Emulator GCC" + env: EMULATOR=1 HEADLESS=1 DEBUG_LINK=1 + compiler: gcc + script: pipenv run ./script/cibuild && pipenv run script/test + - name: "Emulator Clang" + env: EMULATOR=1 HEADLESS=1 DEBUG_LINK=1 + compiler: clang + script: pipenv run ./script/cibuild && pipenv run script/test + +before_install: + - $PYTHON -m ensurepip --user + - $PYTHON -m pip install --user pipenv + +install: + - wget "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" + - unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc + - export PATH="$(pwd)/protoc/bin:$PATH" + - pipenv install + +before_script: + - test "$EMULATOR" = "1" || wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/$TOOLCHAIN_SHORTVER/$TOOLCHAIN_LONGVER-linux.tar.bz2 + - test "$EMULATOR" = "1" || tar xfj $TOOLCHAIN_LONGVER-linux.tar.bz2 + - test "$EMULATOR" = "1" || export PATH=$PWD/$TOOLCHAIN_LONGVER/bin:$PATH + +script: + - pipenv run script/cibuild + - pipenv run make -C bootloader + - pipenv run make -C demo + +notifications: + webhooks: + urls: + - http://ci-bot.satoshilabs.com:5000/travis + on_success: always + on_failure: always + on_start: always diff --git a/legacy/CONTRIBUTING.md b/legacy/CONTRIBUTING.md new file mode 100644 index 0000000000..d885604ed5 --- /dev/null +++ b/legacy/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contribute to Trezor MCU + +Please read the general instructions you can find on our [wiki](https://wiki.trezor.io/Developers_guide:Contributing). + +If you are working on a new feature, you probably want to contribute this to the [trezor-core](https://github.com/trezor/trezor-core) repository instead. diff --git a/legacy/COPYING b/legacy/COPYING new file mode 100644 index 0000000000..65c5ca88a6 --- /dev/null +++ b/legacy/COPYING @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/legacy/Dockerfile b/legacy/Dockerfile new file mode 100644 index 0000000000..720c210761 --- /dev/null +++ b/legacy/Dockerfile @@ -0,0 +1,77 @@ +# initialize from the image + +FROM debian:9 + +ARG TOOLCHAIN_FLAVOR=linux +ENV TOOLCHAIN_FLAVOR=$TOOLCHAIN_FLAVOR + +# install build tools and dependencies + +ARG EMULATOR=0 +ENV EMULATOR=$EMULATOR + +RUN apt-get update && apt-get install -y \ + build-essential wget git python3-pip + +# install dependencies from toolchain source build + +RUN if [ "$TOOLCHAIN_FLAVOR" = "src" ]; then \ + apt-get install -y autoconf autogen bison dejagnu \ + flex flip gawk git gperf gzip nsis \ + openssh-client p7zip-full perl python-dev \ + libisl-dev tcl tofrodos zip \ + texinfo texlive texlive-extra-utils; \ + fi + +# download toolchain + +ENV TOOLCHAIN_SHORTVER=8-2018q4 +ENV TOOLCHAIN_LONGVER=gcc-arm-none-eabi-8-2018-q4-major +ENV TOOLCHAIN_URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/$TOOLCHAIN_SHORTVER/$TOOLCHAIN_LONGVER-$TOOLCHAIN_FLAVOR.tar.bz2 +ENV TOOLCHAIN_HASH_linux=fb31fbdfe08406ece43eef5df623c0b2deb8b53e405e2c878300f7a1f303ee52 +ENV TOOLCHAIN_HASH_src=bc228325dbbfaf643f2ee5d19e01d8b1873fcb9c31781b5e1355d40a68704ce7 + +RUN if [ "$EMULATOR" = 1 ]; then \ + apt-get install -y libsdl2-dev libsdl2-image-dev; \ + fi + +# extract toolchain + +RUN cd /opt && wget $TOOLCHAIN_URL + +RUN cd /opt && echo "$TOOLCHAIN_HASH_linux $TOOLCHAIN_LONGVER-linux.tar.bz2\n$TOOLCHAIN_HASH_src $TOOLCHAIN_LONGVER-src.tar.bz2" | sha256sum -c --ignore-missing + +RUN cd /opt && tar xfj $TOOLCHAIN_LONGVER-$TOOLCHAIN_FLAVOR.tar.bz2 + +# build toolchain (if required) + +RUN if [ "$TOOLCHAIN_FLAVOR" = "src" ]; then \ + pushd /opt/$TOOLCHAIN_LONGVER ; \ + ./install-sources.sh --skip_steps=mingw32 ; \ + ./build-prerequisites.sh --skip_steps=mingw32 ; \ + ./build-toolchain.sh --skip_steps=mingw32,manual ; \ + popd ; \ + fi + +# download protobuf + +ENV PROTOBUF_VERSION=3.4.0 +ENV PROTOBUF_HASH=e4b51de1b75813e62d6ecdde582efa798586e09b5beaebfb866ae7c9eaadace4 +RUN wget "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" +RUN echo "${PROTOBUF_HASH} protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" | sha256sum -c + +# setup toolchain + +ENV PATH=/opt/$TOOLCHAIN_LONGVER/bin:$PATH + +ENV PYTHON=python3 +ENV LC_ALL=C.UTF-8 LANG=C.UTF-8 + +# use zipfile module to extract files world-readable +RUN $PYTHON -m zipfile -e "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" /usr/local && chmod 755 /usr/local/bin/protoc + +ENV WORKON_HOME=/tmp/.venvs + +# install python dependencies + +RUN $PYTHON -m pip install pipenv diff --git a/legacy/Makefile b/legacy/Makefile new file mode 100644 index 0000000000..7c270e621e --- /dev/null +++ b/legacy/Makefile @@ -0,0 +1,43 @@ +ifneq ($(EMULATOR),1) +OBJS += startup.o +endif + +OBJS += buttons.o +OBJS += common.o +OBJS += flash.o +OBJS += layout.o +OBJS += oled.o +OBJS += rng.o + +ifneq ($(EMULATOR),1) +OBJS += setup.o +endif + +OBJS += util.o +OBJS += memory.o +OBJS += supervise.o + +ifneq ($(EMULATOR),1) +OBJS += timer.o +endif + +OBJS += usb_standard.o +OBJS += usb21_standard.o +OBJS += webusb.o +OBJS += winusb.o + +OBJS += gen/bitmaps.o +OBJS += gen/fonts.o + +libtrezor.a: $(OBJS) + +include Makefile.include + +libtrezor.a: + @printf " AR $@\n" + $(Q)$(AR) rcs $@ $^ + +.PHONY: vendor + +vendor: + git submodule update --init --recursive diff --git a/legacy/Makefile.include b/legacy/Makefile.include new file mode 100644 index 0000000000..029dddae7d --- /dev/null +++ b/legacy/Makefile.include @@ -0,0 +1,226 @@ +TOP_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +TOOLCHAIN_DIR ?= $(TOP_DIR)vendor/libopencm3 + +ifneq ($(V),1) +Q := @ +endif + +PYTHON ?= python + +ifeq ($(EMULATOR),1) +CC ?= gcc +LD := $(CC) +OBJCOPY := objcopy +OBJDUMP := objdump +AR := ar +AS := as + +OPTFLAGS ?= -O3 +DBGFLAGS ?= -g3 -ggdb3 +CPUFLAGS ?= +FPUFLAGS ?= +else +PREFIX ?= arm-none-eabi- +CC := $(PREFIX)gcc +LD := $(PREFIX)gcc +OBJCOPY := $(PREFIX)objcopy +OBJDUMP := $(PREFIX)objdump +AR := $(PREFIX)ar +AS := $(PREFIX)as +OPENOCD := openocd -f interface/stlink-v2.cfg -c "transport select hla_swd" -f target/stm32f2x.cfg +GDB := $(PREFIX)gdb --nx -ex 'set remotetimeout unlimited' -ex 'set confirm off' -ex 'target remote 127.0.0.1:3333' -ex 'monitor reset halt' + +OPTFLAGS ?= -O3 +DBGFLAGS ?= -g -DNDEBUG +CPUFLAGS ?= -mcpu=cortex-m3 -mthumb +FPUFLAGS ?= -msoft-float +endif + +CFLAGS += $(OPTFLAGS) \ + $(DBGFLAGS) \ + -std=gnu11 \ + -W \ + -Wall \ + -Wextra \ + -Wimplicit-function-declaration \ + -Wredundant-decls \ + -Wstrict-prototypes \ + -Wundef \ + -Wshadow \ + -Wpointer-arith \ + -Wformat \ + -Wreturn-type \ + -Wsign-compare \ + -Wmultichar \ + -Wformat-nonliteral \ + -Winit-self \ + -Wuninitialized \ + -Wformat-security \ + -Werror \ + -fno-common \ + -fno-exceptions \ + -fvisibility=internal \ + -ffunction-sections \ + -fdata-sections \ + -fstack-protector-all \ + $(CPUFLAGS) \ + $(FPUFLAGS) \ + -DSTM32F2 \ + -DCONFIDENTIAL='__attribute__((section("confidential")))' \ + -DRAND_PLATFORM_INDEPENDENT=1 \ + -I$(TOOLCHAIN_DIR)/include \ + -I$(TOP_DIR) \ + -I$(TOP_DIR)gen \ + -I$(TOP_DIR)vendor/trezor-crypto \ + -I$(TOP_DIR)vendor/QR-Code-generator/c \ + -I$(TOP_DIR)vendor/trezor-storage + +LDFLAGS += -L$(TOP_DIR) \ + $(DBGFLAGS) \ + $(CPUFLAGS) \ + $(FPUFLAGS) + +ifeq ($(EMULATOR),1) +CFLAGS += -DEMULATOR=1 + +CFLAGS += -include $(TOP_DIR)emulator/emulator.h +CFLAGS += -include stdio.h + +LDFLAGS += -L$(TOP_DIR)emulator + +LDLIBS += -ltrezor -lemulator +LIBDEPS += $(TOP_DIR)/libtrezor.a $(TOP_DIR)emulator/libemulator.a + +ifeq ($(HEADLESS),1) +CFLAGS += -DHEADLESS=1 +else +CFLAGS += -DHEADLESS=0 + +CFLAGS += $(shell pkg-config --cflags sdl2) +LDLIBS += $(shell pkg-config --libs sdl2) +endif + +ifdef RANDOM_DEV_FILE +CFLAGS += -DRANDOM_DEV_FILE=\"$(RANDOM_DEV_FILE)\" +endif + +else +ifdef APPVER +CFLAGS += -DAPPVER=$(APPVER) +LDSCRIPT = $(TOP_DIR)/memory_app_$(APPVER).ld +else +LDSCRIPT = $(TOP_DIR)/memory.ld +endif + +CFLAGS += -DEMULATOR=0 + +LDFLAGS += --static \ + -Wl,--start-group \ + -lc \ + -lgcc \ + -lnosys \ + -Wl,--end-group \ + -L$(TOOLCHAIN_DIR)/lib \ + -T$(LDSCRIPT) \ + -nostartfiles \ + -Wl,--gc-sections + +LDLIBS += -ltrezor +LIBDEPS += $(TOP_DIR)/libtrezor.a + +LDLIBS += -lopencm3_stm32f2 +LIBDEPS += $(TOOLCHAIN_DIR)/lib/libopencm3_stm32f2.a +endif + +ifeq ($(MEMORY_PROTECT), 0) +CFLAGS += -DMEMORY_PROTECT=0 +$(info MEMORY_PROTECT=0) +else +CFLAGS += -DMEMORY_PROTECT=1 +$(info MEMORY_PROTECT=1) +endif + +ifeq ($(DEBUG_RNG), 1) +CFLAGS += -DDEBUG_RNG=1 +else +CFLAGS += -DDEBUG_RNG=0 +endif + +all: $(NAME).bin + +openocd: + $(OPENOCD) + +gdb_bootloader: bootloader/bootloader.elf + $(GDB) $< + +gdb_firmware: firmware/trezor.elf + $(GDB) $< + +flash: $(NAME).bin + $(OPENOCD) -c "init; reset halt; flash write_image erase $(NAME).bin 0x8000000; exit" + +upload: sign + trezorctl firmware_update -f $(NAME).bin -s + +sign: $(NAME).bin + $(PYTHON) ../bootloader/firmware_sign.py -f $(NAME).bin + +release: $(NAME).bin + $(PYTHON) ../bootloader/firmware_sign.py -f $(NAME).bin + cp $(NAME).bin $(NAME)-$(APPVER).bin + chmod -x $(NAME)-$(APPVER).bin + xxd -p $(NAME)-$(APPVER).bin | tr -d '\n' > $(NAME)-$(APPVER).bin.hex + +$(NAME).bin: $(NAME).elf + @printf " OBJCOPY $@\n" + $(Q)$(OBJCOPY) -Obinary $(NAME).elf $(NAME).bin + +$(NAME).hex: $(NAME).elf + @printf " OBJCOPY $@\n" + $(Q)$(OBJCOPY) -Oihex $(NAME).elf $(NAME).hex + +$(NAME).srec: $(NAME).elf + @printf " OBJCOPY $@\n" + $(Q)$(OBJCOPY) -Osrec $(NAME).elf $(NAME).srec + +$(NAME).list: $(NAME).elf + @printf " OBJDUMP $@\n" + $(Q)$(OBJDUMP) -S $(NAME).elf > $(NAME).list + +$(NAME).elf: $(OBJS) $(LDSCRIPT) $(LIBDEPS) + @printf " LD $@\n" + $(Q)$(LD) -o $(NAME).elf $(OBJS) $(LDLIBS) $(LDFLAGS) + +%.o: %.s Makefile + @printf " AS $@\n" + $(Q)$(AS) $(CPUFLAGS) -o $@ $< + +%.o: %.c Makefile + @printf " CC $@\n" + $(Q)$(CC) $(CFLAGS) -MMD -MP -o $@ -c $< + +%.small.o: %.c Makefile + @printf " CC $@\n" + $(Q)$(CC) $(CFLAGS) -MMD -MP -o $@ -c $< + +%.d: %.c Makefile + @printf " DEP $@\n" + $(Q)$(CC) $(CFLAGS) -MM -MP -MG -o $@ $< + +%.small.d: %.c Makefile + @printf " DEP $@\n" + $(Q)$(CC) $(CFLAGS) -MM -MP -MG -o $@ $< + +clean:: + rm -f $(OBJS) + rm -f *.a + rm -f *.bin + rm -f *.d + rm -f *.elf + rm -f *.hex + rm -f *.list + rm -f *.log + rm -f *.srec + +-include $(OBJS:.o=.d) diff --git a/legacy/Pipfile b/legacy/Pipfile new file mode 100644 index 0000000000..ba4ee323c8 --- /dev/null +++ b/legacy/Pipfile @@ -0,0 +1,14 @@ +[[source]] +url = "https://pypi.org/simple" +name = "pypi" +verify_ssl = true + +[packages] +setuptools = ">=24.2.0" +trezor = {git = "https://github.com/trezor/python-trezor", editable = true, ref = "master"} +pytest = "*" +mock = "*" +typing = "*" +protobuf = "==3.4.0" +mako = "*" +munch = "*" diff --git a/legacy/Pipfile.lock b/legacy/Pipfile.lock new file mode 100644 index 0000000000..cd379ed76a --- /dev/null +++ b/legacy/Pipfile.lock @@ -0,0 +1,214 @@ +{ + "_meta": { + "hash": { + "sha256": "0c77aa21c1e385d7c3833a2f95bc6129394f6d9ce67e1181700a76a5e15074cb" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "atomicwrites": { + "hashes": [ + "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", + "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6" + ], + "version": "==1.1.5" + }, + "attrs": { + "hashes": [ + "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", + "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" + ], + "version": "==18.1.0" + }, + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "version": "==6.7" + }, + "ecdsa": { + "hashes": [ + "sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", + "sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa" + ], + "version": "==0.13" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "libusb1": { + "hashes": [ + "sha256:4707f81e933a97fed1c5bf7d4957f07bae1139cb8084bdee1f50201a40e3fd7c" + ], + "version": "==1.6.5" + }, + "mako": { + "hashes": [ + "sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae" + ], + "index": "pypi", + "version": "==1.0.7" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "version": "==1.0" + }, + "mnemonic": { + "hashes": [ + "sha256:02a7306a792370f4a0c106c2cf1ce5a0c84b9dbd7e71c6792fdb9ad88a727f1d" + ], + "version": "==0.18" + }, + "mock": { + "hashes": [ + "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", + "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "more-itertools": { + "hashes": [ + "sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8", + "sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3", + "sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0" + ], + "version": "==4.2.0" + }, + "munch": { + "hashes": [ + "sha256:6ae3d26b837feacf732fb8aa5b842130da1daf221f5af9f9d4b2a0a6414b0d51" + ], + "index": "pypi", + "version": "==2.3.2" + }, + "pbkdf2": { + "hashes": [ + "sha256:ac6397369f128212c43064a2b4878038dab78dab41875364554aaf2a684e6979" + ], + "version": "==1.3" + }, + "pbr": { + "hashes": [ + "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", + "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa" + ], + "version": "==4.2.0" + }, + "pluggy": { + "hashes": [ + "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", + "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" + ], + "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*'", + "version": "==0.7.1" + }, + "protobuf": { + "hashes": [ + "sha256:1fcb9b704bc2e30767352d86b2664d8f65f8ed49654d7a80e7a150739724e80a", + "sha256:41c4555d9754b985352ce5289fa3ba6b21ed715f595111e46e2b90ca53112475", + "sha256:4d4815467f8a61b06d648699842b233017b201f7a16275d680ec5480f10e30e9", + "sha256:5b816951df388f4ab2adbd3f9ae5619b9a5d7033d14b005c345dc3ee88a7faf4", + "sha256:61dbf86993a9312c3a0816b5252079a3943856003bf0380fea3098c929084ad4", + "sha256:9f3be25ad48b051186ee88f9567a3f3f548facd360e0cb62568e2736d9cfda11", + "sha256:ef02609ef445987976a3a26bff77119c518e0915c96661c3a3b17856d0ef6374" + ], + "index": "pypi", + "version": "==3.4.0" + }, + "py": { + "hashes": [ + "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", + "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" + ], + "version": "==1.5.4" + }, + "pyblake2": { + "hashes": [ + "sha256:3757f7ad709b0e1b2a6b3919fa79fe3261f166fc375cd521f2be480f8319dde9", + "sha256:407e02c7f8f36fcec1b7aa114ddca0c1060c598142ea6f6759d03710b946a7e3", + "sha256:4d47b4a2c1d292b1e460bde1dda4d13aa792ed2ed70fcc263b6bc24632c8e902", + "sha256:5ccc7eb02edb82fafb8adbb90746af71460fbc29aa0f822526fc976dff83e93f", + "sha256:8043267fbc0b2f3748c6920591cd0b8b5609dcce60c504c32858aa36206386f2", + "sha256:982295a87907d50f4723db6bc724660da76b6547826d52160171d54f95b919ac", + "sha256:baa2190bfe549e36163aa44664d4ee3a9080b236fc5d42f50dc6fd36bbdc749e", + "sha256:c53417ee0bbe77db852d5fd1036749f03696ebc2265de359fe17418d800196c4", + "sha256:fbc9fcde75713930bc2a91b149e97be2401f7c9c56d735b46a109210f58d7358" + ], + "version": "==1.1.2" + }, + "pytest": { + "hashes": [ + "sha256:341ec10361b64a24accaec3c7ba5f7d5ee1ca4cebea30f76fad3dd12db9f0541", + "sha256:952c0389db115437f966c4c2079ae9d54714b9455190e56acebe14e8c38a7efa" + ], + "index": "pypi", + "version": "==3.6.4" + }, + "requests": { + "hashes": [ + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + ], + "version": "==2.19.1" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "trezor": { + "editable": true, + "git": "https://github.com/trezor/python-trezor", + "ref": "master" + }, + "typing": { + "hashes": [ + "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf", + "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8", + "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2" + ], + "index": "pypi", + "version": "==3.6.4" + }, + "urllib3": { + "hashes": [ + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + ], + "version": "==1.23" + } + }, + "develop": {} +} diff --git a/legacy/README.md b/legacy/README.md new file mode 100644 index 0000000000..bb443372e3 --- /dev/null +++ b/legacy/README.md @@ -0,0 +1,74 @@ +# TREZOR One Bootloader and Firmware + +[![Build Status](https://travis-ci.org/trezor/trezor-mcu.svg?branch=master)](https://travis-ci.org/trezor/trezor-mcu) [![gitter](https://badges.gitter.im/trezor/community.svg)](https://gitter.im/trezor/community) + +https://trezor.io/ + +## How to build the TREZOR bootloader, firmware and emulator + +Ensure that you have Docker installed. You can follow [Docker's installation instructions](https://docs.docker.com/engine/installation/). + +Clone this repository: +```sh +git clone https://github.com/trezor/trezor-mcu.git` +cd trezor-mcu +``` + +Use the `build.sh` command to build the images. + +* to build bootloader 1.6.0 and firmware 1.7.0: + ```sh + ./build.sh bl1.6.0 v1.7.0 + ``` +* to build latest firmware from master: + ```sh + ./build.sh + ``` +* to build the emulator from master: + ```sh + ./build.sh EMU + ``` +* to build the emulator for version 1.7.0: + ```sh + ./build.sh EMU v1.7.0 + ``` + +Build results are stored in the `build/` directory. File `bootloader-.bin` represents +the bootloader, `trezor-.bin` is the firmware image, and `trezor-emulator-.elf` +is the emulator executable. + +You can use `TREZOR_OLED_SCALE` environment variable to make emulator screen bigger. + +## How to get fingerprint of firmware signed and distributed by SatoshiLabs? + +1. Pick version of firmware binary listed on https://wallet.trezor.io/data/firmware/1/releases.json +2. Download it: `wget -O trezor.signed.bin https://wallet.trezor.io/data/firmware/1/trezor-1.6.1.bin` +3. Compute fingerprint: `tail -c +257 trezor.signed.bin | sha256sum` + +Step 3 should produce the same sha256 fingerprint like your local build (for the same version tag). Firmware has a special header (of length 256 bytes) holding signatures themselves, which must be avoided while calculating the fingerprint, that's why tail command has to be used. + +## How to install custom built firmware? + +**WARNING: This will erase the recovery seed stored on the device! You should never do this on TREZOR that contains coins!** + +1. Install python-trezor: `pip install trezor` ([more info](https://github.com/trezor/python-trezor)) +2. `trezorctl firmware_update -f build/trezor-TAG.bin` + +## Building for development + +If you want to build device firmware, make sure you have the +[GNU ARM Embedded toolchain](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) installed. +You will also need Python 3.5 or later and [pipenv](https://pipenv.readthedocs.io/en/latest/install/). + +* If you want to build the emulator instead of the firmware, run `export EMULATOR=1 TREZOR_TRANSPORT_V1=1` +* If you want to build with the debug link, run `export DEBUG_LINK=1`. Use this if you want to run the device tests. +* When you change these variables, use `script/setup` to clean the repository + +1. To initialize the repository, run `script/setup` +2. To initialize a Python environment, run `pipenv install` +3. To build the firmware or emulator, run `pipenv run script/cibuild` + +If you are building device firmware, the firmware will be in `firmware/trezor.bin`. + +You can launch the emulator using `firmware/trezor.elf`. To use `trezorctl` with the emulator, use +`trezorctl -p udp` (for example, `trezorctl -p udp get_features`). diff --git a/legacy/bootloader/.gitignore b/legacy/bootloader/.gitignore new file mode 100644 index 0000000000..e0baf9ab7d --- /dev/null +++ b/legacy/bootloader/.gitignore @@ -0,0 +1,9 @@ +*.o +*.a +*.d +*.bin +*.elf +*.hex +*.list +*.srec +*.log diff --git a/legacy/bootloader/ChangeLog b/legacy/bootloader/ChangeLog new file mode 100644 index 0000000000..a348378c01 --- /dev/null +++ b/legacy/bootloader/ChangeLog @@ -0,0 +1,50 @@ +Version 1.6.1 +* Fix USB issue on some Windows 10 installations + +Version 1.6.0 +* Switch from HID to WebUSB + +Version 1.5.1 +* Improve MPU configuration + +Version 1.5.0 +* Make unofficial firmwares work again + +Version 1.4.0 +* More flash-write tests +* Don't restore storage from unofficial firmware +* Support WipeDevice message +* Activate MPU and don't switch VTOR table for unofficial firmware + +Version 1.3.3 +* Add self-test +* Erase metadata backup after usage +* Erase SRAM on application start + +Version 1.3.2 +* Don't show recovery seed warning if firmware is flashed for the first time +* Don't show fingerprint if firmware is flashed for the first time +* Compute firmware hash before checking signatures +* Add self-test +* Fix usage of RNG before setup +* Fix stack protector fault + +Version 1.3.1 +* Fix button testing so it does not break USB communication + +Version 1.3.0 +* Add test for buttons +* Clean USB descriptor +* Return firmware_present in Features response +* Don't halt on broken firware, stay in bootloader + +Version 1.2.7 +* Optimize speed of firmware update + +Version 1.2.6 +* Show hash of unofficial firmware +* Use stack protector +* Clean USB descriptor + +Version 1.2.5 +* Initial import of code diff --git a/legacy/bootloader/Makefile b/legacy/bootloader/Makefile new file mode 100644 index 0000000000..72b251ae17 --- /dev/null +++ b/legacy/bootloader/Makefile @@ -0,0 +1,21 @@ +NAME = bootloader + +OBJS += bootloader.o +OBJS += signatures.o +OBJS += usb.o + +OBJS += ../vendor/trezor-crypto/bignum.small.o +OBJS += ../vendor/trezor-crypto/ecdsa.small.o +OBJS += ../vendor/trezor-crypto/secp256k1.small.o +OBJS += ../vendor/trezor-crypto/sha2.small.o +OBJS += ../vendor/trezor-crypto/memzero.small.o + +CFLAGS += -DUSE_PRECOMPUTED_IV=0 +CFLAGS += -DUSE_PRECOMPUTED_CP=0 + +OPTFLAGS ?= -Os + +include ../Makefile.include + +align: $(NAME).bin + ./firmware_align.py $(NAME).bin diff --git a/legacy/bootloader/bootloader.c b/legacy/bootloader/bootloader.c new file mode 100644 index 0000000000..e30cc99399 --- /dev/null +++ b/legacy/bootloader/bootloader.c @@ -0,0 +1,153 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include +#include +#include + +#include "bootloader.h" +#include "buttons.h" +#include "layout.h" +#include "memory.h" +#include "oled.h" +#include "rng.h" +#include "setup.h" +#include "signatures.h" +#include "usb.h" +#include "util.h" + +void layoutFirmwareFingerprint(const uint8_t *hash) { + char str[4][17]; + for (int i = 0; i < 4; i++) { + data2hex(hash + i * 8, 8, str[i]); + } + layoutDialog(&bmp_icon_question, "Abort", "Continue", "Compare fingerprints", + str[0], str[1], str[2], str[3], NULL, NULL); +} + +bool get_button_response(void) { + do { + delay(100000); + buttonUpdate(); + } while (!button.YesUp && !button.NoUp); + return button.YesUp; +} + +void show_halt(const char *line1, const char *line2) { + layoutDialog(&bmp_icon_error, NULL, NULL, NULL, line1, line2, NULL, + "Unplug your TREZOR,", "reinstall firmware.", NULL); + shutdown(); +} + +static void show_unofficial_warning(const uint8_t *hash) { + layoutDialog(&bmp_icon_warning, "Abort", "I'll take the risk", NULL, + "WARNING!", NULL, "Unofficial firmware", "detected.", NULL, + NULL); + + bool but = get_button_response(); + if (!but) { // no button was pressed -> halt + show_halt("Unofficial firmware", "aborted."); + } + + layoutFirmwareFingerprint(hash); + + but = get_button_response(); + if (!but) { // no button was pressed -> halt + show_halt("Unofficial firmware", "aborted."); + } + + // everything is OK, user pressed 2x Continue -> continue program +} + +static void __attribute__((noreturn)) load_app(int signed_firmware) { + // zero out SRAM + memset_reg(_ram_start, _ram_end, 0); + + jump_to_firmware((const vector_table_t *)FLASH_PTR(FLASH_APP_START), + signed_firmware); +} + +static void bootloader_loop(void) { + oledClear(); + oledDrawBitmap(0, 0, &bmp_logo64); + if (firmware_present_new()) { + oledDrawStringCenter(90, 10, "TREZOR", FONT_STANDARD); + oledDrawStringCenter(90, 30, "Bootloader", FONT_STANDARD); + oledDrawStringCenter(90, 50, + VERSTR(VERSION_MAJOR) "." VERSTR( + VERSION_MINOR) "." VERSTR(VERSION_PATCH), + FONT_STANDARD); + } else { + oledDrawStringCenter(90, 10, "Welcome!", FONT_STANDARD); + oledDrawStringCenter(90, 30, "Please visit", FONT_STANDARD); + oledDrawStringCenter(90, 50, "trezor.io/start", FONT_STANDARD); + } + oledRefresh(); + + usbLoop(); +} + +int main(void) { +#ifndef APPVER + setup(); +#endif + __stack_chk_guard = random32(); // this supports compiler provided + // unpredictable stack protection checks +#ifndef APPVER + memory_protect(); + oledInit(); +#endif + + mpu_config_bootloader(); + +#ifndef APPVER + bool left_pressed = (buttonRead() & BTN_PIN_NO) == 0; + + if (firmware_present_new() && !left_pressed) { + oledClear(); + oledDrawBitmap(40, 0, &bmp_logo64_empty); + oledRefresh(); + + const image_header *hdr = + (const image_header *)FLASH_PTR(FLASH_FWHEADER_START); + + uint8_t fingerprint[32]; + int signed_firmware = signatures_new_ok(hdr, fingerprint); + if (SIG_OK != signed_firmware) { + show_unofficial_warning(fingerprint); + } + + if (SIG_OK != check_firmware_hashes(hdr)) { + layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Broken firmware", + "detected.", NULL, "Unplug your TREZOR,", + "reinstall firmware.", NULL); + shutdown(); + } + + mpu_config_off(); + load_app(signed_firmware); + } +#endif + + bootloader_loop(); + + return 0; +} diff --git a/legacy/bootloader/bootloader.h b/legacy/bootloader/bootloader.h new file mode 100644 index 0000000000..b967c7f0b9 --- /dev/null +++ b/legacy/bootloader/bootloader.h @@ -0,0 +1,41 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __BOOTLOADER_H__ +#define __BOOTLOADER_H__ + +#define VERSION_MAJOR 1 +#define VERSION_MINOR 8 +#define VERSION_PATCH 0 + +#define STR(X) #X +#define VERSTR(X) STR(X) + +#define VERSION_MAJOR_CHAR "\x01" +#define VERSION_MINOR_CHAR "\x08" +#define VERSION_PATCH_CHAR "\x00" + +#include +#include + +void show_halt(const char *line1, const char *line2); +void layoutFirmwareFingerprint(const uint8_t *hash); +bool get_button_response(void); + +#endif diff --git a/legacy/bootloader/combine/prepare.py b/legacy/bootloader/combine/prepare.py new file mode 100755 index 0000000000..eb073e9820 --- /dev/null +++ b/legacy/bootloader/combine/prepare.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +from __future__ import print_function + +bl = open('bl.bin').read() +fw = open('fw.bin').read() +combined = bl + fw[:256] + (32768-256)*'\x00' + fw[256:] + +open('combined.bin', 'w').write(combined) + +print('bootloader : %d bytes' % len(bl)) +print('firmware : %d bytes' % len(fw)) +print('combined : %d bytes' % len(combined)) diff --git a/legacy/bootloader/combine/write.sh b/legacy/bootloader/combine/write.sh new file mode 100755 index 0000000000..6854ee6008 --- /dev/null +++ b/legacy/bootloader/combine/write.sh @@ -0,0 +1,2 @@ +#!/bin/bash +st-flash write combined.bin 0x8000000 diff --git a/legacy/bootloader/firmware_align.py b/legacy/bootloader/firmware_align.py new file mode 100755 index 0000000000..3c1434987e --- /dev/null +++ b/legacy/bootloader/firmware_align.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +import sys +import os + +TOTALSIZE = 32768 +MAXSIZE = TOTALSIZE - 32 + +fn = sys.argv[1] +fs = os.stat(fn).st_size +if fs > MAXSIZE: + raise Exception('bootloader has to be smaller than %d bytes (current size is %d)' % (MAXSIZE, fs)) +with open(fn, 'ab') as f: + f.write(b'\x00' * (TOTALSIZE - fs)) + f.close() diff --git a/legacy/bootloader/firmware_sign.py b/legacy/bootloader/firmware_sign.py new file mode 100755 index 0000000000..a7b8e13cf6 --- /dev/null +++ b/legacy/bootloader/firmware_sign.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +from __future__ import print_function + +import argparse +import hashlib +import struct + +import ecdsa + + +SLOTS = 3 + +pubkeys = { + 1: "04d571b7f148c5e4232c3814f777d8faeaf1a84216c78d569b71041ffc768a5b2d810fc3bb134dd026b57e65005275aedef43e155f48fc11a32ec790a93312bd58", + 2: "0463279c0c0866e50c05c799d32bd6bab0188b6de06536d1109d2ed9ce76cb335c490e55aee10cc901215132e853097d5432eda06b792073bd7740c94ce4516cb1", + 3: "0443aedbb6f7e71c563f8ed2ef64ec9981482519e7ef4f4aa98b27854e8c49126d4956d300ab45fdc34cd26bc8710de0a31dbdf6de7435fd0b492be70ac75fde58", + 4: "04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a", + 5: "047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45", +} + +FWHEADER_SIZE = 1024 +SIGNATURES_START = 6 * 4 + 8 + 512 +INDEXES_START = SIGNATURES_START + 3 * 64 + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Commandline tool for signing Trezor firmware." + ) + parser.add_argument("-f", "--file", dest="path", help="Firmware file to modify") + parser.add_argument( + "-s", + "--sign", + dest="sign", + action="store_true", + help="Add signature to firmware slot", + ) + parser.add_argument( + "-p", "--pem", dest="pem", action="store_true", help="Use PEM instead of SECEXP" + ) + parser.add_argument( + "-g", + "--generate", + dest="generate", + action="store_true", + help="Generate new ECDSA keypair", + ) + + return parser.parse_args() + + +def pad_to_size(data, size): + if len(data) > size: + raise ValueError("Chunk too big already") + if len(data) == size: + return data + return data + b"\xFF" * (size - len(data)) + + +# see memory.h for details + + +def prepare_hashes(data): + # process chunks + start = 0 + end = (64 - 1) * 1024 + hashes = [] + for i in range(16): + sector = data[start:end] + if len(sector) > 0: + chunk = pad_to_size(sector, end - start) + hashes.append(hashlib.sha256(chunk).digest()) + else: + hashes.append(b"\x00" * 32) + start = end + end += 64 * 1024 + return hashes + + +def check_hashes(data): + expected_hashes = data[0x20 : 0x20 + 16 * 32] + hashes = b"" + for h in prepare_hashes(data[FWHEADER_SIZE:]): + hashes += h + + if expected_hashes == hashes: + print("HASHES OK") + else: + print("HASHES NOT OK") + + +def update_hashes_in_header(data): + # Store hashes in the firmware header + data = bytearray(data) + o = 0 + for h in prepare_hashes(data[FWHEADER_SIZE:]): + data[0x20 + o:0x20 + o + 32] = h + o += 32 + return bytes(data) + + +def get_header(data, zero_signatures=False): + if not zero_signatures: + return data[:FWHEADER_SIZE] + else: + data = bytearray(data[:FWHEADER_SIZE]) + data[SIGNATURES_START : SIGNATURES_START + 3 * 64 + 3] = b"\x00" * (3 * 64 + 3) + return bytes(data) + + +def check_size(data): + size = struct.unpack(" SLOTS: + raise Exception("Invalid slot") + + if is_pem: + print("Paste ECDSA private key in PEM format and press Enter:") + print("(blank private key removes the signature on given index)") + pem_key = "" + while True: + key = input() + pem_key += key + "\n" + if key == "": + break + if pem_key.strip() == "": + # Blank key,let's remove existing signature from slot + return modify(data, slot, 0, b"\x00" * 64) + key = ecdsa.SigningKey.from_pem(pem_key) + else: + print("Paste SECEXP (in hex) and press Enter:") + print("(blank private key removes the signature on given index)") + secexp = input() + if secexp.strip() == "": + # Blank key,let's remove existing signature from slot + return modify(data, slot, 0, b"\x00" * 64) + key = ecdsa.SigningKey.from_secret_exponent( + secexp=int(secexp, 16), + curve=ecdsa.curves.SECP256k1, + hashfunc=hashlib.sha256, + ) + + to_sign = get_header(data, zero_signatures=True) + + # Locate proper index of current signing key + pubkey = "04" + key.get_verifying_key().to_string().hex() + index = None + for i, pk in pubkeys.items(): + if pk == pubkey: + index = i + break + + if index == None: + raise Exception("Unable to find private key index. Unknown private key?") + + signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256) + + return modify(data, slot, index, signature) + + +def main(args): + if args.generate: + key = ecdsa.SigningKey.generate( + curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256 + ) + + print("PRIVATE KEY (SECEXP):") + print(key.to_string().hex()) + print() + + print("PRIVATE KEY (PEM):") + print(key.to_pem()) + + print("PUBLIC KEY:") + print("04" + key.get_verifying_key().to_string().hex()) + return + + if not args.path: + raise Exception("-f/--file is required") + + data = open(args.path, "rb").read() + assert len(data) % 4 == 0 + + if data[:4] != b"TRZF": + raise Exception("Firmware header expected") + + data = update_hashes_in_header(data) + + print("Firmware size %d bytes" % len(data)) + + check_size(data) + check_signatures(data) + check_hashes(data) + + if args.sign: + data = sign(data, args.pem) + check_signatures(data) + check_hashes(data) + + fp = open(args.path, "wb") + fp.write(data) + fp.close() + + +if __name__ == "__main__": + args = parse_args() + main(args) diff --git a/legacy/bootloader/firmware_sign_split.py b/legacy/bootloader/firmware_sign_split.py new file mode 100755 index 0000000000..25c4b341fd --- /dev/null +++ b/legacy/bootloader/firmware_sign_split.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import hashlib +import os +import subprocess +import ecdsa +from binascii import hexlify, unhexlify + +print('master secret:', end='') +h = input() +if h: + h = unhexlify(h).encode('ascii') +else: + h = hashlib.sha256(os.urandom(1024)).digest() + +print() +print('master secret:', hexlify(h)) +print() + +for i in range(1, 6): + se = hashlib.sha256(h + chr(i).encode('ascii')).hexdigest() + print('seckey', i, ':', se) + sk = ecdsa.SigningKey.from_secret_exponent(secexp = int(se, 16), curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256) + print('pubkey', i, ':', (b'04' + hexlify(sk.get_verifying_key().to_string())).decode('ascii')) + print(sk.to_pem().decode('ascii')) + +p = subprocess.Popen('ssss-split -t 3 -n 5 -x'.split(' '), stdin = subprocess.PIPE) +p.communicate(input = hexlify(h) + '\n') + +# to recover use: +# $ ssss-combine -t 3 -x diff --git a/legacy/bootloader/signatures.c b/legacy/bootloader/signatures.c new file mode 100644 index 0000000000..7e98247571 --- /dev/null +++ b/legacy/bootloader/signatures.c @@ -0,0 +1,203 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "bootloader.h" +#include "ecdsa.h" +#include "memory.h" +#include "memzero.h" +#include "secp256k1.h" +#include "sha2.h" +#include "signatures.h" + +const uint32_t FIRMWARE_MAGIC_OLD = 0x525a5254; // TRZR +const uint32_t FIRMWARE_MAGIC_NEW = 0x465a5254; // TRZF + +#define PUBKEYS 5 + +static const uint8_t * const pubkey[PUBKEYS] = { + (const uint8_t *)"\x04\xd5\x71\xb7\xf1\x48\xc5\xe4\x23\x2c\x38\x14\xf7\x77\xd8\xfa\xea\xf1\xa8\x42\x16\xc7\x8d\x56\x9b\x71\x04\x1f\xfc\x76\x8a\x5b\x2d\x81\x0f\xc3\xbb\x13\x4d\xd0\x26\xb5\x7e\x65\x00\x52\x75\xae\xde\xf4\x3e\x15\x5f\x48\xfc\x11\xa3\x2e\xc7\x90\xa9\x33\x12\xbd\x58", + (const uint8_t *)"\x04\x63\x27\x9c\x0c\x08\x66\xe5\x0c\x05\xc7\x99\xd3\x2b\xd6\xba\xb0\x18\x8b\x6d\xe0\x65\x36\xd1\x10\x9d\x2e\xd9\xce\x76\xcb\x33\x5c\x49\x0e\x55\xae\xe1\x0c\xc9\x01\x21\x51\x32\xe8\x53\x09\x7d\x54\x32\xed\xa0\x6b\x79\x20\x73\xbd\x77\x40\xc9\x4c\xe4\x51\x6c\xb1", + (const uint8_t *)"\x04\x43\xae\xdb\xb6\xf7\xe7\x1c\x56\x3f\x8e\xd2\xef\x64\xec\x99\x81\x48\x25\x19\xe7\xef\x4f\x4a\xa9\x8b\x27\x85\x4e\x8c\x49\x12\x6d\x49\x56\xd3\x00\xab\x45\xfd\xc3\x4c\xd2\x6b\xc8\x71\x0d\xe0\xa3\x1d\xbd\xf6\xde\x74\x35\xfd\x0b\x49\x2b\xe7\x0a\xc7\x5f\xde\x58", + (const uint8_t *)"\x04\x87\x7c\x39\xfd\x7c\x62\x23\x7e\x03\x82\x35\xe9\xc0\x75\xda\xb2\x61\x63\x0f\x78\xee\xb8\xed\xb9\x24\x87\x15\x9f\xff\xed\xfd\xf6\x04\x6c\x6f\x8b\x88\x1f\xa4\x07\xc4\xa4\xce\x6c\x28\xde\x0b\x19\xc1\xf4\xe2\x9f\x1f\xcb\xc5\xa5\x8f\xfd\x14\x32\xa3\xe0\x93\x8a", + (const uint8_t *)"\x04\x73\x84\xc5\x1a\xe8\x1a\xdd\x0a\x52\x3a\xdb\xb1\x86\xc9\x1b\x90\x6f\xfb\x64\xc2\xc7\x65\x80\x2b\xf2\x6d\xbd\x13\xbd\xf1\x2c\x31\x9e\x80\xc2\x21\x3a\x13\x6c\x8e\xe0\x3d\x78\x74\xfd\x22\xb7\x0d\x68\xe7\xde\xe4\x69\xde\xcf\xbb\xb5\x10\xee\x9a\x46\x0c\xda\x45", +}; + +#define SIGNATURES 3 + +#define FLASH_META_START 0x08008000 +#define FLASH_META_CODELEN (FLASH_META_START + 0x0004) +#define FLASH_META_SIGINDEX1 (FLASH_META_START + 0x0008) +#define FLASH_META_SIGINDEX2 (FLASH_META_START + 0x0009) +#define FLASH_META_SIGINDEX3 (FLASH_META_START + 0x000A) +#define FLASH_OLD_APP_START 0x08010000 +#define FLASH_META_SIG1 (FLASH_META_START + 0x0040) +#define FLASH_META_SIG2 (FLASH_META_START + 0x0080) +#define FLASH_META_SIG3 (FLASH_META_START + 0x00C0) + +bool firmware_present_old(void) { + if (memcmp(FLASH_PTR(FLASH_META_START), &FIRMWARE_MAGIC_OLD, + 4)) { // magic does not match + return false; + } + if (*((const uint32_t *)FLASH_PTR(FLASH_META_CODELEN)) < + 8192) { // firmware reports smaller size than 8192 + return false; + } + if (*((const uint32_t *)FLASH_PTR(FLASH_META_CODELEN)) > + FLASH_APP_LEN) { // firmware reports bigger size than flash size + return false; + } + + return true; +} + +int signatures_old_ok(void) { + const uint32_t codelen = *((const uint32_t *)FLASH_META_CODELEN); + const uint8_t sigindex1 = *((const uint8_t *)FLASH_META_SIGINDEX1); + const uint8_t sigindex2 = *((const uint8_t *)FLASH_META_SIGINDEX2); + const uint8_t sigindex3 = *((const uint8_t *)FLASH_META_SIGINDEX3); + + if (codelen > FLASH_APP_LEN) { + return false; + } + + uint8_t hash[32]; + sha256_Raw(FLASH_PTR(FLASH_OLD_APP_START), codelen, hash); + + if (sigindex1 < 1 || sigindex1 > PUBKEYS) return SIG_FAIL; // invalid index + if (sigindex2 < 1 || sigindex2 > PUBKEYS) return SIG_FAIL; // invalid index + if (sigindex3 < 1 || sigindex3 > PUBKEYS) return SIG_FAIL; // invalid index + + if (sigindex1 == sigindex2) return SIG_FAIL; // duplicate use + if (sigindex1 == sigindex3) return SIG_FAIL; // duplicate use + if (sigindex2 == sigindex3) return SIG_FAIL; // duplicate use + + if (0 != ecdsa_verify_digest(&secp256k1, pubkey[sigindex1 - 1], + (const uint8_t *)FLASH_META_SIG1, + hash)) { // failure + return SIG_FAIL; + } + if (0 != ecdsa_verify_digest(&secp256k1, pubkey[sigindex2 - 1], + (const uint8_t *)FLASH_META_SIG2, + hash)) { // failure + return SIG_FAIL; + } + if (0 != ecdsa_verify_digest(&secp256k1, pubkey[sigindex3 - 1], + (const uint8_t *)FLASH_META_SIG3, + hash)) { // failture + return SIG_FAIL; + } + + return SIG_OK; +} + +void compute_firmware_fingerprint(const image_header *hdr, uint8_t hash[32]) { + image_header copy; + memcpy(©, hdr, sizeof(image_header)); + memzero(copy.sig1, sizeof(copy.sig1)); + memzero(copy.sig2, sizeof(copy.sig2)); + memzero(copy.sig3, sizeof(copy.sig3)); + copy.sigindex1 = 0; + copy.sigindex2 = 0; + copy.sigindex3 = 0; + sha256_Raw((const uint8_t *)©, sizeof(image_header), hash); +} + +bool firmware_present_new(void) { + const image_header *hdr = + (const image_header *)FLASH_PTR(FLASH_FWHEADER_START); + if (hdr->magic != FIRMWARE_MAGIC_NEW) return false; + // we need to ignore hdrlen for now + // because we keep reset_handler ptr there + // for compatibility with older bootloaders + // after this is no longer necessary, let's uncomment the line below: + // if (hdr->hdrlen != FLASH_FWHEADER_LEN) return false; + if (hdr->codelen > FLASH_APP_LEN) return false; + if (hdr->codelen < 4096) return false; + + return true; +} + +int signatures_new_ok(const image_header *hdr, uint8_t store_fingerprint[32]) { + uint8_t hash[32]; + compute_firmware_fingerprint(hdr, hash); + + if (store_fingerprint) { + memcpy(store_fingerprint, hash, 32); + } + + if (hdr->sigindex1 < 1 || hdr->sigindex1 > PUBKEYS) + return SIG_FAIL; // invalid index + if (hdr->sigindex2 < 1 || hdr->sigindex2 > PUBKEYS) + return SIG_FAIL; // invalid index + if (hdr->sigindex3 < 1 || hdr->sigindex3 > PUBKEYS) + return SIG_FAIL; // invalid index + + if (hdr->sigindex1 == hdr->sigindex2) return SIG_FAIL; // duplicate use + if (hdr->sigindex1 == hdr->sigindex3) return SIG_FAIL; // duplicate use + if (hdr->sigindex2 == hdr->sigindex3) return SIG_FAIL; // duplicate use + + if (0 != ecdsa_verify_digest(&secp256k1, pubkey[hdr->sigindex1 - 1], + hdr->sig1, hash)) { // failure + return SIG_FAIL; + } + if (0 != ecdsa_verify_digest(&secp256k1, pubkey[hdr->sigindex2 - 1], + hdr->sig2, hash)) { // failure + return SIG_FAIL; + } + if (0 != ecdsa_verify_digest(&secp256k1, pubkey[hdr->sigindex3 - 1], + hdr->sig3, hash)) { // failure + return SIG_FAIL; + } + + return SIG_OK; +} + +int mem_is_empty(const uint8_t *src, uint32_t len) { + for (uint32_t i = 0; i < len; i++) { + if (src[i]) return 0; + } + return 1; +} + +int check_firmware_hashes(const image_header *hdr) { + uint8_t hash[32]; + // check hash of the first code chunk + sha256_Raw(FLASH_PTR(FLASH_APP_START), (64 - 1) * 1024, hash); + if (0 != memcmp(hash, hdr->hashes, 32)) return SIG_FAIL; + // check remaining used chunks + uint32_t total_len = FLASH_FWHEADER_LEN + hdr->codelen; + int used_chunks = total_len / FW_CHUNK_SIZE; + if (total_len % FW_CHUNK_SIZE > 0) { + used_chunks++; + } + for (int i = 1; i < used_chunks; i++) { + sha256_Raw(FLASH_PTR(FLASH_FWHEADER_START + (64 * i) * 1024), 64 * 1024, + hash); + if (0 != memcmp(hdr->hashes + 32 * i, hash, 32)) return SIG_FAIL; + } + // check unused chunks + for (int i = used_chunks; i < 16; i++) { + if (!mem_is_empty(hdr->hashes + 32 * i, 32)) return SIG_FAIL; + } + // all OK + return SIG_OK; +} diff --git a/legacy/bootloader/signatures.h b/legacy/bootloader/signatures.h new file mode 100644 index 0000000000..c47e325e40 --- /dev/null +++ b/legacy/bootloader/signatures.h @@ -0,0 +1,69 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __SIGNATURES_H__ +#define __SIGNATURES_H__ + +#include +#include + +extern const uint32_t FIRMWARE_MAGIC_OLD; // TRZR +extern const uint32_t FIRMWARE_MAGIC_NEW; // TRZF + +#define SIG_OK 0x5A3CA5C3 +#define SIG_FAIL 0x00000000 + +bool firmware_present_old(void); +int signatures_old_ok(void); + +// we use the same structure as T2 firmware header +// but we don't use the field sig +// and rather introduce fields sig1, sig2, sig3 +// immediately following the chunk hashes + +typedef struct { + uint32_t magic; + uint32_t hdrlen; + uint32_t expiry; + uint32_t codelen; + uint32_t version; + uint32_t fix_version; + uint8_t __reserved1[8]; + uint8_t hashes[512]; + uint8_t sig1[64]; + uint8_t sig2[64]; + uint8_t sig3[64]; + uint8_t sigindex1; + uint8_t sigindex2; + uint8_t sigindex3; + uint8_t __reserved2[220]; + uint8_t __sigmask; + uint8_t __sig[64]; +} __attribute__((packed)) image_header; + +#define FW_CHUNK_SIZE 65536 + +bool firmware_present_new(void); +void compute_firmware_fingerprint(const image_header *hdr, uint8_t hash[32]); +int signatures_new_ok(const image_header *hdr, uint8_t store_fingerprint[32]); +int check_firmware_hashes(const image_header *hdr); + +int mem_is_empty(const uint8_t *src, uint32_t len); + +#endif diff --git a/legacy/bootloader/usb.c b/legacy/bootloader/usb.c new file mode 100644 index 0000000000..8af36b7c94 --- /dev/null +++ b/legacy/bootloader/usb.c @@ -0,0 +1,467 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include + +#include + +#include "bootloader.h" +#include "buttons.h" +#include "ecdsa.h" +#include "layout.h" +#include "memory.h" +#include "memzero.h" +#include "oled.h" +#include "rng.h" +#include "secp256k1.h" +#include "sha2.h" +#include "signatures.h" +#include "usb.h" +#include "util.h" + +#include "usb21_standard.h" +#include "webusb.h" +#include "winusb.h" + +#include "usb_desc.h" +#include "usb_erase.h" +#include "usb_send.h" + +enum { + STATE_READY, + STATE_OPEN, + STATE_FLASHSTART, + STATE_FLASHING, + STATE_CHECK, + STATE_END, +}; + +static uint32_t flash_pos = 0, flash_len = 0; +static uint32_t chunk_idx = 0; +static char flash_state = STATE_READY; + +static uint32_t FW_HEADER[FLASH_FWHEADER_LEN / sizeof(uint32_t)]; +static uint32_t FW_CHUNK[FW_CHUNK_SIZE / sizeof(uint32_t)]; + +static void check_and_write_chunk(void) { + uint32_t offset = (chunk_idx == 0) ? FLASH_FWHEADER_LEN : 0; + uint32_t chunk_pos = flash_pos % FW_CHUNK_SIZE; + if (chunk_pos == 0) { + chunk_pos = FW_CHUNK_SIZE; + } + uint8_t hash[32]; + SHA256_CTX ctx; + sha256_Init(&ctx); + sha256_Update(&ctx, (const uint8_t *)FW_CHUNK + offset, chunk_pos - offset); + if (chunk_pos < 64 * 1024) { + // pad with FF + for (uint32_t i = chunk_pos; i < 64 * 1024; i += 4) { + sha256_Update(&ctx, (const uint8_t *)"\xFF\xFF\xFF\xFF", 4); + } + } + sha256_Final(&ctx, hash); + + const image_header *hdr = (const image_header *)FW_HEADER; + // invalid chunk sent + if (0 != memcmp(hash, hdr->hashes + chunk_idx * 32, 32)) { + // erase storage + erase_storage(); + flash_state = STATE_END; + show_halt("Error installing", "firmware."); + return; + } + + flash_wait_for_last_operation(); + flash_clear_status_flags(); + flash_unlock(); + for (uint32_t i = offset / sizeof(uint32_t); i < chunk_pos / sizeof(uint32_t); + i++) { + flash_program_word( + FLASH_FWHEADER_START + chunk_idx * FW_CHUNK_SIZE + i * sizeof(uint32_t), + FW_CHUNK[i]); + } + flash_wait_for_last_operation(); + flash_lock(); + + // all done + if (flash_len == flash_pos) { + // check remaining chunks if any + for (uint32_t i = chunk_idx + 1; i < 16; i++) { + // hash should be empty if the chunk is unused + if (!mem_is_empty(hdr->hashes + 32 * i, 32)) { + flash_state = STATE_END; + show_halt("Error installing", "firmware."); + return; + } + } + } + + memzero(FW_CHUNK, sizeof(FW_CHUNK)); + chunk_idx++; +} + +static void rx_callback(usbd_device *dev, uint8_t ep) { + (void)ep; + static uint16_t msg_id = 0xFFFF; + static uint8_t buf[64] __attribute__((aligned(4))); + static uint32_t w; + static int wi; + static int old_was_signed; + + if (usbd_ep_read_packet(dev, ENDPOINT_ADDRESS_OUT, buf, 64) != 64) return; + + if (flash_state == STATE_END) { + return; + } + + if (flash_state == STATE_READY || flash_state == STATE_OPEN || + flash_state == STATE_FLASHSTART || flash_state == STATE_CHECK) { + if (buf[0] != '?' || buf[1] != '#' || + buf[2] != '#') { // invalid start - discard + return; + } + // struct.unpack(">HL") => msg, size + msg_id = (buf[3] << 8) + buf[4]; + } + + if (flash_state == STATE_READY || flash_state == STATE_OPEN) { + if (msg_id == 0x0000) { // Initialize message (id 0) + send_msg_features(dev); + flash_state = STATE_OPEN; + return; + } + if (msg_id == 0x0037) { // GetFeatures message (id 55) + send_msg_features(dev); + return; + } + if (msg_id == 0x0001) { // Ping message (id 1) + send_msg_success(dev); + return; + } + if (msg_id == 0x0005) { // WipeDevice message (id 5) + layoutDialog(&bmp_icon_question, "Cancel", "Confirm", NULL, + "Do you really want to", "wipe the device?", NULL, + "All data will be lost.", NULL, NULL); + bool but = get_button_response(); + if (but) { + erase_storage_code_progress(); + flash_state = STATE_END; + layoutDialog(&bmp_icon_ok, NULL, NULL, NULL, "Device", + "successfully wiped.", NULL, "You may now", + "unplug your TREZOR.", NULL); + send_msg_success(dev); + } else { + flash_state = STATE_END; + layoutDialog(&bmp_icon_warning, NULL, NULL, NULL, "Device wipe", + "aborted.", NULL, "You may now", "unplug your TREZOR.", + NULL); + send_msg_failure(dev); + } + return; + } + } + + if (flash_state == STATE_OPEN) { + if (msg_id == 0x0006) { // FirmwareErase message (id 6) + bool proceed = false; + if (firmware_present_new()) { + layoutDialog(&bmp_icon_question, "Abort", "Continue", NULL, + "Install new", "firmware?", NULL, "Never do this without", + "your recovery card!", NULL); + proceed = get_button_response(); + } else { + proceed = true; + } + if (proceed) { + // check whether the current firmware is signed (old or new method) + if (firmware_present_new()) { + const image_header *hdr = + (const image_header *)FLASH_PTR(FLASH_FWHEADER_START); + old_was_signed = + signatures_new_ok(hdr, NULL) & check_firmware_hashes(hdr); + } else if (firmware_present_old()) { + old_was_signed = signatures_old_ok(); + } else { + old_was_signed = SIG_FAIL; + } + erase_code_progress(); + send_msg_success(dev); + flash_state = STATE_FLASHSTART; + } else { + send_msg_failure(dev); + flash_state = STATE_END; + layoutDialog(&bmp_icon_warning, NULL, NULL, NULL, + "Firmware installation", "aborted.", NULL, "You may now", + "unplug your TREZOR.", NULL); + } + return; + } + return; + } + + if (flash_state == STATE_FLASHSTART) { + if (msg_id == 0x0007) { // FirmwareUpload message (id 7) + if (buf[9] != 0x0a) { // invalid contents + send_msg_failure(dev); + flash_state = STATE_END; + show_halt("Error installing", "firmware."); + return; + } + // read payload length + const uint8_t *p = buf + 10; + flash_len = readprotobufint(&p); + if (flash_len <= FLASH_FWHEADER_LEN) { // firmware is too small + send_msg_failure(dev); + flash_state = STATE_END; + show_halt("Firmware is too small.", NULL); + return; + } + if (flash_len > + FLASH_FWHEADER_LEN + FLASH_APP_LEN) { // firmware is too big + send_msg_failure(dev); + flash_state = STATE_END; + show_halt("Firmware is too big.", NULL); + return; + } + // check firmware magic + if (memcmp(p, &FIRMWARE_MAGIC_NEW, 4) != 0) { + send_msg_failure(dev); + flash_state = STATE_END; + show_halt("Wrong firmware header.", NULL); + return; + } + memzero(FW_HEADER, sizeof(FW_HEADER)); + memzero(FW_CHUNK, sizeof(FW_CHUNK)); + flash_state = STATE_FLASHING; + flash_pos = 0; + chunk_idx = 0; + w = 0; + while (p < buf + 64) { + w = (w >> 8) | (*p << 24); // assign byte to first byte of uint32_t w + wi++; + if (wi == 4) { + FW_HEADER[flash_pos / 4] = w; + flash_pos += 4; + wi = 0; + } + p++; + } + return; + } + return; + } + + if (flash_state == STATE_FLASHING) { + if (buf[0] != '?') { // invalid contents + send_msg_failure(dev); + flash_state = STATE_END; + show_halt("Error installing", "firmware."); + return; + } + + static uint8_t flash_anim = 0; + if (flash_anim % 32 == 4) { + layoutProgress("INSTALLING ... Please wait", + 1000 * flash_pos / flash_len); + } + flash_anim++; + + const uint8_t *p = buf + 1; + while (p < buf + 64 && flash_pos < flash_len) { + w = (w >> 8) | (*p << 24); // assign byte to first byte of uint32_t w + wi++; + if (wi == 4) { + if (flash_pos < FLASH_FWHEADER_LEN) { + FW_HEADER[flash_pos / 4] = w; + } else { + FW_CHUNK[(flash_pos % FW_CHUNK_SIZE) / 4] = w; + } + flash_pos += 4; + wi = 0; + // finished the whole chunk + if (flash_pos % FW_CHUNK_SIZE == 0) { + check_and_write_chunk(); + } + } + p++; + } + // flashing done + if (flash_pos == flash_len) { + // flush remaining data in the last chunk + if (flash_pos % FW_CHUNK_SIZE > 0) { + check_and_write_chunk(); + } + flash_state = STATE_CHECK; + const image_header *hdr = (const image_header *)FW_HEADER; + if (SIG_OK != signatures_new_ok(hdr, NULL)) { + send_msg_buttonrequest_firmwarecheck(dev); + return; + } + } else { + return; + } + } + + if (flash_state == STATE_CHECK) { + // use the firmware header from RAM + const image_header *hdr = (const image_header *)FW_HEADER; + + bool hash_check_ok; + // show fingerprint of unsigned firmware + if (SIG_OK != signatures_new_ok(hdr, NULL)) { + if (msg_id != 0x001B) { // ButtonAck message (id 27) + return; + } + uint8_t hash[32]; + compute_firmware_fingerprint(hdr, hash); + layoutFirmwareFingerprint(hash); + hash_check_ok = get_button_response(); + } else { + hash_check_ok = true; + } + + layoutProgress("INSTALLING ... Please wait", 1000); + // wipe storage if: + // 1) old firmware was unsigned or not present + // 2) signatures are not OK + // 3) hashes are not OK + if (SIG_OK != old_was_signed || SIG_OK != signatures_new_ok(hdr, NULL) || + SIG_OK != check_firmware_hashes(hdr)) { + // erase storage + erase_storage(); + // check erasure + uint8_t hash[32]; + sha256_Raw(FLASH_PTR(FLASH_STORAGE_START), FLASH_STORAGE_LEN, hash); + if (memcmp(hash, + "\x2d\x86\x4c\x0b\x78\x9a\x43\x21\x4e\xee\x85\x24\xd3\x18\x20" + "\x75\x12\x5e\x5c\xa2\xcd\x52\x7f\x35\x82\xec\x87\xff\xd9\x40" + "\x76\xbc", + 32) != 0) { + send_msg_failure(dev); + show_halt("Error installing", "firmware."); + return; + } + } + flash_wait_for_last_operation(); + flash_clear_status_flags(); + flash_unlock(); + // write firmware header only when hash was confirmed + if (hash_check_ok) { + for (size_t i = 0; i < FLASH_FWHEADER_LEN / sizeof(uint32_t); i++) { + flash_program_word(FLASH_FWHEADER_START + i * sizeof(uint32_t), + FW_HEADER[i]); + } + } else { + for (size_t i = 0; i < FLASH_FWHEADER_LEN / sizeof(uint32_t); i++) { + flash_program_word(FLASH_FWHEADER_START + i * sizeof(uint32_t), 0); + } + } + flash_wait_for_last_operation(); + flash_lock(); + + flash_state = STATE_END; + if (hash_check_ok) { + layoutDialog(&bmp_icon_ok, NULL, NULL, NULL, "New firmware", + "successfully installed.", NULL, "You may now", + "unplug your TREZOR.", NULL); + send_msg_success(dev); + shutdown(); + } else { + layoutDialog(&bmp_icon_warning, NULL, NULL, NULL, "Firmware installation", + "aborted.", NULL, "You need to repeat", "the procedure with", + "the correct firmware."); + send_msg_failure(dev); + shutdown(); + } + return; + } +} + +static void set_config(usbd_device *dev, uint16_t wValue) { + (void)wValue; + + usbd_ep_setup(dev, ENDPOINT_ADDRESS_IN, USB_ENDPOINT_ATTR_INTERRUPT, 64, 0); + usbd_ep_setup(dev, ENDPOINT_ADDRESS_OUT, USB_ENDPOINT_ATTR_INTERRUPT, 64, + rx_callback); +} + +static usbd_device *usbd_dev; +static uint8_t usbd_control_buffer[256] __attribute__((aligned(2))); + +static const struct usb_device_capability_descriptor *capabilities[] = { + (const struct usb_device_capability_descriptor + *)&webusb_platform_capability_descriptor, +}; + +static const struct usb_bos_descriptor bos_descriptor = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + .bNumDeviceCaps = sizeof(capabilities) / sizeof(capabilities[0]), + .capabilities = capabilities}; + +static void usbInit(void) { + usbd_dev = usbd_init(&otgfs_usb_driver, &dev_descr, &config, usb_strings, + sizeof(usb_strings) / sizeof(const char *), + usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(usbd_dev, set_config); + usb21_setup(usbd_dev, &bos_descriptor); + webusb_setup(usbd_dev, "trezor.io/start"); + winusb_setup(usbd_dev, USB_INTERFACE_INDEX_MAIN); +} + +static void checkButtons(void) { + static bool btn_left = false, btn_right = false, btn_final = false; + if (btn_final) { + return; + } + uint16_t state = gpio_port_read(BTN_PORT); + if ((state & (BTN_PIN_YES | BTN_PIN_NO)) != (BTN_PIN_YES | BTN_PIN_NO)) { + if ((state & BTN_PIN_NO) != BTN_PIN_NO) { + btn_left = true; + } + if ((state & BTN_PIN_YES) != BTN_PIN_YES) { + btn_right = true; + } + } + if (btn_left) { + oledBox(0, 0, 3, 3, true); + } + if (btn_right) { + oledBox(OLED_WIDTH - 4, 0, OLED_WIDTH - 1, 3, true); + } + if (btn_left || btn_right) { + oledRefresh(); + } + if (btn_left && btn_right) { + btn_final = true; + } +} + +void usbLoop(void) { + bool firmware_present = firmware_present_new(); + usbInit(); + for (;;) { + usbd_poll(usbd_dev); + if (!firmware_present && + (flash_state == STATE_READY || flash_state == STATE_OPEN)) { + checkButtons(); + } + } +} diff --git a/legacy/bootloader/usb.h b/legacy/bootloader/usb.h new file mode 100644 index 0000000000..da506471ac --- /dev/null +++ b/legacy/bootloader/usb.h @@ -0,0 +1,25 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __USB_H__ +#define __USB_H__ + +void usbLoop(void); + +#endif diff --git a/legacy/bootloader/usb_desc.h b/legacy/bootloader/usb_desc.h new file mode 100644 index 0000000000..e23edbe7e0 --- /dev/null +++ b/legacy/bootloader/usb_desc.h @@ -0,0 +1,77 @@ +#define USB_INTERFACE_INDEX_MAIN 0 + +#define ENDPOINT_ADDRESS_IN (0x81) +#define ENDPOINT_ADDRESS_OUT (0x01) + +static const struct usb_device_descriptor dev_descr = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0210, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x1209, + .idProduct = 0x53c0, + .bcdDevice = 0x0100, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, +}; + +static const struct usb_endpoint_descriptor endpoints[2] = { + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_IN, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }, + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_OUT, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }}; + +static const struct usb_interface_descriptor iface[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = USB_INTERFACE_INDEX_MAIN, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0, + .endpoint = endpoints, + .extra = NULL, + .extralen = 0, +}}; + +static const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = iface, +}}; + +static const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x80, + .bMaxPower = 0x32, + .interface = ifaces, +}; + +static const char *usb_strings[] = { + "SatoshiLabs", + "TREZOR", + "000000000000000000000000", +}; diff --git a/legacy/bootloader/usb_erase.h b/legacy/bootloader/usb_erase.h new file mode 100644 index 0000000000..d5ebba0b1d --- /dev/null +++ b/legacy/bootloader/usb_erase.h @@ -0,0 +1,49 @@ +static void erase_storage_code_progress(void) { + flash_wait_for_last_operation(); + flash_clear_status_flags(); + flash_unlock(); + // erase storage area + for (int i = FLASH_STORAGE_SECTOR_FIRST; i <= FLASH_STORAGE_SECTOR_LAST; + i++) { + layoutProgress("WIPING ... Please wait", + 1000 * (i - FLASH_STORAGE_SECTOR_FIRST) / + (FLASH_CODE_SECTOR_LAST - FLASH_STORAGE_SECTOR_FIRST)); + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + // erase code area + for (int i = FLASH_CODE_SECTOR_FIRST; i <= FLASH_CODE_SECTOR_LAST; i++) { + layoutProgress("WIPING ... Please wait", + 1000 * (i - FLASH_STORAGE_SECTOR_FIRST) / + (FLASH_CODE_SECTOR_LAST - FLASH_STORAGE_SECTOR_FIRST)); + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + flash_wait_for_last_operation(); + flash_lock(); +} + +static void erase_code_progress(void) { + flash_wait_for_last_operation(); + flash_clear_status_flags(); + flash_unlock(); + for (int i = FLASH_CODE_SECTOR_FIRST; i <= FLASH_CODE_SECTOR_LAST; i++) { + layoutProgress("PREPARING ... Please wait", + 1000 * (i - FLASH_CODE_SECTOR_FIRST) / + (FLASH_CODE_SECTOR_LAST - FLASH_CODE_SECTOR_FIRST)); + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + layoutProgress("INSTALLING ... Please wait", 0); + flash_wait_for_last_operation(); + flash_lock(); +} + +static void erase_storage(void) { + flash_wait_for_last_operation(); + flash_clear_status_flags(); + flash_unlock(); + for (int i = FLASH_STORAGE_SECTOR_FIRST; i <= FLASH_STORAGE_SECTOR_LAST; + i++) { + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + flash_wait_for_last_operation(); + flash_lock(); +} diff --git a/legacy/bootloader/usb_send.h b/legacy/bootloader/usb_send.h new file mode 100644 index 0000000000..7f94ef9d8a --- /dev/null +++ b/legacy/bootloader/usb_send.h @@ -0,0 +1,92 @@ +static void send_msg_success(usbd_device *dev) { + uint8_t response[64]; + memzero(response, sizeof(response)); + // response: Success message (id 2), payload len 0 + memcpy(response, + // header + "?##" + // msg_id + "\x00\x02" + // msg_size + "\x00\x00\x00\x00", + 9); + while (usbd_ep_write_packet(dev, ENDPOINT_ADDRESS_IN, response, 64) != 64) { + } +} + +static void send_msg_failure(usbd_device *dev) { + uint8_t response[64]; + memzero(response, sizeof(response)); + // response: Failure message (id 3), payload len 2 + // - code = 99 (Failure_FirmwareError) + memcpy(response, + // header + "?##" + // msg_id + "\x00\x03" + // msg_size + "\x00\x00\x00\x02" + // data + "\x08" + "\x63", + 11); + while (usbd_ep_write_packet(dev, ENDPOINT_ADDRESS_IN, response, 64) != 64) { + } +} + +static void send_msg_features(usbd_device *dev) { + uint8_t response[64]; + memzero(response, sizeof(response)); + // response: Features message (id 17), payload len 25 + // - vendor = "trezor.io" + // - major_version = VERSION_MAJOR + // - minor_version = VERSION_MINOR + // - patch_version = VERSION_PATCH + // - bootloader_mode = True + // - firmware_present = True/False + // - model = "1" + memcpy(response, + // header + "?##" + // msg_id + "\x00\x11" + // msg_size + "\x00\x00\x00\x16" + // data + "\x0a" + "\x09" + "trezor.io" + "\x10" VERSION_MAJOR_CHAR "\x18" VERSION_MINOR_CHAR + "\x20" VERSION_PATCH_CHAR + "\x28" + "\x01" + "\x90\x01" + "\x00" + "\xaa" + "\x01" + "1", + 34); + response[30] = firmware_present_new() ? 0x01 : 0x00; + while (usbd_ep_write_packet(dev, ENDPOINT_ADDRESS_IN, response, 64) != 64) { + } +} + +static void send_msg_buttonrequest_firmwarecheck(usbd_device *dev) { + uint8_t response[64]; + memzero(response, sizeof(response)); + // response: ButtonRequest message (id 26), payload len 2 + // - code = ButtonRequest_FirmwareCheck (9) + memcpy(response, + // header + "?##" + // msg_id + "\x00\x1a" + // msg_size + "\x00\x00\x00\x02" + // data + "\x08" + "\x09", + 11); + while (usbd_ep_write_packet(dev, ENDPOINT_ADDRESS_IN, response, 64) != 64) { + } +} diff --git a/legacy/build.sh b/legacy/build.sh new file mode 100755 index 0000000000..6afb96c5da --- /dev/null +++ b/legacy/build.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +BOOTLOADER_COMMIT=${1:-HEAD} +FIRMWARE_COMMIT=${2:-HEAD} + +if [ "$BOOTLOADER_COMMIT" = "EMU" ]; then + export EMULATOR=1 +fi + +if [ "$EMULATOR" = 1 ]; then + IMAGE=trezor-mcu-emulator +else + IMAGE=trezor-mcu-build +fi + +docker build -t "$IMAGE" --build-arg EMULATOR=$EMULATOR . +docker run -it -v $(pwd):/src:z --user="$(stat -c "%u:%g" .)" "$IMAGE" \ + /src/script/fullbuild "$BOOTLOADER_COMMIT" "$FIRMWARE_COMMIT" diff --git a/legacy/buttons.c b/legacy/buttons.c new file mode 100644 index 0000000000..0a86dbe61b --- /dev/null +++ b/legacy/buttons.c @@ -0,0 +1,70 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "buttons.h" + +struct buttonState button; + +#if !EMULATOR +uint16_t buttonRead(void) { return gpio_port_read(BTN_PORT); } +#endif + +void buttonUpdate() { + static uint16_t last_state = BTN_PIN_YES | BTN_PIN_NO; + + uint16_t state = buttonRead(); + + if ((state & BTN_PIN_YES) == 0) { // Yes button is down + if ((last_state & BTN_PIN_YES) == 0) { // last Yes was down + if (button.YesDown < 2000000000) button.YesDown++; + button.YesUp = false; + } else { // last Yes was up + button.YesDown = 0; + button.YesUp = false; + } + } else { // Yes button is up + if ((last_state & BTN_PIN_YES) == 0) { // last Yes was down + button.YesDown = 0; + button.YesUp = true; + } else { // last Yes was up + button.YesDown = 0; + button.YesUp = false; + } + } + + if ((state & BTN_PIN_NO) == 0) { // No button is down + if ((last_state & BTN_PIN_NO) == 0) { // last No was down + if (button.NoDown < 2000000000) button.NoDown++; + button.NoUp = false; + } else { // last No was up + button.NoDown = 0; + button.NoUp = false; + } + } else { // No button is up + if ((last_state & BTN_PIN_NO) == 0) { // last No was down + button.NoDown = 0; + button.NoUp = true; + } else { // last No was up + button.NoDown = 0; + button.NoUp = false; + } + } + + last_state = state; +} diff --git a/legacy/buttons.h b/legacy/buttons.h new file mode 100644 index 0000000000..48f3c99842 --- /dev/null +++ b/legacy/buttons.h @@ -0,0 +1,50 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __BUTTONS_H__ +#define __BUTTONS_H__ + +#include +#include + +struct buttonState { + volatile bool YesUp; + volatile int YesDown; + volatile bool NoUp; + volatile int NoDown; +}; + +extern struct buttonState button; + +uint16_t buttonRead(void); +void buttonUpdate(void); + +#ifndef BTN_PORT +#define BTN_PORT GPIOC +#endif + +#ifndef BTN_PIN_YES +#define BTN_PIN_YES GPIO2 +#endif + +#ifndef BTN_PIN_NO +#define BTN_PIN_NO GPIO5 +#endif + +#endif diff --git a/legacy/common.c b/legacy/common.c new file mode 100644 index 0000000000..0bd282ead3 --- /dev/null +++ b/legacy/common.c @@ -0,0 +1,83 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.h" +#include +#include "bitmaps.h" +#include "firmware/usb.h" +#include "layout.h" +#include "oled.h" +#include "rng.h" +#include "util.h" + +uint8_t HW_ENTROPY_DATA[HW_ENTROPY_LEN]; + +void __attribute__((noreturn)) +__fatal_error(const char *expr, const char *msg, const char *file, int line_num, + const char *func) { + const BITMAP *icon = &bmp_icon_error; + char line[128] = {0}; + int y = icon->height + 3; + oledClear(); + + oledDrawBitmap(0, 0, icon); + oledDrawStringCenter(OLED_WIDTH / 2, (icon->height - FONT_HEIGHT) / 2 + 1, + "FATAL ERROR", FONT_STANDARD); + + snprintf(line, sizeof(line), "Expr: %s", expr ? expr : "(null)"); + oledDrawString(0, y, line, FONT_STANDARD); + y += FONT_HEIGHT + 1; + + snprintf(line, sizeof(line), "Msg: %s", msg ? msg : "(null)"); + oledDrawString(0, y, line, FONT_STANDARD); + y += FONT_HEIGHT + 1; + + const char *label = "File: "; + snprintf(line, sizeof(line), "%s:%d", file ? file : "(null)", line_num); + oledDrawStringRight(OLED_WIDTH - 1, y, line, FONT_STANDARD); + oledBox(0, y, oledStringWidth(label, FONT_STANDARD), y + FONT_HEIGHT, false); + oledDrawString(0, y, label, FONT_STANDARD); + y += FONT_HEIGHT + 1; + + snprintf(line, sizeof(line), "Func: %s", func ? func : "(null)"); + oledDrawString(0, y, line, FONT_STANDARD); + y += FONT_HEIGHT + 1; + + oledDrawString(0, y, "Contact TREZOR support.", FONT_STANDARD); + oledRefresh(); + + shutdown(); +} + +void __attribute__((noreturn)) +error_shutdown(const char *line1, const char *line2, const char *line3, + const char *line4) { + layoutDialog(&bmp_icon_error, NULL, NULL, NULL, line1, line2, line3, line4, + "Please unplug", "the device."); + shutdown(); +} + +#ifndef NDEBUG +void __assert_func(const char *file, int line, const char *func, + const char *expr) { + __fatal_error(expr, "assert failed", file, line, func); +} +#endif + +void hal_delay(uint32_t ms) { usbSleep(ms); } diff --git a/legacy/common.h b/legacy/common.h new file mode 100644 index 0000000000..1774bc7f02 --- /dev/null +++ b/legacy/common.h @@ -0,0 +1,43 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TREZORHAL_COMMON_H__ +#define __TREZORHAL_COMMON_H__ + +#include +#include "secbool.h" + +#define HW_ENTROPY_LEN (12 + 32) +extern uint8_t HW_ENTROPY_DATA[HW_ENTROPY_LEN]; + +void __attribute__((noreturn)) +__fatal_error(const char *expr, const char *msg, const char *file, int line, + const char *func); +void __attribute__((noreturn)) +error_shutdown(const char *line1, const char *line2, const char *line3, + const char *line4); + +#define ensure(expr, msg) \ + (((expr) == sectrue) \ + ? (void)0 \ + : __fatal_error(#expr, msg, __FILE__, __LINE__, __func__)) + +void hal_delay(uint32_t ms); + +#endif diff --git a/legacy/demo/Makefile b/legacy/demo/Makefile new file mode 100644 index 0000000000..306597b7ea --- /dev/null +++ b/legacy/demo/Makefile @@ -0,0 +1,18 @@ +APPVER = 1.0.0 + +NAME = demo + +OBJS += demo.o + +OBJS += ../vendor/trezor-crypto/bignum.o +OBJS += ../vendor/trezor-crypto/bip32.o +OBJS += ../vendor/trezor-crypto/ecdsa.o +OBJS += ../vendor/trezor-crypto/hmac.o +OBJS += ../vendor/trezor-crypto/ripemd160.o +OBJS += ../vendor/trezor-crypto/secp256k1.o +OBJS += ../vendor/trezor-crypto/sha2.o +OBJS += ../vendor/trezor-crypto/bip39.o +OBJS += ../vendor/trezor-crypto/pbkdf2.o +OBJS += ../vendor/trezor-crypto/memzero.o + +include ../Makefile.include diff --git a/legacy/demo/demo.c b/legacy/demo/demo.c new file mode 100644 index 0000000000..bad797012e --- /dev/null +++ b/legacy/demo/demo.c @@ -0,0 +1,309 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include "bitmaps.h" +#include "buttons.h" +#include "hmac.h" +#include "layout.h" +#include "oled.h" +#include "pbkdf2.h" +#include "rng.h" +#include "setup.h" + +const int states = 2; +int state = 0; +int frame = 0; + +uint8_t seed[128]; +uint8_t *pass = (uint8_t *)"meadow"; +uint32_t passlen; +uint8_t *salt = (uint8_t *)"TREZOR"; +uint32_t saltlen; + +static const struct usb_device_descriptor dev_descr = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x1209, + .idProduct = 0x53c1, + .bcdDevice = 0x0100, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, +}; + +/* got via usbhid-dump from CP2110 */ +static const uint8_t hid_report_descriptor[] = { + 0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x09, 0x01, 0x75, 0x08, 0x95, + 0x40, 0x26, 0xFF, 0x00, 0x15, 0x00, 0x85, 0x01, 0x95, 0x01, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x02, 0x95, 0x02, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x03, 0x95, 0x03, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x04, 0x95, 0x04, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x05, 0x95, 0x05, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x06, 0x95, 0x06, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x07, 0x95, 0x07, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x08, 0x95, 0x08, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x09, 0x95, 0x09, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0A, 0x95, 0x0A, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0B, 0x95, 0x0B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0C, 0x95, 0x0C, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0D, 0x95, 0x0D, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0E, 0x95, 0x0E, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0F, 0x95, 0x0F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x10, 0x95, 0x10, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x11, 0x95, 0x11, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x12, 0x95, 0x12, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x13, 0x95, 0x13, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x14, 0x95, 0x14, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x15, 0x95, 0x15, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x16, 0x95, 0x16, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x17, 0x95, 0x17, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x18, 0x95, 0x18, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x19, 0x95, 0x19, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1A, 0x95, 0x1A, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1B, 0x95, 0x1B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1C, 0x95, 0x1C, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1D, 0x95, 0x1D, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1E, 0x95, 0x1E, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1F, 0x95, 0x1F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x20, 0x95, 0x20, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x21, 0x95, 0x21, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x22, 0x95, 0x22, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x23, 0x95, 0x23, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x24, 0x95, 0x24, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x25, 0x95, 0x25, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x26, 0x95, 0x26, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x27, 0x95, 0x27, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x28, 0x95, 0x28, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x29, 0x95, 0x29, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2A, 0x95, 0x2A, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2B, 0x95, 0x2B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2C, 0x95, 0x2C, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2D, 0x95, 0x2D, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2E, 0x95, 0x2E, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2F, 0x95, 0x2F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x30, 0x95, 0x30, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x31, 0x95, 0x31, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x32, 0x95, 0x32, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x33, 0x95, 0x33, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x34, 0x95, 0x34, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x35, 0x95, 0x35, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x36, 0x95, 0x36, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x37, 0x95, 0x37, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x38, 0x95, 0x38, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x39, 0x95, 0x39, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3A, 0x95, 0x3A, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3B, 0x95, 0x3B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3C, 0x95, 0x3C, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3D, 0x95, 0x3D, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3E, 0x95, 0x3E, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3F, 0x95, 0x3F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x40, 0x95, 0x01, 0x09, 0x01, + 0xB1, 0x02, 0x85, 0x41, 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x42, + 0x95, 0x06, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x43, 0x95, 0x01, 0x09, 0x01, + 0xB1, 0x02, 0x85, 0x44, 0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x45, + 0x95, 0x04, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x46, 0x95, 0x02, 0x09, 0x01, + 0xB1, 0x02, 0x85, 0x47, 0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x50, + 0x95, 0x08, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x51, 0x95, 0x01, 0x09, 0x01, + 0xB1, 0x02, 0x85, 0x52, 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x60, + 0x95, 0x0A, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x61, 0x95, 0x3F, 0x09, 0x01, + 0xB1, 0x02, 0x85, 0x62, 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x63, + 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x64, 0x95, 0x3F, 0x09, 0x01, + 0xB1, 0x02, 0x85, 0x65, 0x95, 0x3E, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x66, + 0x95, 0x13, 0x09, 0x01, 0xB1, 0x02, 0xC0, +}; + +static const struct { + struct usb_hid_descriptor hid_descriptor; + struct { + uint8_t bReportDescriptorType; + uint16_t wDescriptorLength; + } __attribute__((packed)) hid_report; +} __attribute__((packed)) +hid_function = {.hid_descriptor = + { + .bLength = sizeof(hid_function), + .bDescriptorType = USB_DT_HID, + .bcdHID = 0x0111, + .bCountryCode = 0, + .bNumDescriptors = 1, + }, + .hid_report = { + .bReportDescriptorType = USB_DT_REPORT, + .wDescriptorLength = sizeof(hid_report_descriptor), + }}; + +static const struct usb_endpoint_descriptor hid_endpoints[2] = { + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }, + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x02, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }}; + +static const struct usb_interface_descriptor hid_iface[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0, + .endpoint = hid_endpoints, + .extra = &hid_function, + .extralen = sizeof(hid_function), +}}; + +static const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = hid_iface, +}}; + +static const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x80, + .bMaxPower = 0x32, + .interface = ifaces, +}; + +static const char *usb_strings[] = { + "SatoshiLabs", + "TREZOR", + "01234567", +}; + +static enum usbd_request_return_codes hid_control_request( + usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + usbd_control_complete_callback *complete) { + (void)complete; + (void)dev; + + if ((req->bmRequestType != 0x81) || + (req->bRequest != USB_REQ_GET_DESCRIPTOR) || (req->wValue != 0x2200)) + return 0; + + /* Handle the HID report descriptor. */ + *buf = (uint8_t *)hid_report_descriptor; + *len = sizeof(hid_report_descriptor); + + return 1; +} + +static void hid_rx_callback(usbd_device *dev, uint8_t ep) { + (void)dev; + (void)ep; +} + +static void hid_set_config(usbd_device *dev, uint16_t wValue) { + (void)wValue; + + usbd_ep_setup(dev, 0x81, USB_ENDPOINT_ATTR_INTERRUPT, 64, 0); + usbd_ep_setup(dev, 0x02, USB_ENDPOINT_ATTR_INTERRUPT, 64, hid_rx_callback); + + usbd_register_control_callback( + dev, USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, hid_control_request); +} + +static usbd_device *usbd_dev; +static uint8_t usbd_control_buffer[128]; + +void usbInit(void) { + usbd_dev = usbd_init(&otgfs_usb_driver, &dev_descr, &config, usb_strings, 3, + usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(usbd_dev, hid_set_config); +} + +int main(void) { +#ifndef APPVER + setup(); + __stack_chk_guard = random32(); // this supports compiler provided + // unpredictable stack protection checks + oledInit(); +#else + setupApp(); + __stack_chk_guard = random32(); // this supports compiler provided + // unpredictable stack protection checks +#endif + + usbInit(); + + passlen = strlen((char *)pass); + saltlen = strlen((char *)salt); + + for (;;) { + frame = 0; + switch (state) { + case 0: + oledClear(); + oledDrawBitmap(40, 0, &bmp_logo64); + break; + } + oledRefresh(); + + do { + usbd_poll(usbd_dev); + switch (state) { + case 1: + layoutProgress("WORKING", frame % 41 * 25); + pbkdf2_hmac_sha512(pass, passlen, salt, saltlen, 100, seed, 64); + usbd_ep_write_packet(usbd_dev, 0x81, seed, 64); + break; + } + + buttonUpdate(); + frame += 1; + } while (!button.YesUp && !button.NoUp); + + if (button.YesUp) { + state = (state + 1) % states; + oledSwipeLeft(); + } else { + state = (state + states - 1) % states; + oledSwipeRight(); + } + } + + return 0; +} diff --git a/legacy/emulator/Makefile b/legacy/emulator/Makefile new file mode 100644 index 0000000000..1243a7fe54 --- /dev/null +++ b/legacy/emulator/Makefile @@ -0,0 +1,17 @@ +EMULATOR := 1 + +OBJS += setup.o + +OBJS += buttons.o +OBJS += memory.o +OBJS += oled.o +OBJS += rng.o +OBJS += timer.o +OBJS += udp.o + +OBJS += strl.o + +libemulator.a: $(OBJS) + $(AR) rcs $@ $(OBJS) + +include ../Makefile.include diff --git a/legacy/emulator/buttons.c b/legacy/emulator/buttons.c new file mode 100644 index 0000000000..2d456aafbb --- /dev/null +++ b/legacy/emulator/buttons.c @@ -0,0 +1,40 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "buttons.h" + +#if !HEADLESS +#include +#endif + +uint16_t buttonRead(void) { + uint16_t state = 0; + +#if !HEADLESS + const uint8_t *scancodes = SDL_GetKeyboardState(NULL); + if (scancodes[SDL_SCANCODE_LEFT]) { + state |= BTN_PIN_NO; + } + if (scancodes[SDL_SCANCODE_RIGHT]) { + state |= BTN_PIN_YES; + } +#endif + + return ~state; +} diff --git a/legacy/emulator/emulator.h b/legacy/emulator/emulator.h new file mode 100644 index 0000000000..ae05f6f9ff --- /dev/null +++ b/legacy/emulator/emulator.h @@ -0,0 +1,38 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __EMULATOR_H__ +#define __EMULATOR_H__ + +#if EMULATOR + +#include "strl.h" + +#include + +void emulatorPoll(void); +void emulatorRandom(void *buffer, size_t size); + +void emulatorSocketInit(void); +size_t emulatorSocketRead(int *iface, void *buffer, size_t size); +size_t emulatorSocketWrite(int iface, const void *buffer, size_t size); + +#endif + +#endif diff --git a/legacy/emulator/memory.c b/legacy/emulator/memory.c new file mode 100644 index 0000000000..a1acac60c9 --- /dev/null +++ b/legacy/emulator/memory.c @@ -0,0 +1,136 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include + +#include "memory.h" + +void flash_lock(void) { sync(); } + +void flash_unlock(void) {} + +void flash_clear_status_flags(void) {} + +void flash_lock_option_bytes(void) {} +void flash_unlock_option_bytes(void) {} + +void flash_program_option_bytes(uint32_t data) { (void)data; } + +static ssize_t sector_to_offset(uint8_t sector) { + switch (sector) { + case 0: + return 0x0; + case 1: + return 0x4000; + case 2: + return 0x8000; + case 3: + return 0xC000; + case 4: + return 0x10000; + case 5: + return 0x20000; + case 6: + return 0x40000; + case 7: + return 0x60000; + case 8: + return 0x80000; + default: + return -1; + } +} + +static void *sector_to_address(uint8_t sector) { + ssize_t offset = sector_to_offset(sector); + if (offset < 0) { + return NULL; + } + + return (void *)FLASH_PTR(FLASH_ORIGIN + offset); +} + +static ssize_t sector_to_size(uint8_t sector) { + ssize_t start = sector_to_offset(sector); + if (start < 0) { + return -1; + } + + ssize_t end = sector_to_offset(sector + 1); + if (end < 0) { + return -1; + } + + return end - start; +} + +void flash_erase_sector(uint8_t sector, uint32_t program_size) { + (void)program_size; + + void *address = sector_to_address(sector); + if (address == NULL) { + return; + } + + ssize_t size = sector_to_size(sector); + if (size < 0) { + return; + } + + memset(address, 0xFF, size); +} + +void flash_erase_all_sectors(uint32_t program_size) { + (void)program_size; + + memset(emulator_flash_base, 0xFF, FLASH_TOTAL_SIZE); +} + +void flash_program_word(uint32_t address, uint32_t data) { + *(volatile uint32_t *)FLASH_PTR(address) = data; +} + +void flash_program_byte(uint32_t address, uint8_t data) { + *(volatile uint8_t *)FLASH_PTR(address) = data; +} + +static bool flash_locked = true; +void svc_flash_unlock(void) { + assert(flash_locked); + flash_locked = false; +} +void svc_flash_program(uint32_t size) { + (void)size; + assert(!flash_locked); +} +void svc_flash_erase_sector(uint16_t sector) { + assert(!flash_locked); + assert(sector >= FLASH_STORAGE_SECTOR_FIRST && + sector <= FLASH_STORAGE_SECTOR_LAST); + flash_erase_sector(sector, 3); +} +uint32_t svc_flash_lock(void) { + assert(!flash_locked); + flash_locked = true; + sync(); + return 0; +} diff --git a/legacy/emulator/oled.c b/legacy/emulator/oled.c new file mode 100644 index 0000000000..a4466e9932 --- /dev/null +++ b/legacy/emulator/oled.c @@ -0,0 +1,150 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "oled.h" + +#if HEADLESS + +void oledInit(void) {} +void oledRefresh(void) {} +void emulatorPoll(void) {} + +#else + +#include + +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Rect dstrect; + +#define ENV_OLED_FULLSCREEN "TREZOR_OLED_FULLSCREEN" +#define ENV_OLED_SCALE "TREZOR_OLED_SCALE" + +static int emulatorFullscreen(void) { + const char *variable = getenv(ENV_OLED_FULLSCREEN); + if (!variable) { + return 0; + } + return atoi(variable); +} + +static int emulatorScale(void) { + const char *variable = getenv(ENV_OLED_SCALE); + if (!variable) { + return 1; + } + int scale = atoi(variable); + if (scale >= 1 && scale <= 16) { + return scale; + } + return 1; +} + +void oledInit(void) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError()); + exit(1); + } + atexit(SDL_Quit); + + int scale = emulatorScale(); + int fullscreen = emulatorFullscreen(); + + SDL_Window *window = SDL_CreateWindow( + "TREZOR", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + OLED_WIDTH * scale, OLED_HEIGHT * scale, + fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + + if (window == NULL) { + fprintf(stderr, "Failed to create window: %s\n", SDL_GetError()); + exit(1); + } + + renderer = SDL_CreateRenderer(window, -1, 0); + if (!renderer) { + fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError()); + exit(1); + } + if (fullscreen) { + SDL_DisplayMode current_mode; + if (SDL_GetCurrentDisplayMode(0, ¤t_mode) != 0) { + fprintf(stderr, "Failed to get current display mode: %s\n", + SDL_GetError()); + exit(1); + } + + dstrect.x = (current_mode.w - OLED_WIDTH * scale) / 2; + dstrect.y = (current_mode.h - OLED_HEIGHT * scale) / 2; + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); + SDL_RenderClear(renderer); + SDL_ShowCursor(SDL_DISABLE); + } else { + dstrect.x = 0; + dstrect.y = 0; + } + + dstrect.w = OLED_WIDTH * scale; + dstrect.h = OLED_HEIGHT * scale; + + texture = + SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, OLED_WIDTH, OLED_HEIGHT); + + oledClear(); + oledRefresh(); +} + +void oledRefresh(void) { + /* Draw triangle in upper right corner */ + oledInvertDebugLink(); + + const uint8_t *buffer = oledGetBuffer(); + + static uint32_t data[OLED_HEIGHT][OLED_WIDTH]; + + for (size_t i = 0; i < OLED_BUFSIZE; i++) { + int x = (OLED_BUFSIZE - 1 - i) % OLED_WIDTH; + int y = (OLED_BUFSIZE - 1 - i) / OLED_WIDTH * 8 + 7; + + for (uint8_t shift = 0; shift < 8; shift++, y--) { + bool set = (buffer[i] >> shift) & 1; + data[y][x] = set ? 0xFFFFFFFF : 0xFF000000; + } + } + + SDL_UpdateTexture(texture, NULL, data, OLED_WIDTH * sizeof(uint32_t)); + SDL_RenderCopy(renderer, texture, NULL, &dstrect); + SDL_RenderPresent(renderer); + + /* Return it back */ + oledInvertDebugLink(); +} + +void emulatorPoll(void) { + SDL_Event event; + + if (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + exit(1); + } + } +} + +#endif diff --git a/legacy/emulator/rng.c b/legacy/emulator/rng.c new file mode 100644 index 0000000000..cd6eb616ee --- /dev/null +++ b/legacy/emulator/rng.c @@ -0,0 +1,32 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "rng.h" + +uint32_t random32(void) { + static uint32_t last = 0; + uint32_t new; + + do { + emulatorRandom(&new, sizeof(new)); + } while (last == new); + + last = new; + return new; +} diff --git a/legacy/emulator/setup.c b/legacy/emulator/setup.c new file mode 100644 index 0000000000..81b283ecc4 --- /dev/null +++ b/legacy/emulator/setup.c @@ -0,0 +1,108 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "memory.h" +#include "oled.h" +#include "rng.h" +#include "setup.h" +#include "timer.h" + +#define EMULATOR_FLASH_FILE "emulator.img" + +#ifndef RANDOM_DEV_FILE +#define RANDOM_DEV_FILE "/dev/urandom" +#endif + +uint8_t *emulator_flash_base = NULL; + +uint32_t __stack_chk_guard; + +static int random_fd = -1; + +static void setup_urandom(void); +static void setup_flash(void); + +void setup(void) { + setup_urandom(); + setup_flash(); +} + +void __attribute__((noreturn)) shutdown(void) { + for (;;) pause(); +} + +void emulatorRandom(void *buffer, size_t size) { + ssize_t n, len = 0; + do { + n = read(random_fd, (char *)buffer + len, size - len); + if (n < 0) { + perror("Failed to read " RANDOM_DEV_FILE); + exit(1); + } + len += n; + } while (len != (ssize_t)size); +} + +static void setup_urandom(void) { + random_fd = open(RANDOM_DEV_FILE, O_RDONLY); + if (random_fd < 0) { + perror("Failed to open " RANDOM_DEV_FILE); + exit(1); + } +} + +static void setup_flash(void) { + int fd = open(EMULATOR_FLASH_FILE, O_RDWR | O_SYNC | O_CREAT, 0644); + if (fd < 0) { + perror("Failed to open flash emulation file"); + exit(1); + } + + off_t length = lseek(fd, 0, SEEK_END); + if (length < 0) { + perror("Failed to read length of flash emulation file"); + exit(1); + } + + emulator_flash_base = + mmap(NULL, FLASH_TOTAL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (emulator_flash_base == MAP_FAILED) { + perror("Failed to map flash emulation file"); + exit(1); + } + + if (length < FLASH_TOTAL_SIZE) { + if (ftruncate(fd, FLASH_TOTAL_SIZE) != 0) { + perror("Failed to initialize flash emulation file"); + exit(1); + } + + /* Initialize the flash */ + flash_erase_all_sectors(FLASH_CR_PROGRAM_X32); + } +} diff --git a/legacy/emulator/strl.c b/legacy/emulator/strl.c new file mode 100644 index 0000000000..da48ac86f5 --- /dev/null +++ b/legacy/emulator/strl.c @@ -0,0 +1,44 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "strl.h" +#include "util.h" + +#include + +#if (!defined __APPLE__) && (!defined HAVE_STRLCPY) +size_t strlcpy(char *dst, const char *src, size_t size) { + size_t ret = strlen(src); + + if (size) { + size_t len = MIN(ret, size - 1); + memcpy(dst, src, len); + dst[len] = '\0'; + } + + return ret; +} + +size_t strlcat(char *dst, const char *src, size_t size) { + size_t n = strnlen(dst, size); + + return n + strlcpy(&dst[n], src, size - n); +} + +#endif diff --git a/legacy/emulator/strl.h b/legacy/emulator/strl.h new file mode 100644 index 0000000000..c43fd4c72e --- /dev/null +++ b/legacy/emulator/strl.h @@ -0,0 +1,30 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __STRL_H__ +#define __STRL_H__ + +#include + +#if (!defined __APPLE__) && (!defined HAVE_STRLCPY) +size_t strlcpy(char *dst, const char *src, size_t size); +size_t strlcat(char *dst, const char *src, size_t size); +#endif + +#endif diff --git a/legacy/emulator/timer.c b/legacy/emulator/timer.c new file mode 100644 index 0000000000..e994d9613d --- /dev/null +++ b/legacy/emulator/timer.c @@ -0,0 +1,32 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "timer.h" + +void timer_init(void) {} + +uint32_t timer_ms(void) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + + uint32_t msec = t.tv_sec * 1000 + (t.tv_nsec / 1000000); + return msec; +} diff --git a/legacy/emulator/udp.c b/legacy/emulator/udp.c new file mode 100644 index 0000000000..4a80059963 --- /dev/null +++ b/legacy/emulator/udp.c @@ -0,0 +1,127 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#define TREZOR_UDP_PORT 21324 + +struct usb_socket { + int fd; + struct sockaddr_in from; + socklen_t fromlen; +}; + +static struct usb_socket usb_main; +static struct usb_socket usb_debug; + +static int socket_setup(int port) { + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + perror("Failed to create socket"); + exit(1); + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + perror("Failed to bind socket"); + exit(1); + } + + return fd; +} + +static size_t socket_write(struct usb_socket *sock, const void *buffer, + size_t size) { + if (sock->fromlen > 0) { + ssize_t n = sendto(sock->fd, buffer, size, MSG_DONTWAIT, + (const struct sockaddr *)&sock->from, sock->fromlen); + if (n < 0 || ((size_t)n) != size) { + perror("Failed to write socket"); + return 0; + } + } + + return size; +} + +static size_t socket_read(struct usb_socket *sock, void *buffer, size_t size) { + sock->fromlen = sizeof(sock->from); + ssize_t n = recvfrom(sock->fd, buffer, size, MSG_DONTWAIT, + (struct sockaddr *)&sock->from, &sock->fromlen); + + if (n < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + perror("Failed to read socket"); + } + return 0; + } + + static const char msg_ping[] = {'P', 'I', 'N', 'G', 'P', 'I', 'N', 'G'}; + static const char msg_pong[] = {'P', 'O', 'N', 'G', 'P', 'O', 'N', 'G'}; + + if (n == sizeof(msg_ping) && + memcmp(buffer, msg_ping, sizeof(msg_ping)) == 0) { + socket_write(sock, msg_pong, sizeof(msg_pong)); + return 0; + } + + return n; +} + +void emulatorSocketInit(void) { + usb_main.fd = socket_setup(TREZOR_UDP_PORT); + usb_main.fromlen = 0; + usb_debug.fd = socket_setup(TREZOR_UDP_PORT + 1); + usb_debug.fromlen = 0; +} + +size_t emulatorSocketRead(int *iface, void *buffer, size_t size) { + size_t n = socket_read(&usb_main, buffer, size); + if (n > 0) { + *iface = 0; + return n; + } + + n = socket_read(&usb_debug, buffer, size); + if (n > 0) { + *iface = 1; + return n; + } + + return 0; +} + +size_t emulatorSocketWrite(int iface, const void *buffer, size_t size) { + if (iface == 0) { + return socket_write(&usb_main, buffer, size); + } + if (iface == 1) { + return socket_write(&usb_debug, buffer, size); + } + return 0; +} diff --git a/legacy/firmware/.gitignore b/legacy/firmware/.gitignore new file mode 100644 index 0000000000..da8ec7c158 --- /dev/null +++ b/legacy/firmware/.gitignore @@ -0,0 +1,6 @@ +coin_info.[ch] +nem_mosaics.[ch] +ethereum_networks.h +ethereum_tokens.[ch] + +bl_data.h diff --git a/legacy/firmware/ChangeLog b/legacy/firmware/ChangeLog new file mode 100644 index 0000000000..be72340c89 --- /dev/null +++ b/legacy/firmware/ChangeLog @@ -0,0 +1,178 @@ +Version 1.8.0 +* Stable release, optional update +* Security improvements +* Upgraded to new storage format +* Stellar and NEM fixes +* New coins: ATS, KMD, XPM, XSN, ZCL +* New ETH tokens + +Version 1.7.3 +* Stable release, optional update +* Fix USB issue on some Windows 10 installations + +Version 1.7.2 +* Stable release, optional update +* Add support for OMNI layer: OMNI/MAID/USDT +* U2F fixes +* Don't ask for PIN if it has been just set + +Version 1.7.1 +* Stable release, optional update +* Add support for Lisk +* Add support for Zcash Sapling hardfork +* Implement seedless setup + +Version 1.7.0 +* Stable release, optional update +* Switch from HID to WebUSB +* Add support for Stellar +* Included bootloader 1.6.0 + +Version 1.6.3 +* Stable release, required update +* Implement RSKIP-60 Ethereum checksum encoding +* Add support for new Ethereum networks (ESN, AKA, ETHO, MUSI, PIRL, ATH, GO) +* Add support for new 80 Ethereum tokens +* Improve MPU configuration +* Included bootloader 1.5.1 + +Version 1.6.2 +* Stable release, optional update +* Add possibility to set custom auto-lock delay +* Bitcoin Cash cashaddr support +* Zcash Overwinter hardfork support +* Support for new coins: + - Decred, Bitcoin Private, Fujicoin, Groestlcoin, Vertcoin, Viacoin, Zcoin +* Support for new Ethereum networks: + - EOS Classic, Ethereum Social, Ellaism, Callisto, EtherGem, Wanchain +* Support for 500+ new Ethereum tokens +* Included bootloader 1.5.0 + +Version 1.6.1 +* Stable release, required update +* Use fixed-width font for addresses +* Lots of under-the-hood improvements +* Fixed issue with write-protection settings +* Included bootloader 1.4.0 + +Version 1.6.0 +* Stable release, optional update +* Native SegWit (Bech32) address support +* Show recognized BIP44/BIP49 paths in GetAddress dialog +* NEM support +* Expanse and UBIQ chains support +* Bitcoin Gold, DigiByte, Monacoin support +* Ed25519 collective signatures (CoSi) support + +Version 1.5.2 +* Stable release, required update +* Clean memory on start +* Fix storage import from older versions + +Version 1.5.1 +* Stable release, optional update +* Wipe storage after 16 wrong PIN attempts +* Enable Segwit for Bitcoin +* Bcash aka Bitcoin Cash support +* Message signing/verification for Ethereum and Segwit +* Make address dialog nicer (switch text/QR via button) +* Use checksum for Ethereum addresses +* Add more ERC-20 tokens, handle unrecognized ERC-20 tokens +* Allow "dry run" recovery procedure +* Allow separated backup procedure + +Version 1.5.0 +* Stable release, optional update +* Enable Segwit for Testnet and Litecoin +* Enable ERC-20 tokens for Ethereum chains + +Version 1.4.2 +* Stable release, optional update +* New Matrix-based recovery method +* Minor Ethereum fixes (including EIP-155 replay protection) +* Minor USB, U2F and GPG fixes + +Version 1.4.1 +* Stable release, optional update +* Support for Zcash JoinSplit transactions +* Enable device lock after 10 minutes of inactivity +* Enable device lock by pressing left button for 2 seconds +* Confirm dialog for U2F counter change + +Version 1.4.0 +* Stable release, optional update +* U2F support +* Ethereum support +* GPG decryption support +* Zcash support + +Version 1.3.6 +* Stable release, optional update +* Enable advanced transactions such as ones with REPLACE-BY-FEE and CHECKLOCKTIMEVERIFY +* Fix message signing for altcoins +* Message verification now shows address +* Enable GPG signing support +* Enable Ed25519 curve (for SSH and GPG) +* Use separate deterministic hierarchy for NIST256P1 and Ed25519 curves +* Users using SSH already need to regenerate their keys using the new firmware!!! + +Version 1.3.5 +* Stable release, optional update +* Double size font for recovery words during the device setup +* Optimizations for simultaneous access when more applications try communicate with the device + +Version 1.3.4 +* Stable release, optional update +* Screensaver active on ClearSession message +* Support for NIST P-256 curve +* Updated SignIdentity to v2 format +* Show seconds counter during PIN lockdown +* Updated maxfee per kb for coins + +Version 1.3.3 +* Stable release, mandatory update +* Ask for PIN on GetAddress and GetPublicKey +* Signing speed improved + +Version 1.3.2 +* Stable release, optional update +* Fix check during transaction streaming +* Login feature via SignIdentity message +* GetAddress for multisig shows M of N description +* PIN checking in constant time + +Version 1.3.1 +* Stable release, optional update +* Optimized signing speed +* Enabled OP_RETURN +* Added option to change home screen +* Moved fee calculation before any signing +* Made PIN delay increase immune against hardware hacking + +Version 1.3.0 +* Stable release, optional update +* Added multisig support +* Added visual validation of receiving address +* Added ECIES encryption capabilities + +Version 1.2.1 +* Stable release, mandatory update +* Added stack overflow protection +* Added compatibility with TREZOR Bridge + +Version 1.2.0 +* Stable release, optional update +* Fix false positives for fee warning +* Better UI for signing/verifying messages +* Smaller firmware size + +Version 1.1.0 +* Stable release, optional update +* Minor UI fixes +* Better handling of unexpected messages +* Added AES support + +Version 1.0.0 +* Stable release, mandatory update +* Added support for streaming of transactions into the device +* Removed all current limits on size of signed transaction diff --git a/legacy/firmware/Makefile b/legacy/firmware/Makefile new file mode 100644 index 0000000000..a21f0cd26b --- /dev/null +++ b/legacy/firmware/Makefile @@ -0,0 +1,139 @@ +APPVER = 1.8.0 + +NAME = trezor + +ifeq ($(EMULATOR),1) +OBJS += udp.o +else +OBJS += usb.o +OBJS += bl_check.o +OBJS += otp.o +OBJS += header.o +endif + +OBJS += u2f.o +OBJS += messages.o +OBJS += config.o +OBJS += trezor.o +OBJS += pinmatrix.o +OBJS += fsm.o +OBJS += coins.o +OBJS += coin_info.o +OBJS += transaction.o +OBJS += protect.o +OBJS += layout2.o +OBJS += recovery.o +OBJS += reset.o +OBJS += signing.o +OBJS += crypto.o +OBJS += ethereum.o +OBJS += ethereum_tokens.o +OBJS += nem2.o +OBJS += nem_mosaics.o +OBJS += stellar.o +OBJS += lisk.o + +OBJS += debug.o + +OBJS += ../vendor/trezor-crypto/address.o +OBJS += ../vendor/trezor-crypto/bignum.o +OBJS += ../vendor/trezor-crypto/ecdsa.o +OBJS += ../vendor/trezor-crypto/curves.o +OBJS += ../vendor/trezor-crypto/secp256k1.o +OBJS += ../vendor/trezor-crypto/nist256p1.o +OBJS += ../vendor/trezor-crypto/rand.o +OBJS += ../vendor/trezor-crypto/memzero.o + +OBJS += ../vendor/trezor-crypto/ed25519-donna/curve25519-donna-32bit.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/curve25519-donna-helpers.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/ed25519-donna-basepoint-table.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/ed25519-donna-32bit-tables.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/ed25519-donna-impl-base.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/ed25519.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/curve25519-donna-scalarmult-base.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/ed25519-sha3.o +OBJS += ../vendor/trezor-crypto/ed25519-donna/ed25519-keccak.o + +OBJS += ../vendor/trezor-crypto/hmac.o +OBJS += ../vendor/trezor-crypto/bip32.o +OBJS += ../vendor/trezor-crypto/bip39.o +OBJS += ../vendor/trezor-crypto/pbkdf2.o +OBJS += ../vendor/trezor-crypto/base32.o +OBJS += ../vendor/trezor-crypto/base58.o +OBJS += ../vendor/trezor-crypto/segwit_addr.o +OBJS += ../vendor/trezor-crypto/cash_addr.o + +OBJS += ../vendor/trezor-crypto/ripemd160.o +OBJS += ../vendor/trezor-crypto/sha2.o +OBJS += ../vendor/trezor-crypto/sha3.o +OBJS += ../vendor/trezor-crypto/blake256.o +OBJS += ../vendor/trezor-crypto/blake2b.o +OBJS += ../vendor/trezor-crypto/groestl.o +OBJS += ../vendor/trezor-crypto/hasher.o + +OBJS += ../vendor/trezor-crypto/aes/aescrypt.o +OBJS += ../vendor/trezor-crypto/aes/aeskey.o +OBJS += ../vendor/trezor-crypto/aes/aestab.o +OBJS += ../vendor/trezor-crypto/aes/aes_modes.o + +OBJS += ../vendor/trezor-crypto/chacha20poly1305/chacha20poly1305.o +OBJS += ../vendor/trezor-crypto/chacha20poly1305/chacha_merged.o +OBJS += ../vendor/trezor-crypto/chacha20poly1305/poly1305-donna.o +OBJS += ../vendor/trezor-crypto/chacha20poly1305/rfc7539.o + +OBJS += ../vendor/trezor-crypto/nem.o + +OBJS += ../vendor/QR-Code-generator/c/qrcodegen.o + +OBJS += ../vendor/trezor-storage/storage.o +OBJS += ../vendor/trezor-storage/norcow.o + +OBJS += ../vendor/nanopb/pb_common.o +OBJS += ../vendor/nanopb/pb_decode.o +OBJS += ../vendor/nanopb/pb_encode.o + +OBJS += protob/messages.pb.o +OBJS += protob/messages-bitcoin.pb.o +OBJS += protob/messages-common.pb.o +OBJS += protob/messages-crypto.pb.o +OBJS += protob/messages-debug.pb.o +OBJS += protob/messages-ethereum.pb.o +OBJS += protob/messages-management.pb.o +OBJS += protob/messages-nem.pb.o +OBJS += protob/messages-stellar.pb.o +OBJS += protob/messages-lisk.pb.o + +OPTFLAGS ?= -Os + +../vendor/trezor-crypto/bip32.o: OPTFLAGS = -O3 +../vendor/trezor-crypto/bip39.o: OPTFLAGS = -O3 +../vendor/trezor-crypto/ecdsa.o: OPTFLAGS = -O3 +../vendor/trezor-crypto/sha2.o: OPTFLAGS = -O3 +../vendor/trezor-crypto/secp256k1.o: OPTFLAGS = -O3 + +include ../Makefile.include + +DEBUG_LINK ?= 0 +DEBUG_LOG ?= 0 + +CFLAGS += -Wno-sequence-point +CFLAGS += -I../vendor/nanopb -Iprotob -DPB_FIELD_16BIT=1 +CFLAGS += -DDEBUG_LINK=$(DEBUG_LINK) +CFLAGS += -DDEBUG_LOG=$(DEBUG_LOG) +CFLAGS += -DSCM_REVISION='"$(shell git rev-parse HEAD | sed 's:\(..\):\\x\1:g')"' +CFLAGS += -DUSE_ETHEREUM=1 +CFLAGS += -DUSE_NEM=1 +CFLAGS += -DUSE_MONERO=0 + +%:: %.mako defs + @printf " MAKO $@\n" + $(Q)$(PYTHON) ../vendor/trezor-common/tools/cointool.py render $@.mako + +bl_data.h: bl_data.py ../bootloader/bootloader.bin + @printf " PYTHON bl_data.py\n" + $(Q)$(PYTHON) bl_data.py + +clean:: + rm -f bl_data.h + find -maxdepth 1 -name "*.mako" | sed 's/.mako$$//' | xargs rm -f diff --git a/legacy/firmware/bl_check.c b/legacy/firmware/bl_check.c new file mode 100644 index 0000000000..68728902f9 --- /dev/null +++ b/legacy/firmware/bl_check.c @@ -0,0 +1,192 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include "bl_data.h" +#include "gettext.h" +#include "layout.h" +#include "memory.h" +#include "util.h" + +static int known_bootloader(int r, const uint8_t *hash) { + if (r != 32) return 0; + if (0 == + memcmp(hash, + "\xbf\x72\xe2\x5e\x2c\x2f\xc1\xba\x57\x04\x50\xfa\xdf\xb6\x6f\xaa" + "\x5a\x71\x6d\xcd\xc0\x33\x35\x88\x55\x7b\x77\x54\x0a\xb8\x7e\x98", + 32)) + return 1; // 1.2.0a + if (0 == + memcmp(hash, + "\x77\xb8\xe2\xf2\x5f\xaa\x8e\x8c\x7d\x9f\x5b\x32\x3b\x27\xce\x05" + "\x6c\xa3\xdb\xc2\x3f\x56\xc3\x7e\xe3\x3f\x97\x7c\xa6\xeb\x4d\x3e", + 32)) + return 1; // 1.2.0b + if (0 == + memcmp(hash, + "\xc4\xc3\x25\x39\xb4\xa0\x25\xa8\xe7\x53\xa4\xc4\x62\x64\x28\x59" + "\x11\xa4\x5f\xcb\x14\xf4\x71\x81\x79\xe7\x11\xb1\xce\x99\x05\x24", + 32)) + return 1; // 1.2.5 + if (0 == + memcmp(hash, + "\x42\x59\x66\x94\xa0\xf2\x9d\x1e\xc2\x35\x71\x29\x2d\x54\x39\xd8" + "\x2f\xa1\x8c\x07\x37\xcb\x10\x7e\x98\xf6\x1e\xf5\x93\x4d\xe7\x16", + 32)) + return 1; // 1.3.0a + if (0 == + memcmp(hash, + "\x3a\xcf\x2e\x51\x0b\x0f\xe1\x56\xb5\x58\xbb\xf7\x9c\x7e\x48\x5e" + "\xb0\x26\xe5\xe0\x8c\xb4\x4d\x15\x2d\x44\xd6\x4e\x0c\x6a\x41\x37", + 32)) + return 1; // 1.3.0b + if (0 == + memcmp(hash, + "\x15\x85\x21\x5b\xc6\xe5\x5a\x34\x07\xa8\xb3\xee\xe2\x79\x03\x4e" + "\x95\xb9\xc4\x34\x00\x33\xe1\xb6\xae\x16\x0c\xe6\x61\x19\x67\x15", + 32)) + return 1; // 1.3.1 + if (0 == + memcmp(hash, + "\x76\x51\xb7\xca\xba\x5a\xae\x0c\xc1\xc6\x5c\x83\x04\xf7\x60\x39" + "\x6f\x77\x60\x6c\xd3\x99\x0c\x99\x15\x98\xf0\xe2\x2a\x81\xe0\x07", + 32)) + return 1; // 1.3.2 + // note to those verifying these values: bootloader versions above this + // comment are aligned/padded to 32KiB with trailing 0xFF bytes and versions + // below are padded with 0x00. + // for more info, refer to "make -C + // bootloader align" and + // "firmware/bl_data.py". + if (0 == + memcmp(hash, + "\x8c\xe8\xd7\x9e\xdf\x43\x0c\x03\x42\x64\x68\x6c\xa9\xb1\xd7\x8d" + "\x26\xed\xb2\xac\xab\x71\x39\xbe\x8f\x98\x5c\x2a\x3c\x6c\xae\x11", + 32)) + return 1; // 1.3.3 + if (0 == + memcmp(hash, + "\x63\x30\xfc\xec\x16\x72\xfa\xd3\x0b\x42\x1b\x60\xf7\x4f\x83\x9a" + "\x39\x39\x33\x45\x65\xcb\x70\x3b\x2b\xd7\x18\x2e\xa2\xdd\xa0\x19", + 32)) + return 1; // 1.4.0 shipped with fw 1.6.1 + if (0 == + memcmp(hash, + "\xaf\xb4\xcf\x7a\x4a\x57\x96\x10\x0e\xd5\x41\x6b\x75\x12\x1b\xc7" + "\x10\x08\xc2\xa2\xfd\x54\x49\xbd\x8f\x63\xcc\x22\xa6\xa7\xd6\x80", + 32)) + return 1; // 1.5.0 shipped with fw 1.6.2 + if (0 == + memcmp(hash, + "\x51\x12\x90\xa8\x72\x3f\xaf\xe7\x34\x15\x25\x9d\x25\x96\x76\x54" + "\x06\x32\x5c\xe2\x4b\x4b\x80\x03\x2c\x0b\x70\xb0\x5d\x98\x46\xe9", + 32)) + return 1; // 1.5.1 shipped with fw 1.6.3 + if (0 == + memcmp(hash, + "\x3e\xc4\xbd\xd5\x77\xea\x0c\x36\xc7\xba\xb7\xb9\xa3\x5b\x87\x17" + "\xb3\xf1\xfc\x2f\x80\x9e\x69\x0c\x8a\xbe\x5b\x05\xfb\xc2\x43\xc6", + 32)) + return 1; // 1.6.0 shipped with fw 1.7.0 + if (0 == + memcmp(hash, + "\x8e\x83\x02\x3f\x0d\x4f\x82\x4f\x64\x71\x20\x75\x2b\x6c\x71\x6f" + "\x55\xd7\x95\x70\x66\x8f\xd4\x90\x65\xd5\xb7\x97\x6e\x7a\x6e\x19", + 32)) + return 1; // 1.6.0 shipped with fw 1.7.1 and 1.7.2 + if (0 == + memcmp(hash, + "\xa2\x36\x6e\x77\xde\x8e\xfd\xfd\xc9\x99\xf4\x72\x20\xc0\x16\xe3" + "\x3f\x6d\x24\x24\xe2\x45\x90\x79\x11\x7a\x90\xb3\xa8\x88\xba\xdd", + 32)) + return 1; // 1.6.1 shipped with fw 1.7.3 + if (0 == + memcmp(hash, + "\xf7\xfa\x16\x5b\xe6\xd7\x80\xf3\xe1\xaf\x00\xab\xc0\x7d\xf8\xb3" + "\x07\x6b\xcd\xad\x72\xd7\x0d\xa2\x2a\x63\xd8\x89\x6b\x63\x91\xd8", + 32)) + return 1; // 1.8.0 shipped with fw 1.8.0 + return 0; +} + +void check_bootloader(void) { +#if MEMORY_PROTECT + uint8_t hash[32]; + int r = memory_bootloader_hash(hash); + + if (!known_bootloader(r, hash)) { + layoutDialog(&bmp_icon_error, NULL, NULL, NULL, _("Unknown bootloader"), + _("detected."), NULL, _("Unplug your TREZOR"), + _("contact our support."), NULL); + shutdown(); + } + + if (is_mode_unprivileged()) { + return; + } + + if (r == 32 && 0 == memcmp(hash, bl_hash, 32)) { + // all OK -> done + return; + } + + // ENABLE THIS AT YOUR OWN RISK + // ATTEMPTING TO OVERWRITE BOOTLOADER WITH UNSIGNED FIRMWARE MAY BRICK + // YOUR DEVICE. + + layoutDialog(&bmp_icon_warning, NULL, NULL, NULL, _("Updating bootloader"), + NULL, NULL, _("DO NOT UNPLUG"), _("YOUR TREZOR!"), NULL); + + // unlock sectors + memory_write_unlock(); + + for (int tries = 0; tries < 10; tries++) { + // replace bootloader + flash_wait_for_last_operation(); + flash_clear_status_flags(); + flash_unlock(); + for (int i = FLASH_BOOT_SECTOR_FIRST; i <= FLASH_BOOT_SECTOR_LAST; i++) { + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + for (int i = 0; i < FLASH_BOOT_LEN / 4; i++) { + const uint32_t *w = (const uint32_t *)(bl_data + i * 4); + flash_program_word(FLASH_BOOT_START + i * 4, *w); + } + flash_wait_for_last_operation(); + flash_lock(); + // check whether the write was OK + r = memory_bootloader_hash(hash); + if (r == 32 && 0 == memcmp(hash, bl_hash, 32)) { + // OK -> show info and halt + layoutDialog(&bmp_icon_info, NULL, NULL, NULL, _("Update finished"), + _("successfully."), NULL, _("Please reconnect"), + _("the device."), NULL); + shutdown(); + return; + } + } + // show info and halt + layoutDialog(&bmp_icon_error, NULL, NULL, NULL, _("Bootloader update"), + _("broken."), NULL, _("Unplug your TREZOR"), + _("contact our support."), NULL); + shutdown(); +#endif +} diff --git a/legacy/firmware/bl_check.h b/legacy/firmware/bl_check.h new file mode 100644 index 0000000000..8ab070c52a --- /dev/null +++ b/legacy/firmware/bl_check.h @@ -0,0 +1,25 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __BL_CHECK_H__ +#define __BL_CHECK_H__ + +void check_bootloader(void); + +#endif diff --git a/legacy/firmware/bl_data.py b/legacy/firmware/bl_data.py new file mode 100755 index 0000000000..da25fa5a17 --- /dev/null +++ b/legacy/firmware/bl_data.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +from hashlib import sha256 + +fn = '../bootloader/bootloader.bin' + +data = open(fn, 'rb').read() +if len(data) > 32768: + raise Exception('bootloader has to be smaller than 32768 bytes') + +data += b'\x00' * (32768 - len(data)) + +h = sha256(sha256(data).digest()).digest() + +bl_hash = ', '.join('0x%02x' % x for x in bytearray(h)) +bl_data = ', '.join('0x%02x' % x for x in bytearray(data)) + +with open('bl_data.h', 'wt') as f: + f.write('static const uint8_t bl_hash[32] = {%s};\n' % bl_hash) + f.write('static const uint8_t bl_data[32768] = {%s};\n' % bl_data) diff --git a/legacy/firmware/coin_info.c.mako b/legacy/firmware/coin_info.c.mako new file mode 100644 index 0000000000..5b76886c33 --- /dev/null +++ b/legacy/firmware/coin_info.c.mako @@ -0,0 +1,51 @@ +<% +def signed_message_header(s): + return r'"\x{:02x}" {}'.format(len(s), c_str(s)) + +def c_bool(b): + return "true" if b else "false" + +def c_int(i): + return int(i or 0) + +def defined(s): + return c_bool(s is not None) + +def hex(x): + return "0x{:08x}".format(c_int(x)) +%>\ +// This file is automatically generated from coin_info.c.mako +// DO NOT EDIT + +#include "coins.h" + +#include "curves.h" +#include "secp256k1.h" + +const CoinInfo coins[COINS_COUNT] = { +% for c in supported_on("trezor1", bitcoin): +{ + .coin_name = ${c_str(c.coin_name)}, + .coin_shortcut = ${c_str(" " + c.coin_shortcut)}, + .maxfee_kb = ${c_int(c.maxfee_kb)}, + .signed_message_header = ${signed_message_header(c.signed_message_header)}, + .has_address_type = ${defined(c.address_type)}, + .has_address_type_p2sh = ${defined(c.address_type_p2sh)}, + .has_segwit = ${c_bool(c.segwit)}, + .has_fork_id = ${defined(c.fork_id)}, + .force_bip143 = ${c_bool(c.force_bip143)}, + .decred = ${c_bool(c.decred)}, + .address_type = ${c.address_type}, + .address_type_p2sh = ${c.address_type_p2sh}, + .xpub_magic = ${hex(c.xpub_magic)}, + .xpub_magic_segwit_p2sh = ${hex(c.xpub_magic_segwit_p2sh)}, + .xpub_magic_segwit_native = ${hex(c.xpub_magic_segwit_native)}, + .fork_id = ${c_int(c.fork_id)}, + .bech32_prefix = ${c_str(c.bech32_prefix)}, + .cashaddr_prefix = ${c_str(c.cashaddr_prefix)}, + .coin_type = (${c_int(c.slip44)} | 0x80000000), + .curve_name = ${c.curve_name.upper()}_NAME, + .curve = &${c.curve_name}_info, +}, +% endfor +}; diff --git a/legacy/firmware/coin_info.h.mako b/legacy/firmware/coin_info.h.mako new file mode 100644 index 0000000000..bcd16120bf --- /dev/null +++ b/legacy/firmware/coin_info.h.mako @@ -0,0 +1,14 @@ +// This file is automatically generated from coin_info.h.mako +// DO NOT EDIT + +#ifndef __COIN_INFO_H__ +#define __COIN_INFO_H__ + +#include "coins.h" + +<% coins_list = list(supported_on("trezor1", bitcoin)) %>\ +#define COINS_COUNT (${len(coins_list)}) + +extern const CoinInfo coins[COINS_COUNT]; + +#endif diff --git a/legacy/firmware/coins.c b/legacy/firmware/coins.c new file mode 100644 index 0000000000..55ae243884 --- /dev/null +++ b/legacy/firmware/coins.c @@ -0,0 +1,80 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "coins.h" +#include +#include "address.h" +#include "base58.h" +#include "ecdsa.h" + +const CoinInfo *coinByName(const char *name) { + if (!name) return 0; + for (int i = 0; i < COINS_COUNT; i++) { + if (strcmp(name, coins[i].coin_name) == 0) { + return &(coins[i]); + } + } + return 0; +} + +const CoinInfo *coinByAddressType(uint32_t address_type) { + for (int i = 0; i < COINS_COUNT; i++) { + if (address_type == coins[i].address_type) { + return &(coins[i]); + } + } + return 0; +} + +const CoinInfo *coinBySlip44(uint32_t coin_type) { + for (int i = 0; i < COINS_COUNT; i++) { + if (coin_type == coins[i].coin_type) { + return &(coins[i]); + } + } + return 0; +} + +bool coinExtractAddressType(const CoinInfo *coin, const char *addr, + uint32_t *address_type) { + if (!addr) return false; + uint8_t addr_raw[MAX_ADDR_RAW_SIZE]; + int len = base58_decode_check(addr, coin->curve->hasher_base58, addr_raw, + MAX_ADDR_RAW_SIZE); + if (len >= 21) { + return coinExtractAddressTypeRaw(coin, addr_raw, address_type); + } + return false; +} + +bool coinExtractAddressTypeRaw(const CoinInfo *coin, const uint8_t *addr_raw, + uint32_t *address_type) { + if (coin->has_address_type && + address_check_prefix(addr_raw, coin->address_type)) { + *address_type = coin->address_type; + return true; + } + if (coin->has_address_type_p2sh && + address_check_prefix(addr_raw, coin->address_type_p2sh)) { + *address_type = coin->address_type_p2sh; + return true; + } + *address_type = 0; + return false; +} diff --git a/legacy/firmware/coins.h b/legacy/firmware/coins.h new file mode 100644 index 0000000000..166cc47fc7 --- /dev/null +++ b/legacy/firmware/coins.h @@ -0,0 +1,64 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __COINS_H__ +#define __COINS_H__ + +#include +#include + +#include "bip32.h" +#include "hasher.h" + +typedef struct _CoinInfo { + const char *coin_name; + const char *coin_shortcut; + uint64_t maxfee_kb; + const char *signed_message_header; + bool has_address_type; + bool has_address_type_p2sh; + bool has_segwit; + bool has_fork_id; + bool force_bip143; + bool decred; + // address types > 0xFF represent a two-byte prefix in big-endian order + uint32_t address_type; + uint32_t address_type_p2sh; + uint32_t xpub_magic; + uint32_t xpub_magic_segwit_p2sh; + uint32_t xpub_magic_segwit_native; + uint32_t fork_id; + const char *bech32_prefix; + const char *cashaddr_prefix; + uint32_t coin_type; + const char *curve_name; + const curve_info *curve; +} CoinInfo; + +#include "coin_info.h" + +const CoinInfo *coinByName(const char *name); +const CoinInfo *coinByAddressType(uint32_t address_type); +const CoinInfo *coinBySlip44(uint32_t coin_type); +bool coinExtractAddressType(const CoinInfo *coin, const char *addr, + uint32_t *address_type); +bool coinExtractAddressTypeRaw(const CoinInfo *coin, const uint8_t *addr_raw, + uint32_t *address_type); + +#endif diff --git a/legacy/firmware/config.c b/legacy/firmware/config.c new file mode 100644 index 0000000000..16cac6e3c0 --- /dev/null +++ b/legacy/firmware/config.c @@ -0,0 +1,937 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include + +#include "messages.pb.h" + +#include "aes/aes.h" +#include "bip32.h" +#include "bip39.h" +#include "common.h" +#include "config.h" +#include "curves.h" +#include "debug.h" +#include "gettext.h" +#include "hmac.h" +#include "layout2.h" +#include "memory.h" +#include "memzero.h" +#include "pbkdf2.h" +#include "protect.h" +#include "rng.h" +#include "sha2.h" +#include "storage.h" +#include "supervise.h" +#include "trezor.h" +#include "u2f.h" +#include "usb.h" +#include "util.h" + +/* Magic constants to check validity of storage block for storage versions 1 + * to 10. */ +static const uint32_t CONFIG_MAGIC_V10 = 0x726f7473; // 'stor' as uint32_t + +#if !EMULATOR +static const uint32_t META_MAGIC_V10 = 0x525a5254; // 'TRZR' as uint32_t +#else +static const uint32_t META_MAGIC_V10 = 0xFFFFFFFF; +#endif + +#define APP 0x0100 +#define FLAG_PUBLIC 0x8000 +#define FLAGS_WRITE 0xC000 + +#define KEY_UUID (0 | APP | FLAG_PUBLIC) // bytes(12) +#define KEY_VERSION (1 | APP) // uint32 +#define KEY_MNEMONIC (2 | APP) // string(241) +#define KEY_LANGUAGE (3 | APP | FLAG_PUBLIC) // string(17) +#define KEY_LABEL (4 | APP | FLAG_PUBLIC) // string(33) +#define KEY_PASSPHRASE_PROTECTION (5 | APP | FLAG_PUBLIC) // bool +#define KEY_HOMESCREEN (6 | APP | FLAG_PUBLIC) // bytes(1024) +#define KEY_NEEDS_BACKUP (7 | APP) // bool +#define KEY_FLAGS (8 | APP) // uint32 +#define KEY_U2F_COUNTER (9 | APP | FLAGS_WRITE) // uint32 +#define KEY_UNFINISHED_BACKUP (11 | APP) // bool +#define KEY_AUTO_LOCK_DELAY_MS (12 | APP) // uint32 +#define KEY_NO_BACKUP (13 | APP) // bool +#define KEY_INITIALIZED (14 | APP | FLAG_PUBLIC) // uint32 +#define KEY_NODE (15 | APP) // node +#define KEY_IMPORTED (16 | APP) // bool +#define KEY_U2F_ROOT (17 | APP | FLAG_PUBLIC) // node +#define KEY_DEBUG_LINK_PIN (255 | APP | FLAG_PUBLIC) // string(10) + +// The PIN value corresponding to an empty PIN. +static const uint32_t PIN_EMPTY = 1; + +static uint32_t config_uuid[UUID_SIZE / sizeof(uint32_t)]; +_Static_assert(sizeof(config_uuid) == UUID_SIZE, "config_uuid has wrong size"); + +char config_uuid_str[2 * UUID_SIZE + 1]; + +/* + Old storage layout: + + offset | type/length | description +--------+--------------+------------------------------- + 0x0000 | 4 bytes | magic = 'stor' + 0x0004 | 12 bytes | uuid + 0x0010 | ? bytes | Storage structure +--------+--------------+------------------------------- + 0x4000 | 4 kbytes | area for pin failures + 0x5000 | 256 bytes | area for u2f counter updates + 0x5100 | 11.75 kbytes | reserved + +The area for pin failures looks like this: +0 ... 0 pinfail 0xffffffff .. 0xffffffff +The pinfail is a binary number of the form 1...10...0, +the number of zeros is the number of pin failures. +This layout is used because we can only clear bits without +erasing the flash. + +The area for u2f counter updates is just a sequence of zero-bits +followed by a sequence of one-bits. The bits in a byte are numbered +from LSB to MSB. The number of zero bits is the offset that should +be added to the storage u2f_counter to get the real counter value. + + */ + +/* Current u2f offset, i.e. u2f counter is + * storage.u2f_counter + config_u2f_offset. + * This corresponds to the number of cleared bits in the U2FAREA. + */ +static secbool sessionSeedCached, sessionSeedUsesPassphrase; +static uint8_t CONFIDENTIAL sessionSeed[64]; + +static secbool sessionPassphraseCached = secfalse; +static char CONFIDENTIAL sessionPassphrase[51]; + +#define autoLockDelayMsDefault (10 * 60 * 1000U) // 10 minutes +static secbool autoLockDelayMsCached = secfalse; +static uint32_t autoLockDelayMs = autoLockDelayMsDefault; + +static const uint32_t CONFIG_VERSION = 11; + +static const uint8_t FALSE_BYTE = '\x00'; +static const uint8_t TRUE_BYTE = '\x01'; + +static uint32_t pin_to_int(const char *pin) { + uint32_t val = 1; + size_t i = 0; + for (i = 0; i < MAX_PIN_LEN && pin[i] != '\0'; ++i) { + if (pin[i] < '0' || pin[i] > '9') { + return 0; + } + val = 10 * val + pin[i] - '0'; + } + + if (pin[i] != '\0') { + return 0; + } + + return val; +} + +static secbool config_set_bool(uint16_t key, bool value) { + if (value) { + return storage_set(key, &TRUE_BYTE, sizeof(TRUE_BYTE)); + } else { + return storage_set(key, &FALSE_BYTE, sizeof(FALSE_BYTE)); + } +} + +static secbool config_get_bool(uint16_t key, bool *value) { + uint8_t val = 0; + uint16_t len = 0; + if (sectrue == storage_get(key, &val, sizeof(val), &len) && + len == sizeof(TRUE_BYTE)) { + *value = (val == TRUE_BYTE); + return sectrue; + } else { + *value = false; + return secfalse; + } +} + +static secbool config_get_bytes(uint16_t key, uint8_t *dest, uint16_t dest_size, + uint16_t *real_size) { + if (dest_size == 0) { + return secfalse; + } + + if (sectrue != storage_get(key, dest, dest_size, real_size)) { + return secfalse; + } + return sectrue; +} + +static secbool config_get_string(uint16_t key, char *dest, uint16_t dest_size) { + if (dest_size == 0) { + return secfalse; + } + + uint16_t len = 0; + if (sectrue != storage_get(key, dest, dest_size - 1, &len)) { + dest[0] = '\0'; + return secfalse; + } + dest[len] = '\0'; + return sectrue; +} + +static secbool config_get_uint32(uint16_t key, uint32_t *value) { + uint16_t len = 0; + if (sectrue != storage_get(key, value, sizeof(uint32_t), &len) || + len != sizeof(uint32_t)) { + *value = 0; + return secfalse; + } + return sectrue; +} + +#define FLASH_META_START 0x08008000 +#define FLASH_META_LEN 0x100 + +static secbool config_upgrade_v10(void) { +#define OLD_STORAGE_SIZE(last_member) \ + (((offsetof(Storage, last_member) + pb_membersize(Storage, last_member)) + \ + 3) & \ + ~3) + + if (memcmp(FLASH_PTR(FLASH_META_START), &META_MAGIC_V10, + sizeof(META_MAGIC_V10)) != 0 || + memcmp(FLASH_PTR(FLASH_META_START + FLASH_META_LEN), &CONFIG_MAGIC_V10, + sizeof(CONFIG_MAGIC_V10)) != 0) { + // wrong magic + return secfalse; + } + + Storage config __attribute__((aligned(4))); + _Static_assert((sizeof(config) & 3) == 0, "storage unaligned"); + + memcpy( + config_uuid, + FLASH_PTR(FLASH_META_START + FLASH_META_LEN + sizeof(CONFIG_MAGIC_V10)), + sizeof(config_uuid)); + memcpy(&config, + FLASH_PTR(FLASH_META_START + FLASH_META_LEN + + sizeof(CONFIG_MAGIC_V10) + sizeof(config_uuid)), + sizeof(config)); + + // version 1: since 1.0.0 + // version 2: since 1.2.1 + // version 3: since 1.3.1 + // version 4: since 1.3.2 + // version 5: since 1.3.3 + // version 6: since 1.3.6 + // version 7: since 1.5.1 + // version 8: since 1.5.2 + // version 9: since 1.6.1 + // version 10: since 1.7.2 + if (config.version > CONFIG_VERSION) { + // downgrade -> clear storage + config_wipe(); + return secfalse; + } + + size_t old_config_size = 0; + if (config.version == 0) { + } else if (config.version <= 2) { + old_config_size = OLD_STORAGE_SIZE(imported); + } else if (config.version <= 5) { + // added homescreen + old_config_size = OLD_STORAGE_SIZE(homescreen); + } else if (config.version <= 7) { + // added u2fcounter + old_config_size = OLD_STORAGE_SIZE(u2f_counter); + } else if (config.version <= 8) { + // added flags and needsBackup + old_config_size = OLD_STORAGE_SIZE(flags); + } else if (config.version <= 9) { + // added u2froot, unfinished_backup and auto_lock_delay_ms + old_config_size = OLD_STORAGE_SIZE(auto_lock_delay_ms); + } else if (config.version <= 10) { + // added no_backup + old_config_size = OLD_STORAGE_SIZE(no_backup); + } + + // Erase newly added fields. + if (old_config_size != sizeof(Storage)) { + memzero((char *)&config + old_config_size, + sizeof(Storage) - old_config_size); + } + + const uint32_t FLASH_STORAGE_PINAREA = FLASH_META_START + 0x4000; + uint32_t pin_wait = 0; + if (config.version <= 5) { + // Get PIN failure counter from version 5 format. + uint32_t pinctr = + config.has_pin_failed_attempts ? config.pin_failed_attempts : 0; + if (pinctr > 31) { + pinctr = 31; + } + + pin_wait = (1 << pinctr) - 1; + } else { + // Get PIN failure counter from version 10 format. + uint32_t flash_pinfails = FLASH_STORAGE_PINAREA; + while (*(const uint32_t *)FLASH_PTR(flash_pinfails) == 0) { + flash_pinfails += sizeof(uint32_t); + } + pin_wait = ~*(const uint32_t *)FLASH_PTR(flash_pinfails); + } + + uint32_t u2f_offset = 0; + if (config.has_u2f_counter) { + const uint32_t FLASH_STORAGE_U2FAREA = FLASH_STORAGE_PINAREA + 0x1000; + const uint32_t *u2fptr = (const uint32_t *)FLASH_PTR(FLASH_STORAGE_U2FAREA); + while (*u2fptr == 0) { + u2fptr++; + } + u2f_offset = + 32 * (u2fptr - (const uint32_t *)FLASH_PTR(FLASH_STORAGE_U2FAREA)); + uint32_t u2fword = *u2fptr; + while ((u2fword & 1) == 0) { + u2f_offset++; + u2fword >>= 1; + } + } + + storage_init(NULL, HW_ENTROPY_DATA, HW_ENTROPY_LEN); + storage_unlock(PIN_EMPTY); + if (config.has_pin) { + storage_change_pin(PIN_EMPTY, pin_to_int(config.pin)); + } + + while (pin_wait != 0) { + storage_pin_fails_increase(); + pin_wait >>= 1; + } + + storage_set(KEY_UUID, config_uuid, sizeof(config_uuid)); + storage_set(KEY_VERSION, &CONFIG_VERSION, sizeof(CONFIG_VERSION)); + if (config.has_node) { + if (sectrue == storage_set(KEY_NODE, &config.node, sizeof(config.node))) { + config_set_bool(KEY_INITIALIZED, true); + } + } + if (config.has_mnemonic) { + config_setMnemonic(config.mnemonic); + } + if (config.has_passphrase_protection) { + config_setPassphraseProtection(config.passphrase_protection); + } + if (config.has_language) { + config_setLanguage(config.language); + } + if (config.has_label) { + config_setLabel(config.label); + } + if (config.has_imported) { + config_setImported(config.imported); + } + if (config.has_homescreen) { + config_setHomescreen(config.homescreen.bytes, config.homescreen.size); + } + if (config.has_u2f_counter) { + config_setU2FCounter(config.u2f_counter + u2f_offset); + } + if (config.has_needs_backup) { + config_setNeedsBackup(config.needs_backup); + } + if (config.has_flags) { + config_applyFlags(config.flags); + } + if (config.has_unfinished_backup) { + config_setUnfinishedBackup(config.unfinished_backup); + } + if (config.has_auto_lock_delay_ms) { + config_setAutoLockDelayMs(config.auto_lock_delay_ms); + } + if (config.has_no_backup && config.no_backup) { + config_setNoBackup(); + } + memzero(&config, sizeof(config)); + + session_clear(true); + + return sectrue; +} + +void config_init(void) { + char oldTiny = usbTiny(1); + + config_upgrade_v10(); + + storage_init(&protectPinUiCallback, HW_ENTROPY_DATA, HW_ENTROPY_LEN); + memzero(HW_ENTROPY_DATA, sizeof(HW_ENTROPY_DATA)); + + // Auto-unlock storage if no PIN is set. + if (storage_is_unlocked() == secfalse && storage_has_pin() == secfalse) { + storage_unlock(PIN_EMPTY); + } + + uint16_t len = 0; + // If UUID is not set, then the config is uninitialized. + if (sectrue != + storage_get(KEY_UUID, config_uuid, sizeof(config_uuid), &len) || + len != sizeof(config_uuid)) { + random_buffer((uint8_t *)config_uuid, sizeof(config_uuid)); + storage_set(KEY_UUID, config_uuid, sizeof(config_uuid)); + storage_set(KEY_VERSION, &CONFIG_VERSION, sizeof(CONFIG_VERSION)); + } + data2hex(config_uuid, sizeof(config_uuid), config_uuid_str); + + usbTiny(oldTiny); +} + +void session_clear(bool lock) { + sessionSeedCached = secfalse; + memzero(&sessionSeed, sizeof(sessionSeed)); + sessionPassphraseCached = secfalse; + memzero(&sessionPassphrase, sizeof(sessionPassphrase)); + if (lock) { + storage_lock(); + } +} + +static void get_u2froot_callback(uint32_t iter, uint32_t total) { + layoutProgress(_("Updating"), 1000 * iter / total); +} + +static void config_compute_u2froot(const char *mnemonic, + StorageHDNode *u2froot) { + static CONFIDENTIAL HDNode node; + char oldTiny = usbTiny(1); + mnemonic_to_seed(mnemonic, "", sessionSeed, + get_u2froot_callback); // BIP-0039 + usbTiny(oldTiny); + hdnode_from_seed(sessionSeed, 64, NIST256P1_NAME, &node); + hdnode_private_ckd(&node, U2F_KEY_PATH); + u2froot->depth = node.depth; + u2froot->child_num = U2F_KEY_PATH; + u2froot->chain_code.size = sizeof(node.chain_code); + memcpy(u2froot->chain_code.bytes, node.chain_code, sizeof(node.chain_code)); + u2froot->has_private_key = true; + u2froot->private_key.size = sizeof(node.private_key); + memcpy(u2froot->private_key.bytes, node.private_key, + sizeof(node.private_key)); + memzero(&node, sizeof(node)); + session_clear(false); // invalidate seed cache +} + +static void config_setNode(const HDNodeType *node) { + StorageHDNode storageHDNode; + memzero(&storageHDNode, sizeof(storageHDNode)); + + storageHDNode.depth = node->depth; + storageHDNode.fingerprint = node->fingerprint; + storageHDNode.child_num = node->child_num; + storageHDNode.chain_code.size = 32; + memcpy(storageHDNode.chain_code.bytes, node->chain_code.bytes, 32); + + if (node->has_private_key) { + storageHDNode.has_private_key = true; + storageHDNode.private_key.size = 32; + memcpy(storageHDNode.private_key.bytes, node->private_key.bytes, 32); + } + if (sectrue == storage_set(KEY_NODE, &storageHDNode, sizeof(storageHDNode))) { + config_set_bool(KEY_INITIALIZED, true); + } + memzero(&storageHDNode, sizeof(storageHDNode)); +} + +#if DEBUG_LINK +bool config_dumpNode(HDNodeType *node) { + memzero(node, sizeof(HDNodeType)); + + StorageHDNode storageNode; + uint16_t len = 0; + if (sectrue != + storage_get(KEY_NODE, &storageNode, sizeof(storageNode), &len) || + len != sizeof(StorageHDNode)) { + memzero(&storageNode, sizeof(storageNode)); + return false; + } + + node->depth = storageNode.depth; + node->fingerprint = storageNode.fingerprint; + node->child_num = storageNode.child_num; + + node->chain_code.size = 32; + memcpy(node->chain_code.bytes, storageNode.chain_code.bytes, 32); + + if (storageNode.has_private_key) { + node->has_private_key = true; + node->private_key.size = 32; + memcpy(node->private_key.bytes, storageNode.private_key.bytes, 32); + } + + memzero(&storageNode, sizeof(storageNode)); + return true; +} +#endif + +void config_loadDevice(const LoadDevice *msg) { + session_clear(false); + config_set_bool(KEY_IMPORTED, true); + config_setPassphraseProtection(msg->has_passphrase_protection && + msg->passphrase_protection); + + if (msg->has_pin) { + config_changePin("", msg->pin); + } + + if (msg->has_node) { + storage_delete(KEY_MNEMONIC); + config_setNode(&(msg->node)); + } else if (msg->has_mnemonic) { + storage_delete(KEY_NODE); + config_setMnemonic(msg->mnemonic); + } + + if (msg->has_language) { + config_setLanguage(msg->language); + } + + config_setLabel(msg->has_label ? msg->label : ""); + + if (msg->has_u2f_counter) { + config_setU2FCounter(msg->u2f_counter); + } +} + +void config_setLabel(const char *label) { + if (label == NULL || label[0] == '\0') { + storage_delete(KEY_LABEL); + } else { + storage_set(KEY_LABEL, label, strnlen(label, MAX_LABEL_LEN)); + } +} + +void config_setLanguage(const char *lang) { + if (lang == NULL) { + return; + } + + // Sanity check. + if (strcmp(lang, "english") != 0) { + return; + } + storage_set(KEY_LANGUAGE, lang, strnlen(lang, MAX_LANGUAGE_LEN)); +} + +void config_setPassphraseProtection(bool passphrase_protection) { + sessionSeedCached = secfalse; + sessionPassphraseCached = secfalse; + config_set_bool(KEY_PASSPHRASE_PROTECTION, passphrase_protection); +} + +bool config_getPassphraseProtection(bool *passphrase_protection) { + return sectrue == + config_get_bool(KEY_PASSPHRASE_PROTECTION, passphrase_protection); +} + +void config_setHomescreen(const uint8_t *data, uint32_t size) { + if (data != NULL && size == HOMESCREEN_SIZE) { + storage_set(KEY_HOMESCREEN, data, size); + } else { + storage_delete(KEY_HOMESCREEN); + } +} + +static void get_root_node_callback(uint32_t iter, uint32_t total) { + usbSleep(1); + layoutProgress(_("Waking up"), 1000 * iter / total); +} + +const uint8_t *config_getSeed(bool usePassphrase) { + // root node is properly cached + if (usePassphrase == (sectrue == sessionSeedUsesPassphrase) && + sectrue == sessionSeedCached) { + return sessionSeed; + } + + // if storage has mnemonic, convert it to node and use it + char mnemonic[MAX_MNEMONIC_LEN + 1]; + if (config_getMnemonic(mnemonic, sizeof(mnemonic))) { + if (usePassphrase && !protectPassphrase()) { + memzero(mnemonic, sizeof(mnemonic)); + return NULL; + } + // if storage was not imported (i.e. it was properly generated or recovered) + bool imported = false; + config_get_bool(KEY_IMPORTED, &imported); + if (!imported) { + // test whether mnemonic is a valid BIP-0039 mnemonic + if (!mnemonic_check(mnemonic)) { + // and if not then halt the device + error_shutdown(_("Storage failure"), _("detected."), NULL, NULL); + } + } + char oldTiny = usbTiny(1); + mnemonic_to_seed(mnemonic, usePassphrase ? sessionPassphrase : "", + sessionSeed, get_root_node_callback); // BIP-0039 + memzero(mnemonic, sizeof(mnemonic)); + usbTiny(oldTiny); + sessionSeedCached = sectrue; + sessionSeedUsesPassphrase = usePassphrase ? sectrue : secfalse; + return sessionSeed; + } + + return NULL; +} + +static bool config_loadNode(const StorageHDNode *node, const char *curve, + HDNode *out) { + return hdnode_from_xprv(node->depth, node->child_num, node->chain_code.bytes, + node->private_key.bytes, curve, out); +} + +bool config_getU2FRoot(HDNode *node) { + StorageHDNode u2fNode; + uint16_t len = 0; + if (sectrue != storage_get(KEY_U2F_ROOT, &u2fNode, sizeof(u2fNode), &len) || + len != sizeof(StorageHDNode)) { + memzero(&u2fNode, sizeof(u2fNode)); + return false; + } + bool ret = config_loadNode(&u2fNode, NIST256P1_NAME, node); + memzero(&u2fNode, sizeof(u2fNode)); + return ret; +} + +bool config_getRootNode(HDNode *node, const char *curve, bool usePassphrase) { + // if storage has node, decrypt and use it + StorageHDNode storageHDNode; + uint16_t len = 0; + if (strcmp(curve, SECP256K1_NAME) == 0 && + sectrue == + storage_get(KEY_NODE, &storageHDNode, sizeof(storageHDNode), &len) && + len == sizeof(StorageHDNode)) { + if (!protectPassphrase()) { + memzero(&storageHDNode, sizeof(storageHDNode)); + return false; + } + if (!config_loadNode(&storageHDNode, curve, node)) { + memzero(&storageHDNode, sizeof(storageHDNode)); + return false; + } + bool passphrase_protection = false; + config_getPassphraseProtection(&passphrase_protection); + if (passphrase_protection && sectrue == sessionPassphraseCached && + sessionPassphrase[0] != '\0') { + // decrypt hd node + uint8_t secret[64]; + PBKDF2_HMAC_SHA512_CTX pctx; + char oldTiny = usbTiny(1); + pbkdf2_hmac_sha512_Init(&pctx, (const uint8_t *)sessionPassphrase, + strlen(sessionPassphrase), + (const uint8_t *)"TREZORHD", 8, 1); + get_root_node_callback(0, BIP39_PBKDF2_ROUNDS); + for (int i = 0; i < 8; i++) { + pbkdf2_hmac_sha512_Update(&pctx, BIP39_PBKDF2_ROUNDS / 8); + get_root_node_callback((i + 1) * BIP39_PBKDF2_ROUNDS / 8, + BIP39_PBKDF2_ROUNDS); + } + pbkdf2_hmac_sha512_Final(&pctx, secret); + usbTiny(oldTiny); + aes_decrypt_ctx ctx; + aes_decrypt_key256(secret, &ctx); + aes_cbc_decrypt(node->chain_code, node->chain_code, 32, secret + 32, + &ctx); + aes_cbc_decrypt(node->private_key, node->private_key, 32, secret + 32, + &ctx); + } + return true; + } + memzero(&storageHDNode, sizeof(storageHDNode)); + + const uint8_t *seed = config_getSeed(usePassphrase); + if (seed == NULL) { + return false; + } + + return hdnode_from_seed(seed, 64, curve, node); +} + +bool config_getLabel(char *dest, uint16_t dest_size) { + return sectrue == config_get_string(KEY_LABEL, dest, dest_size); +} + +bool config_getLanguage(char *dest, uint16_t dest_size) { + return sectrue == config_get_string(KEY_LANGUAGE, dest, dest_size); +} + +bool config_getHomescreen(uint8_t *dest, uint16_t dest_size) { + uint16_t len = 0; + secbool ret = storage_get(KEY_HOMESCREEN, dest, dest_size, &len); + if (sectrue != ret || len != HOMESCREEN_SIZE) { + return false; + } + return true; +} + +bool config_setMnemonic(const char *mnemonic) { + if (mnemonic == NULL) { + return false; + } + + if (sectrue != storage_set(KEY_MNEMONIC, mnemonic, + strnlen(mnemonic, MAX_MNEMONIC_LEN))) { + return false; + } + + StorageHDNode u2fNode; + memzero(&u2fNode, sizeof(u2fNode)); + config_compute_u2froot(mnemonic, &u2fNode); + secbool ret = storage_set(KEY_U2F_ROOT, &u2fNode, sizeof(u2fNode)); + memzero(&u2fNode, sizeof(u2fNode)); + + if (sectrue != ret) { + storage_delete(KEY_MNEMONIC); + return false; + } + + config_set_bool(KEY_INITIALIZED, true); + + return true; +} + +bool config_getMnemonicBytes(uint8_t *dest, uint16_t dest_size, + uint16_t *real_size) { + return sectrue == config_get_bytes(KEY_MNEMONIC, dest, dest_size, real_size); +} + +bool config_getMnemonic(char *dest, uint16_t dest_size) { + return sectrue == config_get_string(KEY_MNEMONIC, dest, dest_size); +} + +/* Check whether mnemonic matches storage. The mnemonic must be + * a null-terminated string. + */ +bool config_containsMnemonic(const char *mnemonic) { + uint16_t len = 0; + uint8_t stored_mnemonic[MAX_MNEMONIC_LEN]; + if (sectrue != storage_get(KEY_MNEMONIC, stored_mnemonic, + sizeof(stored_mnemonic), &len)) { + return false; + } + + // Compare the digests to mitigate side-channel attacks. + uint8_t digest_stored[SHA256_DIGEST_LENGTH]; + sha256_Raw(stored_mnemonic, len, digest_stored); + memzero(stored_mnemonic, sizeof(stored_mnemonic)); + + uint8_t digest_input[SHA256_DIGEST_LENGTH]; + sha256_Raw((const uint8_t *)mnemonic, strnlen(mnemonic, MAX_MNEMONIC_LEN), + digest_input); + + uint8_t diff = 0; + for (size_t i = 0; i < sizeof(digest_input); i++) { + diff |= (digest_stored[i] - digest_input[i]); + } + memzero(digest_stored, sizeof(digest_stored)); + memzero(digest_input, sizeof(digest_input)); + return diff == 0; +} + +/* Check whether pin matches storage. The pin must be + * a null-terminated string with at most 9 characters. + */ +bool config_unlock(const char *pin) { + char oldTiny = usbTiny(1); + secbool ret = storage_unlock(pin_to_int(pin)); + usbTiny(oldTiny); + return sectrue == ret; +} + +bool config_hasPin(void) { return sectrue == storage_has_pin(); } + +bool config_changePin(const char *old_pin, const char *new_pin) { + uint32_t new_pin_int = pin_to_int(new_pin); + if (new_pin_int == 0) { + return false; + } + + char oldTiny = usbTiny(1); + secbool ret = storage_change_pin(pin_to_int(old_pin), new_pin_int); + usbTiny(oldTiny); + +#if DEBUG_LINK + if (sectrue == ret) { + if (new_pin_int != PIN_EMPTY) { + storage_set(KEY_DEBUG_LINK_PIN, new_pin, strnlen(new_pin, MAX_PIN_LEN)); + } else { + storage_delete(KEY_DEBUG_LINK_PIN); + } + } +#endif + + memzero(&new_pin_int, sizeof(new_pin_int)); + + return sectrue == ret; +} + +#if DEBUG_LINK +bool config_getPin(char *dest, uint16_t dest_size) { + return sectrue == config_get_string(KEY_DEBUG_LINK_PIN, dest, dest_size); +} +#endif + +void session_cachePassphrase(const char *passphrase) { + strlcpy(sessionPassphrase, passphrase, sizeof(sessionPassphrase)); + sessionPassphraseCached = sectrue; +} + +bool session_isPassphraseCached(void) { + return sectrue == sessionPassphraseCached; +} + +bool session_getState(const uint8_t *salt, uint8_t *state, + const char *passphrase) { + if (!passphrase && sectrue != sessionPassphraseCached) { + return false; + } else { + passphrase = sessionPassphrase; + } + if (!salt) { + // if salt is not provided fill the first half of the state with random data + random_buffer(state, 32); + } else { + // if salt is provided fill the first half of the state with salt + memcpy(state, salt, 32); + } + // state[0:32] = salt + // state[32:64] = HMAC(passphrase, salt || device_id) + HMAC_SHA256_CTX ctx; + hmac_sha256_Init(&ctx, (const uint8_t *)passphrase, strlen(passphrase)); + hmac_sha256_Update(&ctx, state, 32); + hmac_sha256_Update(&ctx, (const uint8_t *)config_uuid, sizeof(config_uuid)); + hmac_sha256_Final(&ctx, state + 32); + + memzero(&ctx, sizeof(ctx)); + + return true; +} + +bool session_isUnlocked(void) { return sectrue == storage_is_unlocked(); } + +bool config_isInitialized(void) { + bool initialized = false; + config_get_bool(KEY_INITIALIZED, &initialized); + return initialized; +} + +bool config_getImported(bool *imported) { + return sectrue == config_get_bool(KEY_IMPORTED, imported); +} + +void config_setImported(bool imported) { + config_set_bool(KEY_IMPORTED, imported); +} + +bool config_getNeedsBackup(bool *needs_backup) { + return sectrue == config_get_bool(KEY_NEEDS_BACKUP, needs_backup); +} + +void config_setNeedsBackup(bool needs_backup) { + config_set_bool(KEY_NEEDS_BACKUP, needs_backup); +} + +bool config_getUnfinishedBackup(bool *unfinished_backup) { + return sectrue == config_get_bool(KEY_UNFINISHED_BACKUP, unfinished_backup); +} + +void config_setUnfinishedBackup(bool unfinished_backup) { + config_set_bool(KEY_UNFINISHED_BACKUP, unfinished_backup); +} + +bool config_getNoBackup(bool *no_backup) { + return sectrue == config_get_bool(KEY_NO_BACKUP, no_backup); +} + +void config_setNoBackup(void) { config_set_bool(KEY_NO_BACKUP, true); } + +void config_applyFlags(uint32_t flags) { + uint32_t old_flags = 0; + config_get_uint32(KEY_FLAGS, &old_flags); + flags |= old_flags; + if (flags == old_flags) { + return; // no new flags + } + storage_set(KEY_FLAGS, &flags, sizeof(flags)); +} + +bool config_getFlags(uint32_t *flags) { + return sectrue == config_get_uint32(KEY_FLAGS, flags); +} + +uint32_t config_nextU2FCounter(void) { + uint32_t u2fcounter = 0; + storage_next_counter(KEY_U2F_COUNTER, &u2fcounter); + return u2fcounter; +} + +void config_setU2FCounter(uint32_t u2fcounter) { + storage_set_counter(KEY_U2F_COUNTER, u2fcounter); +} + +uint32_t config_getAutoLockDelayMs() { + if (sectrue == autoLockDelayMsCached) { + return autoLockDelayMs; + } + + if (sectrue != storage_is_unlocked()) { + return autoLockDelayMsDefault; + } + + if (sectrue != config_get_uint32(KEY_AUTO_LOCK_DELAY_MS, &autoLockDelayMs)) { + autoLockDelayMs = autoLockDelayMsDefault; + } + autoLockDelayMsCached = sectrue; + return autoLockDelayMs; +} + +void config_setAutoLockDelayMs(uint32_t auto_lock_delay_ms) { + const uint32_t min_delay_ms = 10 * 1000U; // 10 seconds + auto_lock_delay_ms = MAX(auto_lock_delay_ms, min_delay_ms); + if (sectrue == storage_set(KEY_AUTO_LOCK_DELAY_MS, &auto_lock_delay_ms, + sizeof(auto_lock_delay_ms))) { + autoLockDelayMs = auto_lock_delay_ms; + autoLockDelayMsCached = sectrue; + } +} + +void config_wipe(void) { + char oldTiny = usbTiny(1); + storage_wipe(); + if (storage_is_unlocked() != sectrue) { + storage_unlock(PIN_EMPTY); + } + usbTiny(oldTiny); + random_buffer((uint8_t *)config_uuid, sizeof(config_uuid)); + data2hex(config_uuid, sizeof(config_uuid), config_uuid_str); + autoLockDelayMsCached = secfalse; + storage_set(KEY_UUID, config_uuid, sizeof(config_uuid)); + storage_set(KEY_VERSION, &CONFIG_VERSION, sizeof(CONFIG_VERSION)); + session_clear(false); +} diff --git a/legacy/firmware/config.h b/legacy/firmware/config.h new file mode 100644 index 0000000000..593cb90fe5 --- /dev/null +++ b/legacy/firmware/config.h @@ -0,0 +1,158 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#include "bip32.h" +#include "messages-management.pb.h" + +#define STORAGE_FIELD(TYPE, NAME) \ + bool has_##NAME; \ + TYPE NAME; + +#define STORAGE_STRING(NAME, SIZE) \ + bool has_##NAME; \ + char NAME[SIZE]; + +#define STORAGE_BYTES(NAME, SIZE) \ + bool has_##NAME; \ + struct { \ + uint32_t size; \ + uint8_t bytes[SIZE]; \ + } NAME; + +#define STORAGE_BOOL(NAME) STORAGE_FIELD(bool, NAME) +#define STORAGE_NODE(NAME) STORAGE_FIELD(StorageHDNode, NAME) +#define STORAGE_UINT32(NAME) STORAGE_FIELD(uint32_t, NAME) + +typedef struct { + uint32_t depth; + uint32_t fingerprint; + uint32_t child_num; + struct { + uint32_t size; + uint8_t bytes[32]; + } chain_code; + + STORAGE_BYTES(private_key, 32); + STORAGE_BYTES(public_key, 33); +} StorageHDNode; + +typedef struct _Storage { + uint32_t version; + + STORAGE_NODE(node) + STORAGE_STRING(mnemonic, 241) + STORAGE_BOOL(passphrase_protection) + STORAGE_UINT32(pin_failed_attempts) + STORAGE_STRING(pin, 10) + STORAGE_STRING(language, 17) + STORAGE_STRING(label, 33) + STORAGE_BOOL(imported) + STORAGE_BYTES(homescreen, 1024) + STORAGE_UINT32(u2f_counter) + STORAGE_BOOL(needs_backup) + STORAGE_UINT32(flags) + STORAGE_NODE(u2froot) + STORAGE_BOOL(unfinished_backup) + STORAGE_UINT32(auto_lock_delay_ms) + STORAGE_BOOL(no_backup) +} Storage; + +extern Storage configUpdate; + +#define MAX_PIN_LEN 9 +#define MAX_LABEL_LEN 32 +#define MAX_LANGUAGE_LEN 16 +#define MAX_MNEMONIC_LEN 240 +#define HOMESCREEN_SIZE 1024 +#define UUID_SIZE 12 + +void config_init(void); +void session_clear(bool lock); + +void config_loadDevice(const LoadDevice *msg); + +const uint8_t *config_getSeed(bool usePassphrase); + +bool config_getU2FRoot(HDNode *node); +bool config_getRootNode(HDNode *node, const char *curve, bool usePassphrase); + +bool config_getLabel(char *dest, uint16_t dest_size); +void config_setLabel(const char *label); + +bool config_getLanguage(char *dest, uint16_t dest_size); +void config_setLanguage(const char *lang); + +void config_setPassphraseProtection(bool passphrase_protection); +bool config_getPassphraseProtection(bool *passphrase_protection); + +bool config_getHomescreen(uint8_t *dest, uint16_t dest_size); +void config_setHomescreen(const uint8_t *data, uint32_t size); + +void session_cachePassphrase(const char *passphrase); +bool session_isPassphraseCached(void); +bool session_getState(const uint8_t *salt, uint8_t *state, + const char *passphrase); + +bool config_setMnemonic(const char *mnemonic); +bool config_containsMnemonic(const char *mnemonic); +bool config_getMnemonic(char *dest, uint16_t dest_size); +bool config_getMnemonicBytes(uint8_t *dest, uint16_t dest_size, + uint16_t *real_size); + +#if DEBUG_LINK +bool config_dumpNode(HDNodeType *node); +bool config_getPin(char *dest, uint16_t dest_size); +#endif + +bool config_unlock(const char *pin); +bool config_hasPin(void); +bool config_changePin(const char *old_pin, const char *new_pin); +bool session_isUnlocked(void); + +uint32_t config_nextU2FCounter(void); +void config_setU2FCounter(uint32_t u2fcounter); + +bool config_isInitialized(void); + +bool config_getImported(bool *imported); +void config_setImported(bool imported); + +bool config_getNeedsBackup(bool *needs_backup); +void config_setNeedsBackup(bool needs_backup); + +bool config_getUnfinishedBackup(bool *unfinished_backup); +void config_setUnfinishedBackup(bool unfinished_backup); + +bool config_getNoBackup(bool *no_backup); +void config_setNoBackup(void); + +void config_applyFlags(uint32_t flags); +bool config_getFlags(uint32_t *flags); + +uint32_t config_getAutoLockDelayMs(void); +void config_setAutoLockDelayMs(uint32_t auto_lock_delay_ms); + +void config_wipe(void); + +extern char config_uuid_str[2 * UUID_SIZE + 1]; + +#endif diff --git a/legacy/firmware/crypto.c b/legacy/firmware/crypto.c new file mode 100644 index 0000000000..3b7aac7d68 --- /dev/null +++ b/legacy/firmware/crypto.c @@ -0,0 +1,498 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "crypto.h" +#include +#include "address.h" +#include "aes/aes.h" +#include "base58.h" +#include "bip32.h" +#include "cash_addr.h" +#include "coins.h" +#include "curves.h" +#include "hmac.h" +#include "layout.h" +#include "pbkdf2.h" +#include "secp256k1.h" +#include "segwit_addr.h" +#include "sha2.h" + +uint32_t ser_length(uint32_t len, uint8_t *out) { + if (len < 253) { + out[0] = len & 0xFF; + return 1; + } + if (len < 0x10000) { + out[0] = 253; + out[1] = len & 0xFF; + out[2] = (len >> 8) & 0xFF; + return 3; + } + out[0] = 254; + out[1] = len & 0xFF; + out[2] = (len >> 8) & 0xFF; + out[3] = (len >> 16) & 0xFF; + out[4] = (len >> 24) & 0xFF; + return 5; +} + +uint32_t ser_length_hash(Hasher *hasher, uint32_t len) { + if (len < 253) { + hasher_Update(hasher, (const uint8_t *)&len, 1); + return 1; + } + if (len < 0x10000) { + uint8_t d = 253; + hasher_Update(hasher, &d, 1); + hasher_Update(hasher, (const uint8_t *)&len, 2); + return 3; + } + uint8_t d = 254; + hasher_Update(hasher, &d, 1); + hasher_Update(hasher, (const uint8_t *)&len, 4); + return 5; +} + +uint32_t deser_length(const uint8_t *in, uint32_t *out) { + if (in[0] < 253) { + *out = in[0]; + return 1; + } + if (in[0] == 253) { + *out = in[1] + (in[2] << 8); + return 1 + 2; + } + if (in[0] == 254) { + *out = in[1] + (in[2] << 8) + (in[3] << 16) + ((uint32_t)in[4] << 24); + return 1 + 4; + } + *out = 0; // ignore 64 bit + return 1 + 8; +} + +int sshMessageSign(HDNode *node, const uint8_t *message, size_t message_len, + uint8_t *signature) { + signature[0] = 0; // prefix: pad with zero, so all signatures are 65 bytes + return hdnode_sign(node, message, message_len, HASHER_SHA2, signature + 1, + NULL, NULL); +} + +int gpgMessageSign(HDNode *node, const uint8_t *message, size_t message_len, + uint8_t *signature) { + signature[0] = 0; // prefix: pad with zero, so all signatures are 65 bytes + const curve_info *ed25519_curve_info = get_curve_by_name(ED25519_NAME); + if (ed25519_curve_info && node->curve == ed25519_curve_info) { + // GPG supports variable size digest for Ed25519 signatures + return hdnode_sign(node, message, message_len, 0, signature + 1, NULL, + NULL); + } else { + // Ensure 256-bit digest before proceeding + if (message_len != 32) { + return 1; + } + return hdnode_sign_digest(node, message, signature + 1, NULL, NULL); + } +} + +static void cryptoMessageHash(const CoinInfo *coin, const uint8_t *message, + size_t message_len, + uint8_t hash[HASHER_DIGEST_LENGTH]) { + Hasher hasher; + hasher_Init(&hasher, coin->curve->hasher_sign); + hasher_Update(&hasher, (const uint8_t *)coin->signed_message_header, + strlen(coin->signed_message_header)); + uint8_t varint[5]; + uint32_t l = ser_length(message_len, varint); + hasher_Update(&hasher, varint, l); + hasher_Update(&hasher, message, message_len); + hasher_Final(&hasher, hash); +} + +int cryptoMessageSign(const CoinInfo *coin, HDNode *node, + InputScriptType script_type, const uint8_t *message, + size_t message_len, uint8_t *signature) { + uint8_t hash[HASHER_DIGEST_LENGTH]; + cryptoMessageHash(coin, message, message_len, hash); + + uint8_t pby; + int result = hdnode_sign_digest(node, hash, signature + 1, &pby, NULL); + if (result == 0) { + switch (script_type) { + case InputScriptType_SPENDP2SHWITNESS: + // segwit-in-p2sh + signature[0] = 35 + pby; + break; + case InputScriptType_SPENDWITNESS: + // segwit + signature[0] = 39 + pby; + break; + default: + // p2pkh + signature[0] = 31 + pby; + break; + } + } + return result; +} + +int cryptoMessageVerify(const CoinInfo *coin, const uint8_t *message, + size_t message_len, const char *address, + const uint8_t *signature) { + // check for invalid signature prefix + if (signature[0] < 27 || signature[0] > 43) { + return 1; + } + + uint8_t hash[HASHER_DIGEST_LENGTH]; + cryptoMessageHash(coin, message, message_len, hash); + + uint8_t recid = (signature[0] - 27) % 4; + bool compressed = signature[0] >= 31; + + // check if signature verifies the digest and recover the public key + uint8_t pubkey[65]; + if (ecdsa_recover_pub_from_sig(coin->curve->params, pubkey, signature + 1, + hash, recid) != 0) { + return 3; + } + // convert public key to compressed pubkey if necessary + if (compressed) { + pubkey[0] = 0x02 | (pubkey[64] & 1); + } + + // check if the address is correct + uint8_t addr_raw[MAX_ADDR_RAW_SIZE]; + uint8_t recovered_raw[MAX_ADDR_RAW_SIZE]; + + // p2pkh + if (signature[0] >= 27 && signature[0] <= 34) { + size_t len; + if (coin->cashaddr_prefix) { + if (!cash_addr_decode(addr_raw, &len, coin->cashaddr_prefix, address)) { + return 2; + } + } else { + len = base58_decode_check(address, coin->curve->hasher_base58, addr_raw, + MAX_ADDR_RAW_SIZE); + } + ecdsa_get_address_raw(pubkey, coin->address_type, + coin->curve->hasher_pubkey, recovered_raw); + if (memcmp(recovered_raw, addr_raw, len) != 0 || + len != address_prefix_bytes_len(coin->address_type) + 20) { + return 2; + } + } else + // segwit-in-p2sh + if (signature[0] >= 35 && signature[0] <= 38) { + size_t len = base58_decode_check(address, coin->curve->hasher_base58, + addr_raw, MAX_ADDR_RAW_SIZE); + ecdsa_get_address_segwit_p2sh_raw(pubkey, coin->address_type_p2sh, + coin->curve->hasher_pubkey, + recovered_raw); + if (memcmp(recovered_raw, addr_raw, len) != 0 || + len != address_prefix_bytes_len(coin->address_type_p2sh) + 20) { + return 2; + } + } else + // segwit + if (signature[0] >= 39 && signature[0] <= 42) { + int witver; + size_t len; + if (!coin->bech32_prefix || + !segwit_addr_decode(&witver, recovered_raw, &len, coin->bech32_prefix, + address)) { + return 4; + } + ecdsa_get_pubkeyhash(pubkey, coin->curve->hasher_pubkey, addr_raw); + if (memcmp(recovered_raw, addr_raw, len) != 0 || witver != 0 || len != 20) { + return 2; + } + } else { + return 4; + } + + return 0; +} + +/* ECIES disabled +int cryptoMessageEncrypt(curve_point *pubkey, const uint8_t *msg, size_t +msg_size, bool display_only, uint8_t *nonce, size_t *nonce_len, uint8_t +*payload, size_t *payload_len, uint8_t *hmac, size_t *hmac_len, const uint8_t +*privkey, const uint8_t *address_raw) +{ + if (privkey && address_raw) { // signing == true + HDNode node; + payload[0] = display_only ? 0x81 : 0x01; + uint32_t l = ser_length(msg_size, payload + 1); + memcpy(payload + 1 + l, msg, msg_size); + memcpy(payload + 1 + l + msg_size, address_raw, 21); + hdnode_from_xprv(0, 0, 0, privkey, privkey, SECP256K1_NAME, +&node); if (cryptoMessageSign(&node, msg, msg_size, payload + 1 + l + msg_size + +21) != 0) { return 1; + } + *payload_len = 1 + l + msg_size + 21 + 65; + } else { + payload[0] = display_only ? 0x80 : 0x00; + uint32_t l = ser_length(msg_size, payload + 1); + memcpy(payload + 1 + l, msg, msg_size); + *payload_len = 1 + l + msg_size; + } + // generate random nonce + curve_point R; + bignum256 k; + if (generate_k_random(&secp256k1, &k) != 0) { + return 2; + } + // compute k*G + scalar_multiply(&secp256k1, &k, &R); + nonce[0] = 0x02 | (R.y.val[0] & 0x01); + bn_write_be(&R.x, nonce + 1); + *nonce_len = 33; + // compute shared secret + point_multiply(&secp256k1, &k, pubkey, &R); + uint8_t shared_secret[33]; + shared_secret[0] = 0x02 | (R.y.val[0] & 0x01); + bn_write_be(&R.x, shared_secret + 1); + // generate keying bytes + uint8_t keying_bytes[80]; + uint8_t salt[22 + 33]; + memcpy(salt, "Bitcoin Secure Message", 22); + memcpy(salt + 22, nonce, 33); + pbkdf2_hmac_sha256(shared_secret, 33, salt, 22 + 33, 2048, keying_bytes, +80); + // encrypt payload + aes_encrypt_ctx ctx; + aes_encrypt_key256(keying_bytes, &ctx); + aes_cfb_encrypt(payload, payload, *payload_len, keying_bytes + 64, +&ctx); + // compute hmac + uint8_t out[32]; + hmac_sha256(keying_bytes + 32, 32, payload, *payload_len, out); + memcpy(hmac, out, 8); + *hmac_len = 8; + + return 0; +} + +int cryptoMessageDecrypt(curve_point *nonce, uint8_t *payload, size_t +payload_len, const uint8_t *hmac, size_t hmac_len, const uint8_t *privkey, +uint8_t *msg, size_t *msg_len, bool *display_only, bool *signing, uint8_t +*address_raw) +{ + if (hmac_len != 8) { + return 1; + } + // compute shared secret + curve_point R; + bignum256 k; + bn_read_be(privkey, &k); + point_multiply(&secp256k1, &k, nonce, &R); + uint8_t shared_secret[33]; + shared_secret[0] = 0x02 | (R.y.val[0] & 0x01); + bn_write_be(&R.x, shared_secret + 1); + // generate keying bytes + uint8_t keying_bytes[80]; + uint8_t salt[22 + 33]; + memcpy(salt, "Bitcoin Secure Message", 22); + salt[22] = 0x02 | (nonce->y.val[0] & 0x01); + bn_write_be(&(nonce->x), salt + 23); + pbkdf2_hmac_sha256(shared_secret, 33, salt, 22 + 33, 2048, keying_bytes, +80); + // compute hmac + uint8_t out[32]; + hmac_sha256(keying_bytes + 32, 32, payload, payload_len, out); + if (memcmp(hmac, out, 8) != 0) { + return 2; + } + // decrypt payload + aes_encrypt_ctx ctx; + aes_encrypt_key256(keying_bytes, &ctx); + aes_cfb_decrypt(payload, payload, payload_len, keying_bytes + 64, &ctx); + // check first byte + if (payload[0] != 0x00 && payload[0] != 0x01 && payload[0] != 0x80 && +payload[0] != 0x81) { return 3; + } + *signing = payload[0] & 0x01; + *display_only = payload[0] & 0x80; + uint32_t l, o; + l = deser_length(payload + 1, &o); + if (*signing) { + // FIXME: assumes a raw address is 21 bytes (also below). + if (1 + l + o + 21 + 65 != payload_len) { + return 4; + } + // FIXME: cryptoMessageVerify changed to take the address_type +as a parameter. if (cryptoMessageVerify(payload + 1 + l, o, payload + 1 + l + o, +payload + 1 + l + o + 21) != 0) { return 5; + } + memcpy(address_raw, payload + 1 + l + o, 21); + } else { + if (1 + l + o != payload_len) { + return 4; + } + } + memcpy(msg, payload + 1 + l, o); + *msg_len = o; + return 0; +} +*/ + +const HDNode *cryptoMultisigPubkey(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint32_t index) { + const HDNodeType *node_ptr; + const uint32_t *address_n; + uint32_t address_n_count; + if (multisig->nodes_count) { // use multisig->nodes + if (index >= multisig->nodes_count) { + return 0; + } + node_ptr = &(multisig->nodes[index]); + address_n = multisig->address_n; + address_n_count = multisig->address_n_count; + } else if (multisig->pubkeys_count) { // use multisig->pubkeys + if (index >= multisig->pubkeys_count) { + return 0; + } + node_ptr = &(multisig->pubkeys[index].node); + address_n = multisig->pubkeys[index].address_n; + address_n_count = multisig->pubkeys[index].address_n_count; + } else { + return 0; + } + if (node_ptr->chain_code.size != 32) return 0; + if (!node_ptr->has_public_key || node_ptr->public_key.size != 33) return 0; + static HDNode node; + if (!hdnode_from_xpub(node_ptr->depth, node_ptr->child_num, + node_ptr->chain_code.bytes, node_ptr->public_key.bytes, + coin->curve_name, &node)) { + return 0; + } + layoutProgressUpdate(true); + for (uint32_t i = 0; i < address_n_count; i++) { + if (!hdnode_public_ckd(&node, address_n[i])) { + return 0; + } + layoutProgressUpdate(true); + } + return &node; +} + +uint32_t cryptoMultisigPubkeyCount(const MultisigRedeemScriptType *multisig) { + return multisig->nodes_count ? multisig->nodes_count + : multisig->pubkeys_count; +} + +int cryptoMultisigPubkeyIndex(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + const uint8_t *pubkey) { + for (size_t i = 0; i < cryptoMultisigPubkeyCount(multisig); i++) { + const HDNode *pubnode = cryptoMultisigPubkey(coin, multisig, i); + if (pubnode && memcmp(pubnode->public_key, pubkey, 33) == 0) { + return i; + } + } + return -1; +} + +int cryptoMultisigFingerprint(const MultisigRedeemScriptType *multisig, + uint8_t *hash) { + static const HDNodeType *pubnodes[15], *swap; + const uint32_t n = cryptoMultisigPubkeyCount(multisig); + if (n < 1 || n > 15) { + return 0; + } + if (!multisig->has_m || multisig->m < 1 || multisig->m > 15) { + return 0; + } + for (uint32_t i = 0; i < n; i++) { + if (multisig->nodes_count) { // use multisig->nodes + pubnodes[i] = &(multisig->nodes[i]); + } else if (multisig->pubkeys_count) { // use multisig->pubkeys + pubnodes[i] = &(multisig->pubkeys[i].node); + } else { + return 0; + } + } + for (uint32_t i = 0; i < n; i++) { + if (!pubnodes[i]->has_public_key || pubnodes[i]->public_key.size != 33) + return 0; + if (pubnodes[i]->chain_code.size != 32) return 0; + } + // minsort according to pubkey + for (uint32_t i = 0; i < n - 1; i++) { + for (uint32_t j = n - 1; j > i; j--) { + if (memcmp(pubnodes[i]->public_key.bytes, pubnodes[j]->public_key.bytes, + 33) > 0) { + swap = pubnodes[i]; + pubnodes[i] = pubnodes[j]; + pubnodes[j] = swap; + } + } + } + // hash sorted nodes + SHA256_CTX ctx; + sha256_Init(&ctx); + sha256_Update(&ctx, (const uint8_t *)&(multisig->m), sizeof(uint32_t)); + for (uint32_t i = 0; i < n; i++) { + sha256_Update(&ctx, (const uint8_t *)&(pubnodes[i]->depth), + sizeof(uint32_t)); + sha256_Update(&ctx, (const uint8_t *)&(pubnodes[i]->fingerprint), + sizeof(uint32_t)); + sha256_Update(&ctx, (const uint8_t *)&(pubnodes[i]->child_num), + sizeof(uint32_t)); + sha256_Update(&ctx, pubnodes[i]->chain_code.bytes, 32); + sha256_Update(&ctx, pubnodes[i]->public_key.bytes, 33); + } + sha256_Update(&ctx, (const uint8_t *)&n, sizeof(uint32_t)); + sha256_Final(&ctx, hash); + layoutProgressUpdate(true); + return 1; +} + +int cryptoIdentityFingerprint(const IdentityType *identity, uint8_t *hash) { + SHA256_CTX ctx; + sha256_Init(&ctx); + sha256_Update(&ctx, (const uint8_t *)&(identity->index), sizeof(uint32_t)); + if (identity->has_proto && identity->proto[0]) { + sha256_Update(&ctx, (const uint8_t *)(identity->proto), + strlen(identity->proto)); + sha256_Update(&ctx, (const uint8_t *)"://", 3); + } + if (identity->has_user && identity->user[0]) { + sha256_Update(&ctx, (const uint8_t *)(identity->user), + strlen(identity->user)); + sha256_Update(&ctx, (const uint8_t *)"@", 1); + } + if (identity->has_host && identity->host[0]) { + sha256_Update(&ctx, (const uint8_t *)(identity->host), + strlen(identity->host)); + } + if (identity->has_port && identity->port[0]) { + sha256_Update(&ctx, (const uint8_t *)":", 1); + sha256_Update(&ctx, (const uint8_t *)(identity->port), + strlen(identity->port)); + } + if (identity->has_path && identity->path[0]) { + sha256_Update(&ctx, (const uint8_t *)(identity->path), + strlen(identity->path)); + } + sha256_Final(&ctx, hash); + return 1; +} diff --git a/legacy/firmware/crypto.h b/legacy/firmware/crypto.h new file mode 100644 index 0000000000..ab550864ed --- /dev/null +++ b/legacy/firmware/crypto.h @@ -0,0 +1,82 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __CRYPTO_H__ +#define __CRYPTO_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "coins.h" +#include "hasher.h" +#include "messages-bitcoin.pb.h" +#include "messages-crypto.pb.h" + +#define ser_length_size(len) ((len) < 253 ? 1 : (len) < 0x10000 ? 3 : 5) + +uint32_t ser_length(uint32_t len, uint8_t *out); + +uint32_t ser_length_hash(Hasher *hasher, uint32_t len); + +int sshMessageSign(HDNode *node, const uint8_t *message, size_t message_len, + uint8_t *signature); + +int gpgMessageSign(HDNode *node, const uint8_t *message, size_t message_len, + uint8_t *signature); + +int cryptoMessageSign(const CoinInfo *coin, HDNode *node, + InputScriptType script_type, const uint8_t *message, + size_t message_len, uint8_t *signature); + +int cryptoMessageVerify(const CoinInfo *coin, const uint8_t *message, + size_t message_len, const char *address, + const uint8_t *signature); + +/* ECIES disabled +int cryptoMessageEncrypt(curve_point *pubkey, const uint8_t *msg, size_t +msg_size, bool display_only, uint8_t *nonce, size_t *nonce_len, uint8_t +*payload, size_t *payload_len, uint8_t *hmac, size_t *hmac_len, const uint8_t +*privkey, const uint8_t *address_raw); + +int cryptoMessageDecrypt(curve_point *nonce, uint8_t *payload, size_t +payload_len, const uint8_t *hmac, size_t hmac_len, const uint8_t *privkey, +uint8_t *msg, size_t *msg_len, bool *display_only, bool *signing, uint8_t +*address_raw); +*/ + +const HDNode *cryptoMultisigPubkey(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint32_t index); + +uint32_t cryptoMultisigPubkeyCount(const MultisigRedeemScriptType *multisig); + +int cryptoMultisigPubkeyIndex(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + const uint8_t *pubkey); + +int cryptoMultisigFingerprint(const MultisigRedeemScriptType *multisig, + uint8_t *hash); + +int cryptoIdentityFingerprint(const IdentityType *identity, uint8_t *hash); + +#endif diff --git a/legacy/firmware/debug.c b/legacy/firmware/debug.c new file mode 100644 index 0000000000..288041a4c0 --- /dev/null +++ b/legacy/firmware/debug.c @@ -0,0 +1,65 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "debug.h" +#include "oled.h" +#include "trezor.h" +#include "util.h" + +#if DEBUG_LOG + +void oledDebug(const char *line) { + static const char *lines[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + static char id = 3; + for (int i = 0; i < 7; i++) { + lines[i] = lines[i + 1]; + } + lines[7] = line; + oledClear(); + for (int i = 0; i < 8; i++) { + if (lines[i]) { + oledDrawChar(0, i * 8, '0' + (id + i) % 10, FONT_STANDARD); + oledDrawString(8, i * 8, lines[i], FONT_STANDARD); + } + } + oledRefresh(); + id = (id + 1) % 10; +} + +void debugLog(int level, const char *bucket, const char *text) { + (void)level; + (void)bucket; +#if EMULATOR + puts(text); +#else + oledDebug(text); +#endif +} + +char *debugInt(const uint32_t i) { + static uint8_t n = 0; + static char id[8][9]; + uint32hex(i, id[n]); + debugLog(0, "", id[n]); + char *ret = (char *)id[n]; + n = (n + 1) % 8; + return ret; +} + +#endif diff --git a/legacy/firmware/debug.h b/legacy/firmware/debug.h new file mode 100644 index 0000000000..0a20807cdb --- /dev/null +++ b/legacy/firmware/debug.h @@ -0,0 +1,42 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#include +#include "trezor.h" + +#if DEBUG_LOG + +void debugLog(int level, const char *bucket, const char *text); +char *debugInt(const uint32_t i); + +#else + +#define debugLog(L, B, T) \ + do { \ + } while (0) +#define debugInt(I) \ + do { \ + } while (0) + +#endif + +#endif diff --git a/legacy/firmware/defs b/legacy/firmware/defs new file mode 120000 index 0000000000..bbc8f8e0c4 --- /dev/null +++ b/legacy/firmware/defs @@ -0,0 +1 @@ +../vendor/trezor-common/defs \ No newline at end of file diff --git a/legacy/firmware/ethereum.c b/legacy/firmware/ethereum.c new file mode 100644 index 0000000000..51011cd6ac --- /dev/null +++ b/legacy/firmware/ethereum.c @@ -0,0 +1,771 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2016 Alex Beregszaszi + * Copyright (C) 2016 Pavol Rusnak + * Copyright (C) 2016 Jochen Hoenicke + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "ethereum.h" +#include "address.h" +#include "crypto.h" +#include "ecdsa.h" +#include "ethereum_networks.h" +#include "ethereum_tokens.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "memzero.h" +#include "messages.h" +#include "messages.pb.h" +#include "protect.h" +#include "secp256k1.h" +#include "sha3.h" +#include "transaction.h" +#include "util.h" + +/* maximum supported chain id. v must fit in an uint32_t. */ +#define MAX_CHAIN_ID 2147483629 + +static bool ethereum_signing = false; +static uint32_t data_total, data_left; +static EthereumTxRequest msg_tx_request; +static CONFIDENTIAL uint8_t privkey[32]; +static uint32_t chain_id; +static uint32_t tx_type; +struct SHA3_CTX keccak_ctx; + +static inline void hash_data(const uint8_t *buf, size_t size) { + sha3_Update(&keccak_ctx, buf, size); +} + +/* + * Push an RLP encoded length to the hash buffer. + */ +static void hash_rlp_length(uint32_t length, uint8_t firstbyte) { + uint8_t buf[4]; + if (length == 1 && firstbyte <= 0x7f) { + /* empty length header */ + } else if (length <= 55) { + buf[0] = 0x80 + length; + hash_data(buf, 1); + } else if (length <= 0xff) { + buf[0] = 0xb7 + 1; + buf[1] = length; + hash_data(buf, 2); + } else if (length <= 0xffff) { + buf[0] = 0xb7 + 2; + buf[1] = length >> 8; + buf[2] = length & 0xff; + hash_data(buf, 3); + } else { + buf[0] = 0xb7 + 3; + buf[1] = length >> 16; + buf[2] = length >> 8; + buf[3] = length & 0xff; + hash_data(buf, 4); + } +} + +/* + * Push an RLP encoded list length to the hash buffer. + */ +static void hash_rlp_list_length(uint32_t length) { + uint8_t buf[4]; + if (length <= 55) { + buf[0] = 0xc0 + length; + hash_data(buf, 1); + } else if (length <= 0xff) { + buf[0] = 0xf7 + 1; + buf[1] = length; + hash_data(buf, 2); + } else if (length <= 0xffff) { + buf[0] = 0xf7 + 2; + buf[1] = length >> 8; + buf[2] = length & 0xff; + hash_data(buf, 3); + } else { + buf[0] = 0xf7 + 3; + buf[1] = length >> 16; + buf[2] = length >> 8; + buf[3] = length & 0xff; + hash_data(buf, 4); + } +} + +/* + * Push an RLP encoded length field and data to the hash buffer. + */ +static void hash_rlp_field(const uint8_t *buf, size_t size) { + hash_rlp_length(size, buf[0]); + hash_data(buf, size); +} + +/* + * Push an RLP encoded number to the hash buffer. + * Ethereum yellow paper says to convert to big endian and strip leading zeros. + */ +static void hash_rlp_number(uint32_t number) { + if (!number) { + return; + } + uint8_t data[4]; + data[0] = (number >> 24) & 0xff; + data[1] = (number >> 16) & 0xff; + data[2] = (number >> 8) & 0xff; + data[3] = (number)&0xff; + int offset = 0; + while (!data[offset]) { + offset++; + } + hash_rlp_field(data + offset, 4 - offset); +} + +/* + * Calculate the number of bytes needed for an RLP length header. + * NOTE: supports up to 16MB of data (how unlikely...) + * FIXME: improve + */ +static int rlp_calculate_length(int length, uint8_t firstbyte) { + if (length == 1 && firstbyte <= 0x7f) { + return 1; + } else if (length <= 55) { + return 1 + length; + } else if (length <= 0xff) { + return 2 + length; + } else if (length <= 0xffff) { + return 3 + length; + } else { + return 4 + length; + } +} + +static int rlp_calculate_number_length(uint32_t number) { + if (number <= 0x7f) { + return 1; + } else if (number <= 0xff) { + return 2; + } else if (number <= 0xffff) { + return 3; + } else if (number <= 0xffffff) { + return 4; + } else { + return 5; + } +} + +static void send_request_chunk(void) { + int progress = 1000 - (data_total > 1000000 ? data_left / (data_total / 800) + : data_left * 800 / data_total); + layoutProgress(_("Signing"), progress); + msg_tx_request.has_data_length = true; + msg_tx_request.data_length = data_left <= 1024 ? data_left : 1024; + msg_write(MessageType_MessageType_EthereumTxRequest, &msg_tx_request); +} + +static int ethereum_is_canonic(uint8_t v, uint8_t signature[64]) { + (void)signature; + return (v & 2) == 0; +} + +static void send_signature(void) { + uint8_t hash[32], sig[64]; + uint8_t v; + layoutProgress(_("Signing"), 1000); + + /* eip-155 replay protection */ + if (chain_id) { + /* hash v=chain_id, r=0, s=0 */ + hash_rlp_number(chain_id); + hash_rlp_length(0, 0); + hash_rlp_length(0, 0); + } + + keccak_Final(&keccak_ctx, hash); + if (ecdsa_sign_digest(&secp256k1, privkey, hash, sig, &v, + ethereum_is_canonic) != 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed")); + ethereum_signing_abort(); + return; + } + + memzero(privkey, sizeof(privkey)); + + /* Send back the result */ + msg_tx_request.has_data_length = false; + + msg_tx_request.has_signature_v = true; + if (chain_id > MAX_CHAIN_ID) { + msg_tx_request.signature_v = v; + } else if (chain_id) { + msg_tx_request.signature_v = v + 2 * chain_id + 35; + } else { + msg_tx_request.signature_v = v + 27; + } + + msg_tx_request.has_signature_r = true; + msg_tx_request.signature_r.size = 32; + memcpy(msg_tx_request.signature_r.bytes, sig, 32); + + msg_tx_request.has_signature_s = true; + msg_tx_request.signature_s.size = 32; + memcpy(msg_tx_request.signature_s.bytes, sig + 32, 32); + + msg_write(MessageType_MessageType_EthereumTxRequest, &msg_tx_request); + + ethereum_signing_abort(); +} +/* Format a 256 bit number (amount in wei) into a human readable format + * using standard ethereum units. + * The buffer must be at least 25 bytes. + */ +static void ethereumFormatAmount(const bignum256 *amnt, const TokenType *token, + char *buf, int buflen) { + bignum256 bn1e9; + bn_read_uint32(1000000000, &bn1e9); + const char *suffix = NULL; + int decimals = 18; + if (token == UnknownToken) { + strlcpy(buf, "Unknown token value", buflen); + return; + } else if (token != NULL) { + suffix = token->ticker; + decimals = token->decimals; + } else if (bn_is_less(amnt, &bn1e9)) { + suffix = " Wei"; + decimals = 0; + } else { + if (tx_type == 1 || tx_type == 6) { + suffix = " WAN"; + } else { + ASSIGN_ETHEREUM_SUFFIX(suffix, chain_id); + } + } + bn_format(amnt, NULL, suffix, decimals, 0, false, buf, buflen); +} + +static void layoutEthereumConfirmTx(const uint8_t *to, uint32_t to_len, + const uint8_t *value, uint32_t value_len, + const TokenType *token) { + bignum256 val; + uint8_t pad_val[32]; + memzero(pad_val, sizeof(pad_val)); + memcpy(pad_val + (32 - value_len), value, value_len); + bn_read_be(pad_val, &val); + + char amount[32]; + if (token == NULL) { + if (bn_is_zero(&val)) { + strcpy(amount, _("message")); + } else { + ethereumFormatAmount(&val, NULL, amount, sizeof(amount)); + } + } else { + ethereumFormatAmount(&val, token, amount, sizeof(amount)); + } + + char _to1[] = "to 0x__________"; + char _to2[] = "_______________"; + char _to3[] = "_______________?"; + + if (to_len) { + char to_str[41]; + + bool rskip60 = false; + // constants from trezor-common/defs/ethereum/networks.json + switch (chain_id) { + case 30: + rskip60 = true; + break; + case 31: + rskip60 = true; + break; + } + + ethereum_address_checksum(to, to_str, rskip60, chain_id); + memcpy(_to1 + 5, to_str, 10); + memcpy(_to2, to_str + 10, 15); + memcpy(_to3, to_str + 25, 15); + } else { + strlcpy(_to1, _("to new contract?"), sizeof(_to1)); + strlcpy(_to2, "", sizeof(_to2)); + strlcpy(_to3, "", sizeof(_to3)); + } + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Send"), amount, _to1, _to2, _to3, NULL); +} + +static void layoutEthereumData(const uint8_t *data, uint32_t len, + uint32_t total_len) { + char hexdata[3][17]; + char summary[20]; + uint32_t printed = 0; + for (int i = 0; i < 3; i++) { + uint32_t linelen = len - printed; + if (linelen > 8) { + linelen = 8; + } + data2hex(data, linelen, hexdata[i]); + data += linelen; + printed += linelen; + } + + strcpy(summary, "... bytes"); + char *p = summary + 11; + uint32_t number = total_len; + while (number > 0) { + *p-- = '0' + number % 10; + number = number / 10; + } + char *summarystart = summary; + if (total_len == printed) summarystart = summary + 4; + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Transaction data:"), hexdata[0], hexdata[1], hexdata[2], + summarystart, NULL); +} + +static void layoutEthereumFee(const uint8_t *value, uint32_t value_len, + const uint8_t *gas_price, uint32_t gas_price_len, + const uint8_t *gas_limit, uint32_t gas_limit_len, + bool is_token) { + bignum256 val, gas; + uint8_t pad_val[32]; + char tx_value[32]; + char gas_value[32]; + + memzero(tx_value, sizeof(tx_value)); + memzero(gas_value, sizeof(gas_value)); + + memzero(pad_val, sizeof(pad_val)); + memcpy(pad_val + (32 - gas_price_len), gas_price, gas_price_len); + bn_read_be(pad_val, &val); + + memzero(pad_val, sizeof(pad_val)); + memcpy(pad_val + (32 - gas_limit_len), gas_limit, gas_limit_len); + bn_read_be(pad_val, &gas); + bn_multiply(&val, &gas, &secp256k1.prime); + + ethereumFormatAmount(&gas, NULL, gas_value, sizeof(gas_value)); + + memzero(pad_val, sizeof(pad_val)); + memcpy(pad_val + (32 - value_len), value, value_len); + bn_read_be(pad_val, &val); + + if (bn_is_zero(&val)) { + strcpy(tx_value, is_token ? _("token") : _("message")); + } else { + ethereumFormatAmount(&val, NULL, tx_value, sizeof(tx_value)); + } + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Really send"), tx_value, _("paying up to"), gas_value, + _("for gas?"), NULL); +} + +/* + * RLP fields: + * - nonce (0 .. 32) + * - gas_price (0 .. 32) + * - gas_limit (0 .. 32) + * - to (0, 20) + * - value (0 .. 32) + * - data (0 ..) + */ + +static bool ethereum_signing_check(const EthereumSignTx *msg) { + if (!msg->has_gas_price || !msg->has_gas_limit) { + return false; + } + + size_t tolen = msg->has_to ? strlen(msg->to) : 0; + + if (tolen != 42 && tolen != 40 && tolen != 0) { + /* Address has wrong length */ + return false; + } + + // sending transaction to address 0 (contract creation) without a data field + if (tolen == 0 && (!msg->has_data_length || msg->data_length == 0)) { + return false; + } + + if (msg->gas_price.size + msg->gas_limit.size > 30) { + // sanity check that fee doesn't overflow + return false; + } + + return true; +} + +void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node) { + ethereum_signing = true; + sha3_256_Init(&keccak_ctx); + + memzero(&msg_tx_request, sizeof(EthereumTxRequest)); + /* set fields to 0, to avoid conditions later */ + if (!msg->has_value) msg->value.size = 0; + if (!msg->has_data_initial_chunk) msg->data_initial_chunk.size = 0; + bool toset; + uint8_t pubkeyhash[20]; + if (msg->has_to && ethereum_parse(msg->to, pubkeyhash)) { + toset = true; + } else { + msg->to[0] = 0; + toset = false; + memzero(pubkeyhash, sizeof(pubkeyhash)); + } + if (!msg->has_nonce) msg->nonce.size = 0; + + /* eip-155 chain id */ + if (msg->has_chain_id) { + if (msg->chain_id < 1) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Chain Id out of bounds")); + ethereum_signing_abort(); + return; + } + chain_id = msg->chain_id; + } else { + chain_id = 0; + } + + /* Wanchain txtype */ + if (msg->has_tx_type) { + if (msg->tx_type == 1 || msg->tx_type == 6) { + tx_type = msg->tx_type; + } else { + fsm_sendFailure(FailureType_Failure_DataError, _("Txtype out of bounds")); + ethereum_signing_abort(); + return; + } + } else { + tx_type = 0; + } + + if (msg->has_data_length && msg->data_length > 0) { + if (!msg->has_data_initial_chunk || msg->data_initial_chunk.size == 0) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Data length provided, but no initial chunk")); + ethereum_signing_abort(); + return; + } + /* Our encoding only supports transactions up to 2^24 bytes. To + * prevent exceeding the limit we use a stricter limit on data length. + */ + if (msg->data_length > 16000000) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Data length exceeds limit")); + ethereum_signing_abort(); + return; + } + data_total = msg->data_length; + } else { + data_total = 0; + } + if (msg->data_initial_chunk.size > data_total) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Invalid size of initial chunk")); + ethereum_signing_abort(); + return; + } + + // safety checks + if (!ethereum_signing_check(msg)) { + fsm_sendFailure(FailureType_Failure_DataError, _("Safety check failed")); + ethereum_signing_abort(); + return; + } + + const TokenType *token = NULL; + + // detect ERC-20 token + if (toset && msg->value.size == 0 && data_total == 68 && + msg->data_initial_chunk.size == 68 && + memcmp(msg->data_initial_chunk.bytes, + "\xa9\x05\x9c\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + 16) == 0) { + token = tokenByChainAddress(chain_id, pubkeyhash); + } + + if (token != NULL) { + layoutEthereumConfirmTx(msg->data_initial_chunk.bytes + 16, 20, + msg->data_initial_chunk.bytes + 36, 32, token); + } else { + layoutEthereumConfirmTx(pubkeyhash, 20, msg->value.bytes, msg->value.size, + NULL); + } + + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + ethereum_signing_abort(); + return; + } + + if (token == NULL && data_total > 0) { + layoutEthereumData(msg->data_initial_chunk.bytes, + msg->data_initial_chunk.size, data_total); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + ethereum_signing_abort(); + return; + } + } + + layoutEthereumFee(msg->value.bytes, msg->value.size, msg->gas_price.bytes, + msg->gas_price.size, msg->gas_limit.bytes, + msg->gas_limit.size, token != NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + ethereum_signing_abort(); + return; + } + + /* Stage 1: Calculate total RLP length */ + uint32_t rlp_length = 0; + + layoutProgress(_("Signing"), 0); + + rlp_length += rlp_calculate_length(msg->nonce.size, msg->nonce.bytes[0]); + rlp_length += + rlp_calculate_length(msg->gas_price.size, msg->gas_price.bytes[0]); + rlp_length += + rlp_calculate_length(msg->gas_limit.size, msg->gas_limit.bytes[0]); + rlp_length += rlp_calculate_length(toset ? 20 : 0, pubkeyhash[0]); + rlp_length += rlp_calculate_length(msg->value.size, msg->value.bytes[0]); + rlp_length += + rlp_calculate_length(data_total, msg->data_initial_chunk.bytes[0]); + if (tx_type) { + rlp_length += rlp_calculate_number_length(tx_type); + } + if (chain_id) { + rlp_length += rlp_calculate_number_length(chain_id); + rlp_length += rlp_calculate_length(0, 0); + rlp_length += rlp_calculate_length(0, 0); + } + + /* Stage 2: Store header fields */ + hash_rlp_list_length(rlp_length); + + layoutProgress(_("Signing"), 100); + + if (tx_type) { + hash_rlp_number(tx_type); + } + hash_rlp_field(msg->nonce.bytes, msg->nonce.size); + hash_rlp_field(msg->gas_price.bytes, msg->gas_price.size); + hash_rlp_field(msg->gas_limit.bytes, msg->gas_limit.size); + hash_rlp_field(pubkeyhash, toset ? 20 : 0); + hash_rlp_field(msg->value.bytes, msg->value.size); + hash_rlp_length(data_total, msg->data_initial_chunk.bytes[0]); + hash_data(msg->data_initial_chunk.bytes, msg->data_initial_chunk.size); + data_left = data_total - msg->data_initial_chunk.size; + + memcpy(privkey, node->private_key, 32); + + if (data_left > 0) { + send_request_chunk(); + } else { + send_signature(); + } +} + +void ethereum_signing_txack(const EthereumTxAck *tx) { + if (!ethereum_signing) { + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, + _("Not in Ethereum signing mode")); + layoutHome(); + return; + } + + if (tx->data_chunk.size > data_left) { + fsm_sendFailure(FailureType_Failure_DataError, _("Too much data")); + ethereum_signing_abort(); + return; + } + + if (data_left > 0 && (!tx->has_data_chunk || tx->data_chunk.size == 0)) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Empty data chunk received")); + ethereum_signing_abort(); + return; + } + + hash_data(tx->data_chunk.bytes, tx->data_chunk.size); + + data_left -= tx->data_chunk.size; + + if (data_left > 0) { + send_request_chunk(); + } else { + send_signature(); + } +} + +void ethereum_signing_abort(void) { + if (ethereum_signing) { + memzero(privkey, sizeof(privkey)); + layoutHome(); + ethereum_signing = false; + } +} + +static void ethereum_message_hash(const uint8_t *message, size_t message_len, + uint8_t hash[32]) { + struct SHA3_CTX ctx; + sha3_256_Init(&ctx); + sha3_Update(&ctx, (const uint8_t *)"\x19" "Ethereum Signed Message:\n", 26); + uint8_t c; + if (message_len > 1000000000) { + c = '0' + message_len / 1000000000 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 100000000) { + c = '0' + message_len / 100000000 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 10000000) { + c = '0' + message_len / 10000000 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 1000000) { + c = '0' + message_len / 1000000 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 100000) { + c = '0' + message_len / 100000 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 10000) { + c = '0' + message_len / 10000 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 1000) { + c = '0' + message_len / 1000 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 100) { + c = '0' + message_len / 100 % 10; + sha3_Update(&ctx, &c, 1); + } + if (message_len > 10) { + c = '0' + message_len / 10 % 10; + sha3_Update(&ctx, &c, 1); + } + c = '0' + message_len % 10; + sha3_Update(&ctx, &c, 1); + sha3_Update(&ctx, message, message_len); + keccak_Final(&ctx, hash); +} + +void ethereum_message_sign(const EthereumSignMessage *msg, const HDNode *node, + EthereumMessageSignature *resp) { + uint8_t pubkeyhash[20]; + if (!hdnode_get_ethereum_pubkeyhash(node, pubkeyhash)) { + return; + } + + resp->has_address = true; + resp->address[0] = '0'; + resp->address[1] = 'x'; + ethereum_address_checksum(pubkeyhash, resp->address + 2, false, 0); + // ethereum_address_checksum adds trailing zero + + uint8_t hash[32]; + ethereum_message_hash(msg->message.bytes, msg->message.size, hash); + + uint8_t v; + if (ecdsa_sign_digest(&secp256k1, node->private_key, hash, + resp->signature.bytes, &v, ethereum_is_canonic) != 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed")); + return; + } + + resp->has_signature = true; + resp->signature.bytes[64] = 27 + v; + resp->signature.size = 65; + msg_write(MessageType_MessageType_EthereumMessageSignature, resp); +} + +int ethereum_message_verify(const EthereumVerifyMessage *msg) { + if (msg->signature.size != 65) { + fsm_sendFailure(FailureType_Failure_DataError, _("Malformed signature")); + return 1; + } + + uint8_t pubkeyhash[20]; + if (!ethereum_parse(msg->address, pubkeyhash)) { + fsm_sendFailure(FailureType_Failure_DataError, _("Malformed address")); + return 1; + } + + uint8_t pubkey[65]; + uint8_t hash[32]; + + ethereum_message_hash(msg->message.bytes, msg->message.size, hash); + + /* v should be 27, 28 but some implementations use 0,1. We are + * compatible with both. + */ + uint8_t v = msg->signature.bytes[64]; + if (v >= 27) { + v -= 27; + } + if (v >= 2 || ecdsa_recover_pub_from_sig( + &secp256k1, pubkey, msg->signature.bytes, hash, v) != 0) { + return 2; + } + + struct SHA3_CTX ctx; + sha3_256_Init(&ctx); + sha3_Update(&ctx, pubkey + 1, 64); + keccak_Final(&ctx, hash); + + /* result are the least significant 160 bits */ + if (memcmp(pubkeyhash, hash + 12, 20) != 0) { + return 2; + } + return 0; +} + +bool ethereum_parse(const char *address, uint8_t pubkeyhash[20]) { + memzero(pubkeyhash, 20); + size_t len = strlen(address); + if (len == 40) { + // do nothing + } else if (len == 42) { + // check for "0x" prefix and strip it when required + if (address[0] != '0') return false; + if (address[1] != 'x' && address[1] != 'X') return false; + address += 2; + len -= 2; + } else { + return false; + } + for (size_t i = 0; i < len; i++) { + if (address[i] >= '0' && address[i] <= '9') { + pubkeyhash[i / 2] |= (address[i] - '0') << ((1 - (i % 2)) * 4); + } else if (address[i] >= 'a' && address[i] <= 'f') { + pubkeyhash[i / 2] |= ((address[i] - 'a') + 10) << ((1 - (i % 2)) * 4); + } else if (address[i] >= 'A' && address[i] <= 'F') { + pubkeyhash[i / 2] |= ((address[i] - 'A') + 10) << ((1 - (i % 2)) * 4); + } else { + return false; + } + } + return true; +} diff --git a/legacy/firmware/ethereum.h b/legacy/firmware/ethereum.h new file mode 100644 index 0000000000..3e8ad02f68 --- /dev/null +++ b/legacy/firmware/ethereum.h @@ -0,0 +1,37 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2016 Alex Beregszaszi + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __ETHEREUM_H__ +#define __ETHEREUM_H__ + +#include +#include +#include "bip32.h" +#include "messages-ethereum.pb.h" + +void ethereum_signing_init(EthereumSignTx *msg, const HDNode *node); +void ethereum_signing_abort(void); +void ethereum_signing_txack(const EthereumTxAck *msg); + +void ethereum_message_sign(const EthereumSignMessage *msg, const HDNode *node, + EthereumMessageSignature *resp); +int ethereum_message_verify(const EthereumVerifyMessage *msg); +bool ethereum_parse(const char *address, uint8_t pubkeyhash[20]); + +#endif diff --git a/legacy/firmware/ethereum_networks.h.mako b/legacy/firmware/ethereum_networks.h.mako new file mode 100644 index 0000000000..6416aba398 --- /dev/null +++ b/legacy/firmware/ethereum_networks.h.mako @@ -0,0 +1,34 @@ +<% +BKSL = "\\" + +networks = list(supported_on("trezor1", eth)) +max_chain_id_length = 0 +max_suffix_length = 0 +for n in networks: + max_chain_id_length = max(len(str(n.chain_id)), max_chain_id_length) + max_suffix_length = max(len(n.shortcut), max_suffix_length) + +def align_chain_id(n): + return "{:>{w}}".format(n.chain_id, w=max_chain_id_length) + +def align_suffix(n): + cstr = c_str(" " + n.shortcut) + ";" + # we add two quotes, a space and a semicolon. hence +4 chars + return "{:<{w}}".format(cstr, w=max_suffix_length + 4) + +%>\ +// This file is automatically generated from ethereum_networks.h.mako +// DO NOT EDIT + +#ifndef __ETHEREUM_NETWORKS_H__ +#define __ETHEREUM_NETWORKS_H__ + +#define ASSIGN_ETHEREUM_SUFFIX(suffix, chain_id) ${BKSL} + switch (chain_id) { ${BKSL} +% for n in networks: + case ${align_chain_id(n)}: suffix = ${align_suffix(n)} break; /* ${n.name} */ ${BKSL} +% endfor + default: suffix = " UNKN"; break; /* unknown chain */ ${BKSL} + } + +#endif diff --git a/legacy/firmware/ethereum_tokens.c.mako b/legacy/firmware/ethereum_tokens.c.mako new file mode 100644 index 0000000000..2734c6a430 --- /dev/null +++ b/legacy/firmware/ethereum_tokens.c.mako @@ -0,0 +1,24 @@ +// This file is automatically generated from ethereum_tokens.c.mako +// DO NOT EDIT + +#include +#include "ethereum_tokens.h" + +const TokenType tokens[TOKENS_COUNT] = { +% for t in supported_on("trezor1", erc20): + {${"{:>2}".format(t.chain_id)}, ${c_str(t.address_bytes)}, " ${ascii(t.symbol)}", ${t.decimals}}, // ${t.chain} / ${t.name} +% endfor +}; + +const TokenType *UnknownToken = (const TokenType *)1; + +const TokenType *tokenByChainAddress(uint32_t chain_id, const uint8_t *address) +{ + if (!address) return 0; + for (int i = 0; i < TOKENS_COUNT; i++) { + if (chain_id == tokens[i].chain_id && memcmp(address, tokens[i].address, 20) == 0) { + return &(tokens[i]); + } + } + return UnknownToken; +} diff --git a/legacy/firmware/ethereum_tokens.h.mako b/legacy/firmware/ethereum_tokens.h.mako new file mode 100644 index 0000000000..12008d7efd --- /dev/null +++ b/legacy/firmware/ethereum_tokens.h.mako @@ -0,0 +1,25 @@ +// This file is automatically generated from ethereum_tokens.h.mako +// DO NOT EDIT + +#ifndef __ETHEREUM_TOKENS_H__ +#define __ETHEREUM_TOKENS_H__ + +#include + +<% erc20_list = list(supported_on("trezor1", erc20)) %>\ +#define TOKENS_COUNT ${len(erc20_list)} + +typedef struct { + uint32_t chain_id; + const char * const address; + const char * const ticker; + int decimals; +} TokenType; + +extern const TokenType tokens[TOKENS_COUNT]; + +extern const TokenType *UnknownToken; + +const TokenType *tokenByChainAddress(uint32_t chain_id, const uint8_t *address); + +#endif diff --git a/legacy/firmware/fsm.c b/legacy/firmware/fsm.c new file mode 100644 index 0000000000..59f93774f3 --- /dev/null +++ b/legacy/firmware/fsm.c @@ -0,0 +1,260 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "address.h" +#include "aes/aes.h" +#include "base58.h" +#include "bip32.h" +#include "bip39.h" +#include "coins.h" +#include "config.h" +#include "crypto.h" +#include "curves.h" +#include "debug.h" +#include "ecdsa.h" +#include "ethereum.h" +#include "fsm.h" +#include "gettext.h" +#include "hmac.h" +#include "layout2.h" +#include "lisk.h" +#include "memory.h" +#include "memzero.h" +#include "messages.h" +#include "messages.pb.h" +#include "nem.h" +#include "nem2.h" +#include "oled.h" +#include "pinmatrix.h" +#include "protect.h" +#include "recovery.h" +#include "reset.h" +#include "rfc6979.h" +#include "rng.h" +#include "secp256k1.h" +#include "signing.h" +#include "stellar.h" +#include "supervise.h" +#include "transaction.h" +#include "trezor.h" +#include "usb.h" +#include "util.h" + +// message methods + +static uint8_t msg_resp[MSG_OUT_SIZE] __attribute__((aligned)); + +#define RESP_INIT(TYPE) \ + TYPE *resp = (TYPE *)(void *)msg_resp; \ + _Static_assert(sizeof(msg_resp) >= sizeof(TYPE), #TYPE " is too large"); \ + memzero(resp, sizeof(TYPE)); + +#define CHECK_INITIALIZED \ + if (!config_isInitialized()) { \ + fsm_sendFailure(FailureType_Failure_NotInitialized, NULL); \ + return; \ + } + +#define CHECK_NOT_INITIALIZED \ + if (config_isInitialized()) { \ + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, \ + _("Device is already initialized. Use Wipe first.")); \ + return; \ + } + +#define CHECK_PIN \ + if (!protectPin(true)) { \ + layoutHome(); \ + return; \ + } + +#define CHECK_PIN_UNCACHED \ + if (!protectPin(false)) { \ + layoutHome(); \ + return; \ + } + +#define CHECK_PARAM(cond, errormsg) \ + if (!(cond)) { \ + fsm_sendFailure(FailureType_Failure_DataError, (errormsg)); \ + layoutHome(); \ + return; \ + } + +void fsm_sendSuccess(const char *text) { + RESP_INIT(Success); + if (text) { + resp->has_message = true; + strlcpy(resp->message, text, sizeof(resp->message)); + } + msg_write(MessageType_MessageType_Success, resp); +} + +#if DEBUG_LINK +void fsm_sendFailureDebug(FailureType code, const char *text, + const char *source) +#else +void fsm_sendFailure(FailureType code, const char *text) +#endif +{ + if (protectAbortedByCancel) { + protectAbortedByCancel = false; + } + if (protectAbortedByInitialize) { + fsm_msgInitialize((Initialize *)0); + protectAbortedByInitialize = false; + return; + } + RESP_INIT(Failure); + resp->has_code = true; + resp->code = code; + if (!text) { + switch (code) { + case FailureType_Failure_UnexpectedMessage: + text = _("Unexpected message"); + break; + case FailureType_Failure_ButtonExpected: + text = _("Button expected"); + break; + case FailureType_Failure_DataError: + text = _("Data error"); + break; + case FailureType_Failure_ActionCancelled: + text = _("Action cancelled by user"); + break; + case FailureType_Failure_PinExpected: + text = _("PIN expected"); + break; + case FailureType_Failure_PinCancelled: + text = _("PIN cancelled"); + break; + case FailureType_Failure_PinInvalid: + text = _("PIN invalid"); + break; + case FailureType_Failure_InvalidSignature: + text = _("Invalid signature"); + break; + case FailureType_Failure_ProcessError: + text = _("Process error"); + break; + case FailureType_Failure_NotEnoughFunds: + text = _("Not enough funds"); + break; + case FailureType_Failure_NotInitialized: + text = _("Device not initialized"); + break; + case FailureType_Failure_PinMismatch: + text = _("PIN mismatch"); + break; + case FailureType_Failure_FirmwareError: + text = _("Firmware error"); + break; + } + } +#if DEBUG_LINK + resp->has_message = true; + strlcpy(resp->message, source, sizeof(resp->message)); + if (text) { + strlcat(resp->message, text, sizeof(resp->message)); + } +#else + if (text) { + resp->has_message = true; + strlcpy(resp->message, text, sizeof(resp->message)); + } +#endif + msg_write(MessageType_MessageType_Failure, resp); +} + +static const CoinInfo *fsm_getCoin(bool has_name, const char *name) { + const CoinInfo *coin; + if (has_name) { + coin = coinByName(name); + } else { + coin = coinByName("Bitcoin"); + } + if (!coin) { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid coin name")); + layoutHome(); + return 0; + } + return coin; +} + +static HDNode *fsm_getDerivedNode(const char *curve, const uint32_t *address_n, + size_t address_n_count, + uint32_t *fingerprint) { + static CONFIDENTIAL HDNode node; + if (fingerprint) { + *fingerprint = 0; + } + if (!config_getRootNode(&node, curve, true)) { + fsm_sendFailure(FailureType_Failure_NotInitialized, + _("Device not initialized or passphrase request cancelled " + "or unsupported curve")); + layoutHome(); + return 0; + } + if (!address_n || address_n_count == 0) { + return &node; + } + if (hdnode_private_ckd_cached(&node, address_n, address_n_count, + fingerprint) == 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to derive private key")); + layoutHome(); + return 0; + } + return &node; +} + +static bool fsm_layoutAddress(const char *address, const char *desc, + bool ignorecase, size_t prefixlen, + const uint32_t *address_n, size_t address_n_count, + bool address_is_account) { + bool qrcode = false; + for (;;) { + const char *display_addr = address; + if (prefixlen && !qrcode) { + display_addr += prefixlen; + } + layoutAddress(display_addr, desc, qrcode, ignorecase, address_n, + address_n_count, address_is_account); + if (protectButton(ButtonRequestType_ButtonRequest_Address, false)) { + return true; + } + if (protectAbortedByCancel || protectAbortedByInitialize) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return false; + } + qrcode = !qrcode; + } +} + +#include "fsm_msg_coin.h" +#include "fsm_msg_common.h" +#include "fsm_msg_crypto.h" +#include "fsm_msg_debug.h" +#include "fsm_msg_ethereum.h" +#include "fsm_msg_lisk.h" +#include "fsm_msg_nem.h" +#include "fsm_msg_stellar.h" diff --git a/legacy/firmware/fsm.h b/legacy/firmware/fsm.h new file mode 100644 index 0000000000..d4cde689b7 --- /dev/null +++ b/legacy/firmware/fsm.h @@ -0,0 +1,136 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __FSM_H__ +#define __FSM_H__ + +#include "messages-bitcoin.pb.h" +#include "messages-crypto.pb.h" +#include "messages-debug.pb.h" +#include "messages-ethereum.pb.h" +#include "messages-lisk.pb.h" +#include "messages-management.pb.h" +#include "messages-nem.pb.h" +#include "messages-stellar.pb.h" + +// message functions + +void fsm_sendSuccess(const char *text); + +#if DEBUG_LINK +void fsm_sendFailureDebug(FailureType code, const char *text, + const char *source); + +#define fsm_sendFailure(code, text) \ + fsm_sendFailureDebug((code), (text), __FILE__ ":" VERSTR(__LINE__) ":") +#else +void fsm_sendFailure(FailureType code, const char *text); +#endif + +// void fsm_msgPinMatrixAck(const PinMatrixAck *msg); // tiny +// void fsm_msgButtonAck(const ButtonAck *msg); // tiny +// void fsm_msgPassphraseAck(const PassphraseAck *msg); // tiny + +// common +void fsm_msgInitialize(const Initialize *msg); +void fsm_msgGetFeatures(const GetFeatures *msg); +void fsm_msgPing(const Ping *msg); +void fsm_msgChangePin(const ChangePin *msg); +void fsm_msgWipeDevice(const WipeDevice *msg); +void fsm_msgGetEntropy(const GetEntropy *msg); +void fsm_msgLoadDevice(const LoadDevice *msg); +void fsm_msgResetDevice(const ResetDevice *msg); +void fsm_msgEntropyAck(const EntropyAck *msg); +void fsm_msgBackupDevice(const BackupDevice *msg); +void fsm_msgCancel(const Cancel *msg); +void fsm_msgClearSession(const ClearSession *msg); +void fsm_msgApplySettings(const ApplySettings *msg); +void fsm_msgApplyFlags(const ApplyFlags *msg); +void fsm_msgRecoveryDevice(const RecoveryDevice *msg); +void fsm_msgWordAck(const WordAck *msg); +void fsm_msgSetU2FCounter(const SetU2FCounter *msg); + +// coin +void fsm_msgGetPublicKey(const GetPublicKey *msg); +void fsm_msgSignTx(const SignTx *msg); +void fsm_msgTxAck( + TxAck *msg); // not const because we mutate input/output scripts +void fsm_msgGetAddress(const GetAddress *msg); +void fsm_msgSignMessage(const SignMessage *msg); +void fsm_msgVerifyMessage(const VerifyMessage *msg); + +// crypto +void fsm_msgCipherKeyValue(const CipherKeyValue *msg); +void fsm_msgSignIdentity(const SignIdentity *msg); +void fsm_msgGetECDHSessionKey(const GetECDHSessionKey *msg); +void fsm_msgCosiCommit(const CosiCommit *msg); +void fsm_msgCosiSign(const CosiSign *msg); + +// debug +#if DEBUG_LINK +// void fsm_msgDebugLinkDecision(const DebugLinkDecision *msg); // tiny +void fsm_msgDebugLinkGetState(const DebugLinkGetState *msg); +void fsm_msgDebugLinkStop(const DebugLinkStop *msg); +void fsm_msgDebugLinkMemoryWrite(const DebugLinkMemoryWrite *msg); +void fsm_msgDebugLinkMemoryRead(const DebugLinkMemoryRead *msg); +void fsm_msgDebugLinkFlashErase(const DebugLinkFlashErase *msg); +#endif + +// ethereum +void fsm_msgEthereumGetAddress(const EthereumGetAddress *msg); +void fsm_msgEthereumGetPublicKey(const EthereumGetPublicKey *msg); +void fsm_msgEthereumSignTx( + EthereumSignTx + *msg); // not const because we mutate transaction during validation +void fsm_msgEthereumTxAck(const EthereumTxAck *msg); +void fsm_msgEthereumSignMessage(const EthereumSignMessage *msg); +void fsm_msgEthereumVerifyMessage(const EthereumVerifyMessage *msg); + +// lisk +void fsm_msgLiskGetAddress(const LiskGetAddress *msg); +void fsm_msgLiskGetPublicKey(const LiskGetPublicKey *msg); +void fsm_msgLiskSignMessage(const LiskSignMessage *msg); +void fsm_msgLiskVerifyMessage(const LiskVerifyMessage *msg); +void fsm_msgLiskSignTx(LiskSignTx *msg); // not const because we mutate + // transaction during validation + +// nem +void fsm_msgNEMGetAddress( + NEMGetAddress *msg); // not const because we mutate msg->network +void fsm_msgNEMSignTx( + NEMSignTx *msg); // not const because we mutate msg->network +void fsm_msgNEMDecryptMessage( + NEMDecryptMessage *msg); // not const because we mutate msg->payload + +// stellar +void fsm_msgStellarGetAddress(const StellarGetAddress *msg); +void fsm_msgStellarSignTx(const StellarSignTx *msg); +void fsm_msgStellarPaymentOp(const StellarPaymentOp *msg); +void fsm_msgStellarCreateAccountOp(const StellarCreateAccountOp *msg); +void fsm_msgStellarPathPaymentOp(const StellarPathPaymentOp *msg); +void fsm_msgStellarManageOfferOp(const StellarManageOfferOp *msg); +void fsm_msgStellarCreatePassiveOfferOp(const StellarCreatePassiveOfferOp *msg); +void fsm_msgStellarSetOptionsOp(const StellarSetOptionsOp *msg); +void fsm_msgStellarChangeTrustOp(const StellarChangeTrustOp *msg); +void fsm_msgStellarAllowTrustOp(const StellarAllowTrustOp *msg); +void fsm_msgStellarAccountMergeOp(const StellarAccountMergeOp *msg); +void fsm_msgStellarManageDataOp(const StellarManageDataOp *msg); +void fsm_msgStellarBumpSequenceOp(const StellarBumpSequenceOp *msg); + +#endif diff --git a/legacy/firmware/fsm_msg_coin.h b/legacy/firmware/fsm_msg_coin.h new file mode 100644 index 0000000000..30b5e1747e --- /dev/null +++ b/legacy/firmware/fsm_msg_coin.h @@ -0,0 +1,329 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgGetPublicKey(const GetPublicKey *msg) { + RESP_INIT(PublicKey); + + CHECK_INITIALIZED + + CHECK_PIN + + InputScriptType script_type = + msg->has_script_type ? msg->script_type : InputScriptType_SPENDADDRESS; + + const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name); + if (!coin) return; + + const char *curve = coin->curve_name; + if (msg->has_ecdsa_curve_name) { + curve = msg->ecdsa_curve_name; + } + uint32_t fingerprint; + HDNode *node = node = fsm_getDerivedNode(curve, msg->address_n, + msg->address_n_count, &fingerprint); + if (!node) return; + hdnode_fill_public_key(node); + + if (msg->has_show_display && msg->show_display) { + layoutPublicKey(node->public_key); + if (!protectButton(ButtonRequestType_ButtonRequest_PublicKey, true)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + resp->has_node = true; + resp->node.depth = node->depth; + resp->node.fingerprint = fingerprint; + resp->node.child_num = node->child_num; + resp->node.chain_code.size = 32; + memcpy(resp->node.chain_code.bytes, node->chain_code, 32); + resp->node.has_private_key = false; + resp->node.has_public_key = true; + resp->node.public_key.size = 33; + memcpy(resp->node.public_key.bytes, node->public_key, 33); + if (node->public_key[0] == 1) { + /* ed25519 public key */ + resp->node.public_key.bytes[0] = 0; + } + + resp->has_xpub = true; + if (coin->xpub_magic && (script_type == InputScriptType_SPENDADDRESS || + script_type == InputScriptType_SPENDMULTISIG)) { + hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub, + sizeof(resp->xpub)); + } else if (coin->has_segwit && coin->xpub_magic_segwit_p2sh && + script_type == InputScriptType_SPENDP2SHWITNESS) { + hdnode_serialize_public(node, fingerprint, coin->xpub_magic_segwit_p2sh, + resp->xpub, sizeof(resp->xpub)); + } else if (coin->has_segwit && coin->xpub_magic_segwit_native && + script_type == InputScriptType_SPENDWITNESS) { + hdnode_serialize_public(node, fingerprint, coin->xpub_magic_segwit_native, + resp->xpub, sizeof(resp->xpub)); + } else { + fsm_sendFailure(FailureType_Failure_DataError, + _("Invalid combination of coin and script_type")); + layoutHome(); + return; + } + + msg_write(MessageType_MessageType_PublicKey, resp); + layoutHome(); +} + +void fsm_msgSignTx(const SignTx *msg) { + CHECK_INITIALIZED + + CHECK_PARAM(msg->inputs_count > 0, + _("Transaction must have at least one input")); + CHECK_PARAM(msg->outputs_count > 0, + _("Transaction must have at least one output")); + CHECK_PARAM(msg->inputs_count + msg->outputs_count >= msg->inputs_count, + _("Value overflow")); + + CHECK_PIN + + const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name); + if (!coin) return; + const HDNode *node = fsm_getDerivedNode(coin->curve_name, NULL, 0, NULL); + if (!node) return; + + signing_init(msg, coin, node); +} + +void fsm_msgTxAck(TxAck *msg) { + CHECK_PARAM(msg->has_tx, _("No transaction provided")); + + signing_txack(&(msg->tx)); +} + +static bool path_mismatched(const CoinInfo *coin, const GetAddress *msg) { + bool mismatch = false; + + // m : no path + if (msg->address_n_count == 0) { + return false; + } + + // m/44' : BIP44 Legacy + // m / purpose' / coin_type' / account' / change / address_index + if (msg->address_n[0] == (0x80000000 + 44)) { + mismatch |= (msg->script_type != InputScriptType_SPENDADDRESS); + mismatch |= (msg->address_n_count != 5); + mismatch |= (msg->address_n[1] != coin->coin_type); + mismatch |= (msg->address_n[2] & 0x80000000) == 0; + mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000; + mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000; + return mismatch; + } + + // m/45' - BIP45 Copay Abandoned Multisig P2SH + // m / purpose' / cosigner_index / change / address_index + if (msg->address_n[0] == (0x80000000 + 45)) { + mismatch |= (msg->script_type != InputScriptType_SPENDMULTISIG); + mismatch |= (msg->address_n_count != 4); + mismatch |= (msg->address_n[1] & 0x80000000) == 0x80000000; + mismatch |= (msg->address_n[2] & 0x80000000) == 0x80000000; + mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000; + return mismatch; + } + + // m/48' - BIP48 Copay Multisig P2SH + // m / purpose' / coin_type' / account' / change / address_index + if (msg->address_n[0] == (0x80000000 + 48)) { + mismatch |= (msg->script_type != InputScriptType_SPENDMULTISIG); + mismatch |= (msg->address_n_count != 5); + mismatch |= (msg->address_n[1] != coin->coin_type); + mismatch |= (msg->address_n[2] & 0x80000000) == 0; + mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000; + mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000; + return mismatch; + } + + // m/49' : BIP49 SegWit + // m / purpose' / coin_type' / account' / change / address_index + if (msg->address_n[0] == (0x80000000 + 49)) { + mismatch |= (msg->script_type != InputScriptType_SPENDP2SHWITNESS); + mismatch |= !coin->has_segwit; + mismatch |= !coin->has_address_type_p2sh; + mismatch |= (msg->address_n_count != 5); + mismatch |= (msg->address_n[1] != coin->coin_type); + mismatch |= (msg->address_n[2] & 0x80000000) == 0; + mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000; + mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000; + return mismatch; + } + + // m/84' : BIP84 Native SegWit + // m / purpose' / coin_type' / account' / change / address_index + if (msg->address_n[0] == (0x80000000 + 84)) { + mismatch |= (msg->script_type != InputScriptType_SPENDWITNESS); + mismatch |= !coin->has_segwit; + mismatch |= !coin->bech32_prefix; + mismatch |= (msg->address_n_count != 5); + mismatch |= (msg->address_n[1] != coin->coin_type); + mismatch |= (msg->address_n[2] & 0x80000000) == 0; + mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000; + mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000; + return mismatch; + } + + return false; +} + +void fsm_msgGetAddress(const GetAddress *msg) { + RESP_INIT(Address); + + CHECK_INITIALIZED + + CHECK_PIN + + const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name); + if (!coin) return; + HDNode *node = fsm_getDerivedNode(coin->curve_name, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + char address[MAX_ADDR_SIZE]; + if (msg->has_multisig) { // use progress bar only for multisig + layoutProgress(_("Computing address"), 0); + } + if (!compute_address(coin, msg->script_type, node, msg->has_multisig, + &msg->multisig, address)) { + fsm_sendFailure(FailureType_Failure_DataError, _("Can't encode address")); + layoutHome(); + return; + } + + if (msg->has_show_display && msg->show_display) { + char desc[20]; + if (msg->has_multisig) { + strlcpy(desc, "Multisig __ of __:", sizeof(desc)); + const uint32_t m = msg->multisig.m; + const uint32_t n = msg->multisig.pubkeys_count; + desc[9] = (m < 10) ? ' ' : ('0' + (m / 10)); + desc[10] = '0' + (m % 10); + desc[15] = (n < 10) ? ' ' : ('0' + (n / 10)); + desc[16] = '0' + (n % 10); + } else { + strlcpy(desc, _("Address:"), sizeof(desc)); + } + + bool mismatch = path_mismatched(coin, msg); + + if (mismatch) { + layoutDialogSwipe(&bmp_icon_warning, _("Abort"), _("Continue"), NULL, + _("Wrong address path"), _("for selected coin."), NULL, + _("Continue at your"), _("own risk!"), NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + bool is_cashaddr = coin->cashaddr_prefix != NULL; + bool is_bech32 = msg->script_type == InputScriptType_SPENDWITNESS; + if (!fsm_layoutAddress(address, desc, is_cashaddr || is_bech32, + is_cashaddr ? strlen(coin->cashaddr_prefix) + 1 : 0, + msg->address_n, msg->address_n_count, false)) { + return; + } + } + + strlcpy(resp->address, address, sizeof(resp->address)); + msg_write(MessageType_MessageType_Address, resp); + layoutHome(); +} + +void fsm_msgSignMessage(const SignMessage *msg) { + // CHECK_PARAM(is_ascii_only(msg->message.bytes, msg->message.size), _("Cannot + // sign non-ASCII strings")); + + RESP_INIT(MessageSignature); + + CHECK_INITIALIZED + + layoutSignMessage(msg->message.bytes, msg->message.size); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + CHECK_PIN + + const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name); + if (!coin) return; + HDNode *node = fsm_getDerivedNode(coin->curve_name, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + layoutProgressSwipe(_("Signing"), 0); + if (cryptoMessageSign(coin, node, msg->script_type, msg->message.bytes, + msg->message.size, resp->signature.bytes) == 0) { + resp->has_address = true; + hdnode_fill_public_key(node); + if (!compute_address(coin, msg->script_type, node, false, NULL, + resp->address)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Error computing address")); + layoutHome(); + return; + } + resp->has_signature = true; + resp->signature.size = 65; + msg_write(MessageType_MessageType_MessageSignature, resp); + } else { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Error signing message")); + } + layoutHome(); +} + +void fsm_msgVerifyMessage(const VerifyMessage *msg) { + CHECK_PARAM(msg->has_address, _("No address provided")); + CHECK_PARAM(msg->has_message, _("No message provided")); + + const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name); + if (!coin) return; + layoutProgressSwipe(_("Verifying"), 0); + if (msg->signature.size == 65 && + cryptoMessageVerify(coin, msg->message.bytes, msg->message.size, + msg->address, msg->signature.bytes) == 0) { + layoutVerifyAddress(coin, msg->address); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + layoutVerifyMessage(msg->message.bytes, msg->message.size); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + fsm_sendSuccess(_("Message verified")); + } else { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid signature")); + } + layoutHome(); +} diff --git a/legacy/firmware/fsm_msg_common.h b/legacy/firmware/fsm_msg_common.h new file mode 100644 index 0000000000..265b629d40 --- /dev/null +++ b/legacy/firmware/fsm_msg_common.h @@ -0,0 +1,412 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgInitialize(const Initialize *msg) { + recovery_abort(); + signing_abort(); + if (msg && msg->has_state && msg->state.size == 64) { + uint8_t i_state[64]; + if (!session_getState(msg->state.bytes, i_state, NULL)) { + session_clear(false); // do not clear PIN + } else { + if (0 != memcmp(msg->state.bytes, i_state, 64)) { + session_clear(false); // do not clear PIN + } + } + } else { + session_clear(false); // do not clear PIN + } + layoutHome(); + fsm_msgGetFeatures(0); +} + +void fsm_msgGetFeatures(const GetFeatures *msg) { + (void)msg; + RESP_INIT(Features); + resp->has_vendor = true; + strlcpy(resp->vendor, "trezor.io", sizeof(resp->vendor)); + resp->has_major_version = true; + resp->major_version = VERSION_MAJOR; + resp->has_minor_version = true; + resp->minor_version = VERSION_MINOR; + resp->has_patch_version = true; + resp->patch_version = VERSION_PATCH; + resp->has_device_id = true; + strlcpy(resp->device_id, config_uuid_str, sizeof(resp->device_id)); + resp->has_pin_protection = true; + resp->pin_protection = config_hasPin(); + resp->has_passphrase_protection = true; + config_getPassphraseProtection(&(resp->passphrase_protection)); +#ifdef SCM_REVISION + int len = sizeof(SCM_REVISION) - 1; + resp->has_revision = true; + memcpy(resp->revision.bytes, SCM_REVISION, len); + resp->revision.size = len; +#endif + resp->has_bootloader_hash = true; + resp->bootloader_hash.size = + memory_bootloader_hash(resp->bootloader_hash.bytes); + + resp->has_language = + config_getLanguage(resp->language, sizeof(resp->language)); + resp->has_label = config_getLabel(resp->label, sizeof(resp->label)); + resp->has_initialized = true; + resp->initialized = config_isInitialized(); + resp->has_imported = config_getImported(&(resp->imported)); + resp->has_pin_cached = true; + resp->pin_cached = session_isUnlocked() && config_hasPin(); + resp->has_passphrase_cached = true; + resp->passphrase_cached = session_isPassphraseCached(); + resp->has_needs_backup = true; + config_getNeedsBackup(&(resp->needs_backup)); + resp->has_unfinished_backup = true; + config_getUnfinishedBackup(&(resp->unfinished_backup)); + resp->has_no_backup = true; + config_getNoBackup(&(resp->no_backup)); + resp->has_flags = config_getFlags(&(resp->flags)); + resp->has_model = true; + strlcpy(resp->model, "1", sizeof(resp->model)); + + msg_write(MessageType_MessageType_Features, resp); +} + +void fsm_msgPing(const Ping *msg) { + RESP_INIT(Success); + + if (msg->has_button_protection && msg->button_protection) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("answer to ping?"), NULL, + NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + if (msg->has_pin_protection && msg->pin_protection) { + CHECK_PIN + } + + if (msg->has_passphrase_protection && msg->passphrase_protection) { + if (!protectPassphrase()) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + return; + } + } + + if (msg->has_message) { + resp->has_message = true; + memcpy(&(resp->message), &(msg->message), sizeof(resp->message)); + } + msg_write(MessageType_MessageType_Success, resp); + layoutHome(); +} + +void fsm_msgChangePin(const ChangePin *msg) { + bool removal = msg->has_remove && msg->remove; + if (removal) { + if (config_hasPin()) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("remove current PIN?"), + NULL, NULL, NULL, NULL); + } else { + fsm_sendSuccess(_("PIN removed")); + return; + } + } else { + if (config_hasPin()) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("change current PIN?"), + NULL, NULL, NULL, NULL); + } else { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("set new PIN?"), NULL, + NULL, NULL, NULL); + } + } + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + if (protectChangePin(removal)) { + if (removal) { + fsm_sendSuccess(_("PIN removed")); + } else { + fsm_sendSuccess(_("PIN changed")); + } + } + + layoutHome(); +} + +void fsm_msgWipeDevice(const WipeDevice *msg) { + (void)msg; + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("wipe the device?"), NULL, + _("All data will be lost."), NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_WipeDevice, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + config_wipe(); + // the following does not work on Mac anyway :-/ Linux/Windows are fine, so it + // is not needed usbReconnect(); // force re-enumeration because of the serial + // number change + fsm_sendSuccess(_("Device wiped")); + layoutHome(); +} + +void fsm_msgGetEntropy(const GetEntropy *msg) { +#if !DEBUG_RNG + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("send entropy?"), NULL, NULL, + NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } +#endif + RESP_INIT(Entropy); + uint32_t len = msg->size; + if (len > 1024) { + len = 1024; + } + resp->entropy.size = len; + random_buffer(resp->entropy.bytes, len); + msg_write(MessageType_MessageType_Entropy, resp); + layoutHome(); +} + +void fsm_msgLoadDevice(const LoadDevice *msg) { + CHECK_PIN + + CHECK_NOT_INITIALIZED + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("I take the risk"), NULL, + _("Loading private seed"), _("is not recommended."), + _("Continue only if you"), _("know what you are"), + _("doing!"), NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + if (msg->has_mnemonic && !(msg->has_skip_checksum && msg->skip_checksum)) { + if (!mnemonic_check(msg->mnemonic)) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Mnemonic with wrong checksum provided")); + layoutHome(); + return; + } + } + + config_loadDevice(msg); + fsm_sendSuccess(_("Device loaded")); + layoutHome(); +} + +void fsm_msgResetDevice(const ResetDevice *msg) { + CHECK_PIN + + CHECK_NOT_INITIALIZED + + CHECK_PARAM(!msg->has_strength || msg->strength == 128 || + msg->strength == 192 || msg->strength == 256, + _("Invalid seed strength")); + + reset_init(msg->has_display_random && msg->display_random, + msg->has_strength ? msg->strength : 128, + msg->has_passphrase_protection && msg->passphrase_protection, + msg->has_pin_protection && msg->pin_protection, + msg->has_language ? msg->language : 0, + msg->has_label ? msg->label : 0, + msg->has_u2f_counter ? msg->u2f_counter : 0, + msg->has_skip_backup ? msg->skip_backup : false, + msg->has_no_backup ? msg->no_backup : false); +} + +void fsm_msgEntropyAck(const EntropyAck *msg) { + if (msg->has_entropy) { + reset_entropy(msg->entropy.bytes, msg->entropy.size); + } else { + reset_entropy(0, 0); + } +} + +void fsm_msgBackupDevice(const BackupDevice *msg) { + CHECK_INITIALIZED + + CHECK_PIN_UNCACHED + + (void) + msg; + char mnemonic[MAX_MNEMONIC_LEN + 1]; + if (config_getMnemonic(mnemonic, sizeof(mnemonic))) { + reset_backup(true, mnemonic); + } + memzero(mnemonic, sizeof(mnemonic)); +} + +void fsm_msgCancel(const Cancel *msg) { + (void)msg; + recovery_abort(); + signing_abort(); + ethereum_signing_abort(); + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); +} + +void fsm_msgClearSession(const ClearSession *msg) { + (void)msg; + session_clear(true); // clear PIN as well + layoutScreensaver(); + fsm_sendSuccess(_("Session cleared")); +} + +void fsm_msgApplySettings(const ApplySettings *msg) { + CHECK_PARAM(msg->has_label || msg->has_language || msg->has_use_passphrase || + msg->has_homescreen || msg->has_auto_lock_delay_ms, + _("No setting provided")); + + CHECK_PIN + + if (msg->has_label) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("change name to"), + msg->label, "?", NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + if (msg->has_language) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("change language to"), + msg->language, "?", NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + if (msg->has_use_passphrase) { + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), + msg->use_passphrase ? _("enable passphrase") : _("disable passphrase"), + _("protection?"), NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + if (msg->has_homescreen) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("change the home"), + _("screen?"), NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + if (msg->has_auto_lock_delay_ms) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("change auto-lock"), + _("delay?"), NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + if (msg->has_label) { + config_setLabel(msg->label); + } + if (msg->has_language) { + config_setLanguage(msg->language); + } + if (msg->has_use_passphrase) { + config_setPassphraseProtection(msg->use_passphrase); + } + if (msg->has_homescreen) { + config_setHomescreen(msg->homescreen.bytes, msg->homescreen.size); + } + if (msg->has_auto_lock_delay_ms) { + config_setAutoLockDelayMs(msg->auto_lock_delay_ms); + } + fsm_sendSuccess(_("Settings applied")); + layoutHome(); +} + +void fsm_msgApplyFlags(const ApplyFlags *msg) { + CHECK_PIN + + if (msg->has_flags) { + config_applyFlags(msg->flags); + } + fsm_sendSuccess(_("Flags applied")); +} + +void fsm_msgRecoveryDevice(const RecoveryDevice *msg) { + CHECK_PIN_UNCACHED + + const bool dry_run = msg->has_dry_run ? msg->dry_run : false; + if (!dry_run) { + CHECK_NOT_INITIALIZED + } + + CHECK_PARAM(!msg->has_word_count || msg->word_count == 12 || + msg->word_count == 18 || msg->word_count == 24, + _("Invalid word count")); + + recovery_init(msg->has_word_count ? msg->word_count : 12, + msg->has_passphrase_protection && msg->passphrase_protection, + msg->has_pin_protection && msg->pin_protection, + msg->has_language ? msg->language : 0, + msg->has_label ? msg->label : 0, + msg->has_enforce_wordlist && msg->enforce_wordlist, + msg->has_type ? msg->type : 0, + msg->has_u2f_counter ? msg->u2f_counter : 0, dry_run); +} + +void fsm_msgWordAck(const WordAck *msg) { recovery_word(msg->word); } + +void fsm_msgSetU2FCounter(const SetU2FCounter *msg) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you want to set"), _("the U2F counter?"), NULL, NULL, + NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + config_setU2FCounter(msg->u2f_counter); + fsm_sendSuccess(_("U2F counter set")); + layoutHome(); +} diff --git a/legacy/firmware/fsm_msg_crypto.h b/legacy/firmware/fsm_msg_crypto.h new file mode 100644 index 0000000000..ed07840171 --- /dev/null +++ b/legacy/firmware/fsm_msg_crypto.h @@ -0,0 +1,299 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgCipherKeyValue(const CipherKeyValue *msg) { + CHECK_INITIALIZED + + CHECK_PARAM(msg->has_key, _("No key provided")); + CHECK_PARAM(msg->has_value, _("No value provided")); + CHECK_PARAM(msg->value.size % 16 == 0, + _("Value length must be a multiple of 16")); + + CHECK_PIN + + const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + bool encrypt = msg->has_encrypt && msg->encrypt; + bool ask_on_encrypt = msg->has_ask_on_encrypt && msg->ask_on_encrypt; + bool ask_on_decrypt = msg->has_ask_on_decrypt && msg->ask_on_decrypt; + if ((encrypt && ask_on_encrypt) || (!encrypt && ask_on_decrypt)) { + layoutCipherKeyValue(encrypt, msg->key); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + uint8_t data[256 + 4]; + strlcpy((char *)data, msg->key, sizeof(data)); + strlcat((char *)data, ask_on_encrypt ? "E1" : "E0", sizeof(data)); + strlcat((char *)data, ask_on_decrypt ? "D1" : "D0", sizeof(data)); + + hmac_sha512(node->private_key, 32, data, strlen((char *)data), data); + + if (msg->iv.size == 16) { + // override iv if provided + memcpy(data + 32, msg->iv.bytes, 16); + } + + RESP_INIT(CipheredKeyValue); + if (encrypt) { + aes_encrypt_ctx ctx; + aes_encrypt_key256(data, &ctx); + aes_cbc_encrypt(msg->value.bytes, resp->value.bytes, msg->value.size, + data + 32, &ctx); + } else { + aes_decrypt_ctx ctx; + aes_decrypt_key256(data, &ctx); + aes_cbc_decrypt(msg->value.bytes, resp->value.bytes, msg->value.size, + data + 32, &ctx); + } + resp->has_value = true; + resp->value.size = msg->value.size; + msg_write(MessageType_MessageType_CipheredKeyValue, resp); + layoutHome(); +} + +void fsm_msgSignIdentity(const SignIdentity *msg) { + RESP_INIT(SignedIdentity); + + CHECK_INITIALIZED + + layoutSignIdentity(&(msg->identity), + msg->has_challenge_visual ? msg->challenge_visual : 0); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + CHECK_PIN + + uint8_t hash[32]; + if (!msg->has_identity || + cryptoIdentityFingerprint(&(msg->identity), hash) == 0) { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid identity")); + layoutHome(); + return; + } + + uint32_t address_n[5]; + address_n[0] = 0x80000000 | 13; + address_n[1] = 0x80000000 | hash[0] | (hash[1] << 8) | (hash[2] << 16) | + ((uint32_t)hash[3] << 24); + address_n[2] = 0x80000000 | hash[4] | (hash[5] << 8) | (hash[6] << 16) | + ((uint32_t)hash[7] << 24); + address_n[3] = 0x80000000 | hash[8] | (hash[9] << 8) | (hash[10] << 16) | + ((uint32_t)hash[11] << 24); + address_n[4] = 0x80000000 | hash[12] | (hash[13] << 8) | (hash[14] << 16) | + ((uint32_t)hash[15] << 24); + + const char *curve = SECP256K1_NAME; + if (msg->has_ecdsa_curve_name) { + curve = msg->ecdsa_curve_name; + } + HDNode *node = fsm_getDerivedNode(curve, address_n, 5, NULL); + if (!node) return; + + bool sign_ssh = + msg->identity.has_proto && (strcmp(msg->identity.proto, "ssh") == 0); + bool sign_gpg = + msg->identity.has_proto && (strcmp(msg->identity.proto, "gpg") == 0); + + int result = 0; + layoutProgressSwipe(_("Signing"), 0); + if (sign_ssh) { // SSH does not sign visual challenge + result = sshMessageSign(node, msg->challenge_hidden.bytes, + msg->challenge_hidden.size, resp->signature.bytes); + } else if (sign_gpg) { // GPG should sign a message digest + result = gpgMessageSign(node, msg->challenge_hidden.bytes, + msg->challenge_hidden.size, resp->signature.bytes); + } else { + uint8_t digest[64]; + sha256_Raw(msg->challenge_hidden.bytes, msg->challenge_hidden.size, digest); + sha256_Raw((const uint8_t *)msg->challenge_visual, + strlen(msg->challenge_visual), digest + 32); + result = cryptoMessageSign(&(coins[0]), node, InputScriptType_SPENDADDRESS, + digest, 64, resp->signature.bytes); + } + + if (result == 0) { + hdnode_fill_public_key(node); + if (strcmp(curve, SECP256K1_NAME) != 0) { + resp->has_address = false; + } else { + resp->has_address = true; + hdnode_get_address( + node, 0x00, resp->address, + sizeof(resp->address)); // hardcoded Bitcoin address type + } + resp->has_public_key = true; + resp->public_key.size = 33; + memcpy(resp->public_key.bytes, node->public_key, 33); + if (node->public_key[0] == 1) { + /* ed25519 public key */ + resp->public_key.bytes[0] = 0; + } + resp->has_signature = true; + resp->signature.size = 65; + msg_write(MessageType_MessageType_SignedIdentity, resp); + } else { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Error signing identity")); + } + layoutHome(); +} + +void fsm_msgGetECDHSessionKey(const GetECDHSessionKey *msg) { + RESP_INIT(ECDHSessionKey); + + CHECK_INITIALIZED + + layoutDecryptIdentity(&msg->identity); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + CHECK_PIN + + uint8_t hash[32]; + if (!msg->has_identity || + cryptoIdentityFingerprint(&(msg->identity), hash) == 0) { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid identity")); + layoutHome(); + return; + } + + uint32_t address_n[5]; + address_n[0] = 0x80000000 | 17; + address_n[1] = 0x80000000 | hash[0] | (hash[1] << 8) | (hash[2] << 16) | + ((uint32_t)hash[3] << 24); + address_n[2] = 0x80000000 | hash[4] | (hash[5] << 8) | (hash[6] << 16) | + ((uint32_t)hash[7] << 24); + address_n[3] = 0x80000000 | hash[8] | (hash[9] << 8) | (hash[10] << 16) | + ((uint32_t)hash[11] << 24); + address_n[4] = 0x80000000 | hash[12] | (hash[13] << 8) | (hash[14] << 16) | + ((uint32_t)hash[15] << 24); + + const char *curve = SECP256K1_NAME; + if (msg->has_ecdsa_curve_name) { + curve = msg->ecdsa_curve_name; + } + + const HDNode *node = fsm_getDerivedNode(curve, address_n, 5, NULL); + if (!node) return; + + int result_size = 0; + if (hdnode_get_shared_key(node, msg->peer_public_key.bytes, + resp->session_key.bytes, &result_size) == 0) { + resp->has_session_key = true; + resp->session_key.size = result_size; + msg_write(MessageType_MessageType_ECDHSessionKey, resp); + } else { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Error getting ECDH session key")); + } + layoutHome(); +} + +void fsm_msgCosiCommit(const CosiCommit *msg) { + RESP_INIT(CosiCommitment); + + CHECK_INITIALIZED + + CHECK_PARAM(msg->has_data, _("No data provided")); + + layoutCosiCommitSign(msg->address_n, msg->address_n_count, msg->data.bytes, + msg->data.size, false); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + CHECK_PIN + + const HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + uint8_t nonce[32]; + sha256_Raw(msg->data.bytes, msg->data.size, nonce); + rfc6979_state rng; + init_rfc6979(node->private_key, nonce, &rng); + generate_rfc6979(nonce, &rng); + + resp->has_commitment = true; + resp->has_pubkey = true; + resp->commitment.size = 32; + resp->pubkey.size = 32; + + ed25519_publickey(nonce, resp->commitment.bytes); + ed25519_publickey(node->private_key, resp->pubkey.bytes); + + msg_write(MessageType_MessageType_CosiCommitment, resp); + layoutHome(); +} + +void fsm_msgCosiSign(const CosiSign *msg) { + RESP_INIT(CosiSignature); + + CHECK_INITIALIZED + + CHECK_PARAM(msg->has_data, _("No data provided")); + CHECK_PARAM(msg->has_global_commitment && msg->global_commitment.size == 32, + _("Invalid global commitment")); + CHECK_PARAM(msg->has_global_pubkey && msg->global_pubkey.size == 32, + _("Invalid global pubkey")); + + layoutCosiCommitSign(msg->address_n, msg->address_n_count, msg->data.bytes, + msg->data.size, true); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + CHECK_PIN + + const HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + uint8_t nonce[32]; + sha256_Raw(msg->data.bytes, msg->data.size, nonce); + rfc6979_state rng; + init_rfc6979(node->private_key, nonce, &rng); + generate_rfc6979(nonce, &rng); + + resp->has_signature = true; + resp->signature.size = 32; + + ed25519_cosi_sign(msg->data.bytes, msg->data.size, node->private_key, nonce, + msg->global_commitment.bytes, msg->global_pubkey.bytes, + resp->signature.bytes); + + msg_write(MessageType_MessageType_CosiSignature, resp); + layoutHome(); +} diff --git a/legacy/firmware/fsm_msg_debug.h b/legacy/firmware/fsm_msg_debug.h new file mode 100644 index 0000000000..ff835b761b --- /dev/null +++ b/legacy/firmware/fsm_msg_debug.h @@ -0,0 +1,103 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#if DEBUG_LINK + +void fsm_msgDebugLinkGetState(const DebugLinkGetState *msg) { + (void)msg; + + // Do not use RESP_INIT because it clears msg_resp, but another message + // might be being handled + DebugLinkState resp; + memzero(&resp, sizeof(resp)); + + resp.has_layout = true; + resp.layout.size = OLED_BUFSIZE; + memcpy(resp.layout.bytes, oledGetBuffer(), OLED_BUFSIZE); + + resp.has_pin = config_getPin(resp.pin, sizeof(resp.pin)); + + resp.has_matrix = true; + strlcpy(resp.matrix, pinmatrix_get(), sizeof(resp.matrix)); + + resp.has_reset_entropy = true; + resp.reset_entropy.size = reset_get_int_entropy(resp.reset_entropy.bytes); + + resp.has_reset_word = true; + strlcpy(resp.reset_word, reset_get_word(), sizeof(resp.reset_word)); + + resp.has_recovery_fake_word = true; + strlcpy(resp.recovery_fake_word, recovery_get_fake_word(), + sizeof(resp.recovery_fake_word)); + + resp.has_recovery_word_pos = true; + resp.recovery_word_pos = recovery_get_word_pos(); + + resp.has_mnemonic_secret = config_getMnemonicBytes( + resp.mnemonic_secret.bytes, sizeof(resp.mnemonic_secret.bytes), + &resp.mnemonic_secret.size); + resp.mnemonic_type = 0; // BIP-39 + + resp.has_node = config_dumpNode(&(resp.node)); + + resp.has_passphrase_protection = + config_getPassphraseProtection(&(resp.passphrase_protection)); + + msg_debug_write(MessageType_MessageType_DebugLinkState, &resp); +} + +void fsm_msgDebugLinkStop(const DebugLinkStop *msg) { (void)msg; } + +void fsm_msgDebugLinkMemoryRead(const DebugLinkMemoryRead *msg) { + RESP_INIT(DebugLinkMemory); + + uint32_t length = 1024; + if (msg->has_length && msg->length < length) length = msg->length; + resp->has_memory = true; + memcpy(resp->memory.bytes, FLASH_PTR(msg->address), length); + resp->memory.size = length; + msg_debug_write(MessageType_MessageType_DebugLinkMemory, resp); +} + +void fsm_msgDebugLinkMemoryWrite(const DebugLinkMemoryWrite *msg) { + uint32_t length = msg->memory.size; + if (msg->flash) { + svc_flash_unlock(); + svc_flash_program(FLASH_CR_PROGRAM_X32); + for (uint32_t i = 0; i < length; i += 4) { + uint32_t word; + memcpy(&word, msg->memory.bytes + i, 4); + flash_write32(msg->address + i, word); + } + uint32_t dummy = svc_flash_lock(); + (void)dummy; + } else { +#if !EMULATOR + memcpy((void *)msg->address, msg->memory.bytes, length); +#endif + } +} + +void fsm_msgDebugLinkFlashErase(const DebugLinkFlashErase *msg) { + svc_flash_unlock(); + svc_flash_erase_sector(msg->sector); + uint32_t dummy = svc_flash_lock(); + (void)dummy; +} +#endif diff --git a/legacy/firmware/fsm_msg_ethereum.h b/legacy/firmware/fsm_msg_ethereum.h new file mode 100644 index 0000000000..d5047623ea --- /dev/null +++ b/legacy/firmware/fsm_msg_ethereum.h @@ -0,0 +1,185 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgEthereumGetPublicKey(const EthereumGetPublicKey *msg) { + RESP_INIT(EthereumPublicKey); + + CHECK_INITIALIZED + + CHECK_PIN + + // we use Bitcoin-like format for ETH + const CoinInfo *coin = fsm_getCoin(true, "Bitcoin"); + if (!coin) return; + + const char *curve = coin->curve_name; + uint32_t fingerprint; + HDNode *node = node = fsm_getDerivedNode(curve, msg->address_n, + msg->address_n_count, &fingerprint); + if (!node) return; + hdnode_fill_public_key(node); + + if (msg->has_show_display && msg->show_display) { + layoutPublicKey(node->public_key); + if (!protectButton(ButtonRequestType_ButtonRequest_PublicKey, true)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + resp->has_node = true; + resp->node.depth = node->depth; + resp->node.fingerprint = fingerprint; + resp->node.child_num = node->child_num; + resp->node.chain_code.size = 32; + memcpy(resp->node.chain_code.bytes, node->chain_code, 32); + resp->node.has_private_key = false; + resp->node.has_public_key = true; + resp->node.public_key.size = 33; + memcpy(resp->node.public_key.bytes, node->public_key, 33); + + resp->has_xpub = true; + hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub, + sizeof(resp->xpub)); + + msg_write(MessageType_MessageType_EthereumPublicKey, resp); + layoutHome(); +} + +void fsm_msgEthereumSignTx(EthereumSignTx *msg) { + CHECK_INITIALIZED + + CHECK_PIN + + const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + ethereum_signing_init(msg, node); +} + +void fsm_msgEthereumTxAck(const EthereumTxAck *msg) { + ethereum_signing_txack(msg); +} + +void fsm_msgEthereumGetAddress(const EthereumGetAddress *msg) { + RESP_INIT(EthereumAddress); + + CHECK_INITIALIZED + + CHECK_PIN + + const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + uint8_t pubkeyhash[20]; + + if (!hdnode_get_ethereum_pubkeyhash(node, pubkeyhash)) return; + + uint32_t slip44 = + (msg->address_n_count > 1) ? (msg->address_n[1] & 0x7fffffff) : 0; + bool rskip60 = false; + uint32_t chain_id = 0; + // constants from trezor-common/defs/ethereum/networks.json + switch (slip44) { + case 137: + rskip60 = true; + chain_id = 30; + break; + case 37310: + rskip60 = true; + chain_id = 31; + break; + } + + resp->has_address = true; + resp->address[0] = '0'; + resp->address[1] = 'x'; + ethereum_address_checksum(pubkeyhash, resp->address + 2, rskip60, chain_id); + // ethereum_address_checksum adds trailing zero + + if (msg->has_show_display && msg->show_display) { + char desc[16]; + strlcpy(desc, "Address:", sizeof(desc)); + + if (!fsm_layoutAddress(resp->address, desc, false, 0, msg->address_n, + msg->address_n_count, true)) { + return; + } + } + + msg_write(MessageType_MessageType_EthereumAddress, resp); + layoutHome(); +} + +void fsm_msgEthereumSignMessage(const EthereumSignMessage *msg) { + RESP_INIT(EthereumMessageSignature); + + CHECK_INITIALIZED + + layoutSignMessage(msg->message.bytes, msg->message.size); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + CHECK_PIN + + const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + ethereum_message_sign(msg, node, resp); + layoutHome(); +} + +void fsm_msgEthereumVerifyMessage(const EthereumVerifyMessage *msg) { + CHECK_PARAM(msg->has_address, _("No address provided")); + CHECK_PARAM(msg->has_message, _("No message provided")); + + if (ethereum_message_verify(msg) != 0) { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid signature")); + return; + } + + uint8_t pubkeyhash[20]; + if (!ethereum_parse(msg->address, pubkeyhash)) { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid address")); + return; + } + + layoutVerifyAddress(NULL, msg->address); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + layoutVerifyMessage(msg->message.bytes, msg->message.size); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + fsm_sendSuccess(_("Message verified")); + + layoutHome(); +} diff --git a/legacy/firmware/fsm_msg_lisk.h b/legacy/firmware/fsm_msg_lisk.h new file mode 100644 index 0000000000..e95820f570 --- /dev/null +++ b/legacy/firmware/fsm_msg_lisk.h @@ -0,0 +1,144 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 alepop + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgLiskGetAddress(const LiskGetAddress *msg) { + CHECK_INITIALIZED + + CHECK_PIN + + RESP_INIT(LiskAddress); + + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + resp->has_address = true; + hdnode_fill_public_key(node); + lisk_get_address_from_public_key(&node->public_key[1], resp->address); + + if (msg->has_show_display && msg->show_display) { + if (!fsm_layoutAddress(resp->address, _("Address:"), true, 0, + msg->address_n, msg->address_n_count, false)) { + return; + } + } + + msg_write(MessageType_MessageType_LiskAddress, resp); + + layoutHome(); +} + +void fsm_msgLiskGetPublicKey(const LiskGetPublicKey *msg) { + CHECK_INITIALIZED + + CHECK_PIN + + RESP_INIT(LiskPublicKey); + + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + hdnode_fill_public_key(node); + + resp->has_public_key = true; + resp->public_key.size = 32; + + if (msg->has_show_display && msg->show_display) { + layoutLiskPublicKey(&node->public_key[1]); + if (!protectButton(ButtonRequestType_ButtonRequest_PublicKey, true)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + memcpy(&resp->public_key.bytes, &node->public_key[1], + sizeof(resp->public_key.bytes)); + + msg_write(MessageType_MessageType_LiskPublicKey, resp); + + layoutHome(); +} + +void fsm_msgLiskSignMessage(const LiskSignMessage *msg) { + CHECK_INITIALIZED + + CHECK_PIN + + RESP_INIT(LiskMessageSignature); + + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + hdnode_fill_public_key(node); + + lisk_sign_message(node, msg, resp); + + msg_write(MessageType_MessageType_LiskMessageSignature, resp); + + layoutHome(); +} + +void fsm_msgLiskVerifyMessage(const LiskVerifyMessage *msg) { + if (lisk_verify_message(msg)) { + char address[MAX_LISK_ADDRESS_SIZE]; + lisk_get_address_from_public_key((const uint8_t *)&msg->public_key, + address); + + layoutLiskVerifyAddress(address); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + layoutVerifyMessage(msg->message.bytes, msg->message.size); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + fsm_sendSuccess(_("Message verified")); + } else { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid signature")); + } + + layoutHome(); +} + +void fsm_msgLiskSignTx(LiskSignTx *msg) { + CHECK_INITIALIZED + + CHECK_PIN + + RESP_INIT(LiskSignedTx); + + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + hdnode_fill_public_key(node); + + lisk_sign_tx(node, msg, resp); + + msg_write(MessageType_MessageType_LiskSignedTx, resp); + + layoutHome(); +} diff --git a/legacy/firmware/fsm_msg_nem.h b/legacy/firmware/fsm_msg_nem.h new file mode 100644 index 0000000000..ce7f296032 --- /dev/null +++ b/legacy/firmware/fsm_msg_nem.h @@ -0,0 +1,352 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgNEMGetAddress(NEMGetAddress *msg) { + if (!msg->has_network) { + msg->network = NEM_NETWORK_MAINNET; + } + + const char *network; + CHECK_PARAM((network = nem_network_name(msg->network)), + _("Invalid NEM network")); + + CHECK_INITIALIZED + CHECK_PIN + + RESP_INIT(NEMAddress); + + HDNode *node = fsm_getDerivedNode(ED25519_KECCAK_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + if (!hdnode_get_nem_address(node, msg->network, resp->address)) return; + + if (msg->has_show_display && msg->show_display) { + char desc[16]; + strlcpy(desc, network, sizeof(desc)); + strlcat(desc, ":", sizeof(desc)); + + if (!fsm_layoutAddress(resp->address, desc, true, 0, msg->address_n, + msg->address_n_count, false)) { + return; + } + } + + msg_write(MessageType_MessageType_NEMAddress, resp); + layoutHome(); +} + +void fsm_msgNEMSignTx(NEMSignTx *msg) { + const char *reason; + +#define NEM_CHECK_PARAM(s) CHECK_PARAM((reason = (s)) == NULL, reason) +#define NEM_CHECK_PARAM_WHEN(b, s) \ + CHECK_PARAM(!(b) || (reason = (s)) == NULL, reason) + + CHECK_PARAM(msg->has_transaction, _("No common provided")); + + // Ensure exactly one transaction is provided + unsigned int provided = msg->has_transfer + msg->has_provision_namespace + + msg->has_mosaic_creation + msg->has_supply_change + + msg->has_aggregate_modification + + msg->has_importance_transfer; + CHECK_PARAM(provided != 0, _("No transaction provided")); + CHECK_PARAM(provided == 1, _("More than one transaction provided")); + + NEM_CHECK_PARAM(nem_validate_common(&msg->transaction, false)); + NEM_CHECK_PARAM_WHEN( + msg->has_transfer, + nem_validate_transfer(&msg->transfer, msg->transaction.network)); + NEM_CHECK_PARAM_WHEN( + msg->has_provision_namespace, + nem_validate_provision_namespace(&msg->provision_namespace, + msg->transaction.network)); + NEM_CHECK_PARAM_WHEN(msg->has_mosaic_creation, + nem_validate_mosaic_creation(&msg->mosaic_creation, + msg->transaction.network)); + NEM_CHECK_PARAM_WHEN(msg->has_supply_change, + nem_validate_supply_change(&msg->supply_change)); + NEM_CHECK_PARAM_WHEN(msg->has_aggregate_modification, + nem_validate_aggregate_modification( + &msg->aggregate_modification, !msg->has_multisig)); + NEM_CHECK_PARAM_WHEN( + msg->has_importance_transfer, + nem_validate_importance_transfer(&msg->importance_transfer)); + + bool cosigning = msg->has_cosigning && msg->cosigning; + if (msg->has_multisig) { + NEM_CHECK_PARAM(nem_validate_common(&msg->multisig, true)); + + CHECK_PARAM(msg->transaction.network == msg->multisig.network, + _("Inner transaction network is different")); + } else { + CHECK_PARAM(!cosigning, _("No multisig transaction to cosign")); + } + + CHECK_INITIALIZED + CHECK_PIN + + const char *network = nem_network_name(msg->transaction.network); + + if (msg->has_multisig) { + char address[NEM_ADDRESS_SIZE + 1]; + nem_get_address(msg->multisig.signer.bytes, msg->multisig.network, address); + + if (!nem_askMultisig(address, network, cosigning, msg->transaction.fee)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Signing cancelled by user")); + layoutHome(); + return; + } + } + + RESP_INIT(NEMSignedTx); + + HDNode *node = + fsm_getDerivedNode(ED25519_KECCAK_NAME, msg->transaction.address_n, + msg->transaction.address_n_count, NULL); + if (!node) return; + + hdnode_fill_public_key(node); + + const NEMTransactionCommon *common = + msg->has_multisig ? &msg->multisig : &msg->transaction; + + char address[NEM_ADDRESS_SIZE + 1]; + hdnode_get_nem_address(node, common->network, address); + + if (msg->has_transfer) { + msg->transfer.mosaics_count = nem_canonicalizeMosaics( + msg->transfer.mosaics, msg->transfer.mosaics_count); + } + + if (msg->has_transfer && !nem_askTransfer(common, &msg->transfer, network)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Signing cancelled by user")); + layoutHome(); + return; + } + + if (msg->has_provision_namespace && + !nem_askProvisionNamespace(common, &msg->provision_namespace, network)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Signing cancelled by user")); + layoutHome(); + return; + } + + if (msg->has_mosaic_creation && + !nem_askMosaicCreation(common, &msg->mosaic_creation, network, address)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Signing cancelled by user")); + layoutHome(); + return; + } + + if (msg->has_supply_change && + !nem_askSupplyChange(common, &msg->supply_change, network)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Signing cancelled by user")); + layoutHome(); + return; + } + + if (msg->has_aggregate_modification && + !nem_askAggregateModification(common, &msg->aggregate_modification, + network, !msg->has_multisig)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Signing cancelled by user")); + layoutHome(); + return; + } + + if (msg->has_importance_transfer && + !nem_askImportanceTransfer(common, &msg->importance_transfer, network)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Signing cancelled by user")); + layoutHome(); + return; + } + + nem_transaction_ctx context; + nem_transaction_start(&context, &node->public_key[1], resp->data.bytes, + sizeof(resp->data.bytes)); + + if (msg->has_multisig) { + uint8_t buffer[sizeof(resp->data.bytes)]; + + nem_transaction_ctx inner; + nem_transaction_start(&inner, msg->multisig.signer.bytes, buffer, + sizeof(buffer)); + + if (msg->has_transfer && + !nem_fsmTransfer(&inner, NULL, &msg->multisig, &msg->transfer)) { + layoutHome(); + return; + } + + if (msg->has_provision_namespace && + !nem_fsmProvisionNamespace(&inner, &msg->multisig, + &msg->provision_namespace)) { + layoutHome(); + return; + } + + if (msg->has_mosaic_creation && + !nem_fsmMosaicCreation(&inner, &msg->multisig, &msg->mosaic_creation)) { + layoutHome(); + return; + } + + if (msg->has_supply_change && + !nem_fsmSupplyChange(&inner, &msg->multisig, &msg->supply_change)) { + layoutHome(); + return; + } + + if (msg->has_aggregate_modification && + !nem_fsmAggregateModification(&inner, &msg->multisig, + &msg->aggregate_modification)) { + layoutHome(); + return; + } + + if (msg->has_importance_transfer && + !nem_fsmImportanceTransfer(&inner, &msg->multisig, + &msg->importance_transfer)) { + layoutHome(); + return; + } + + if (!nem_fsmMultisig(&context, &msg->transaction, &inner, cosigning)) { + layoutHome(); + return; + } + } else { + if (msg->has_transfer && + !nem_fsmTransfer(&context, node, &msg->transaction, &msg->transfer)) { + layoutHome(); + return; + } + + if (msg->has_provision_namespace && + !nem_fsmProvisionNamespace(&context, &msg->transaction, + &msg->provision_namespace)) { + layoutHome(); + return; + } + + if (msg->has_mosaic_creation && + !nem_fsmMosaicCreation(&context, &msg->transaction, + &msg->mosaic_creation)) { + layoutHome(); + return; + } + + if (msg->has_supply_change && + !nem_fsmSupplyChange(&context, &msg->transaction, + &msg->supply_change)) { + layoutHome(); + return; + } + + if (msg->has_aggregate_modification && + !nem_fsmAggregateModification(&context, &msg->transaction, + &msg->aggregate_modification)) { + layoutHome(); + return; + } + + if (msg->has_importance_transfer && + !nem_fsmImportanceTransfer(&context, &msg->transaction, + &msg->importance_transfer)) { + layoutHome(); + return; + } + } + + resp->has_data = true; + resp->data.size = + nem_transaction_end(&context, node->private_key, resp->signature.bytes); + + resp->has_signature = true; + resp->signature.size = sizeof(ed25519_signature); + + msg_write(MessageType_MessageType_NEMSignedTx, resp); + layoutHome(); +} + +void fsm_msgNEMDecryptMessage(NEMDecryptMessage *msg) { + RESP_INIT(NEMDecryptedMessage); + + CHECK_INITIALIZED + + CHECK_PARAM(nem_network_name(msg->network), _("Invalid NEM network")); + CHECK_PARAM(msg->has_payload, _("No payload provided")); + CHECK_PARAM(msg->payload.size >= NEM_ENCRYPTED_PAYLOAD_SIZE(0), + _("Invalid encrypted payload")); + CHECK_PARAM(msg->has_public_key, _("No public key provided")); + CHECK_PARAM(msg->public_key.size == 32, _("Invalid public key")); + + char address[NEM_ADDRESS_SIZE + 1]; + nem_get_address(msg->public_key.bytes, msg->network, address); + + layoutNEMDialog(&bmp_icon_question, _("Cancel"), _("Confirm"), + _("Decrypt message"), _("Confirm address?"), address); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + CHECK_PIN + + const HDNode *node = fsm_getDerivedNode(ED25519_KECCAK_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + + const uint8_t *salt = msg->payload.bytes; + uint8_t *iv = &msg->payload.bytes[NEM_SALT_SIZE]; + + const uint8_t *payload = &msg->payload.bytes[NEM_SALT_SIZE + AES_BLOCK_SIZE]; + size_t size = msg->payload.size - NEM_SALT_SIZE - AES_BLOCK_SIZE; + + // hdnode_nem_decrypt mutates the IV, so this will modify msg + bool ret = hdnode_nem_decrypt(node, msg->public_key.bytes, iv, salt, payload, + size, resp->payload.bytes); + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to decrypt payload")); + layoutHome(); + return; + } + + resp->has_payload = true; + resp->payload.size = NEM_DECRYPTED_SIZE(resp->payload.bytes, size); + + layoutNEMTransferPayload(resp->payload.bytes, resp->payload.size, true); + if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + msg_write(MessageType_MessageType_NEMDecryptedMessage, resp); + layoutHome(); +} diff --git a/legacy/firmware/fsm_msg_stellar.h b/legacy/firmware/fsm_msg_stellar.h new file mode 100644 index 0000000000..1aa1bbf829 --- /dev/null +++ b/legacy/firmware/fsm_msg_stellar.h @@ -0,0 +1,274 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 ZuluCrypto + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgStellarGetAddress(const StellarGetAddress *msg) { + RESP_INIT(StellarAddress); + + CHECK_INITIALIZED + + CHECK_PIN + + const HDNode *node = stellar_deriveNode(msg->address_n, msg->address_n_count); + if (!node) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to derive private key")); + return; + } + + if (msg->has_show_display && msg->show_display) { + const char **str_addr_rows = stellar_lineBreakAddress(node->public_key + 1); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), + _("Share public account ID?"), str_addr_rows[0], + str_addr_rows[1], str_addr_rows[2], NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + resp->has_address = true; + stellar_publicAddressAsStr(node->public_key + 1, resp->address, + sizeof(resp->address)); + + msg_write(MessageType_MessageType_StellarAddress, resp); + + layoutHome(); +} + +void fsm_msgStellarSignTx(const StellarSignTx *msg) { + CHECK_INITIALIZED + CHECK_PIN + + if (!stellar_signingInit(msg)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to derive private key")); + layoutHome(); + return; + } + + // Confirm transaction basics + stellar_layoutTransactionSummary(msg); + + // Respond with a request for the first operation + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); +} + +void fsm_msgStellarCreateAccountOp(const StellarCreateAccountOp *msg) { + if (!stellar_confirmCreateAccountOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarPaymentOp(const StellarPaymentOp *msg) { + // This will display additional dialogs to the user + if (!stellar_confirmPaymentOp(msg)) return; + + // Last operation was confirmed, send a StellarSignedTx + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarPathPaymentOp(const StellarPathPaymentOp *msg) { + if (!stellar_confirmPathPaymentOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarManageOfferOp(const StellarManageOfferOp *msg) { + if (!stellar_confirmManageOfferOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarCreatePassiveOfferOp( + const StellarCreatePassiveOfferOp *msg) { + if (!stellar_confirmCreatePassiveOfferOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarSetOptionsOp(const StellarSetOptionsOp *msg) { + if (!stellar_confirmSetOptionsOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarChangeTrustOp(const StellarChangeTrustOp *msg) { + if (!stellar_confirmChangeTrustOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarAllowTrustOp(const StellarAllowTrustOp *msg) { + if (!stellar_confirmAllowTrustOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarAccountMergeOp(const StellarAccountMergeOp *msg) { + if (!stellar_confirmAccountMergeOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarManageDataOp(const StellarManageDataOp *msg) { + if (!stellar_confirmManageDataOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarBumpSequenceOp(const StellarBumpSequenceOp *msg) { + if (!stellar_confirmBumpSequenceOp(msg)) return; + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} diff --git a/legacy/firmware/gettext.h b/legacy/firmware/gettext.h new file mode 100644 index 0000000000..bad59943e0 --- /dev/null +++ b/legacy/firmware/gettext.h @@ -0,0 +1,25 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __GETTEXT_H__ +#define __GETTEXT_H__ + +#define _(X) (X) + +#endif diff --git a/legacy/firmware/header.S b/legacy/firmware/header.S new file mode 100644 index 0000000000..46e4aa3751 --- /dev/null +++ b/legacy/firmware/header.S @@ -0,0 +1,33 @@ + .syntax unified + +#include "version.h" + + .section .header, "a" + + .type g_header, %object + .size g_header, .-g_header + +g_header: + .byte 'T','R','Z','F' // magic + .word reset_handler // reset handler, replace later with : .word g_header_end - g_header // hdrlen + .word 0 // expiry + .word _codelen // codelen + .byte VERSION_MAJOR // vmajor + .byte VERSION_MINOR // vminor + .byte VERSION_PATCH // vpatch + .byte 0 // vbuild + .byte FIX_VERSION_MAJOR // fix_vmajor + .byte FIX_VERSION_MINOR // fix_vminor + .byte FIX_VERSION_PATCH // fix_vpatch + .byte 0 // fix_vbuild + . = . + 8 // reserved + . = . + 512 // hash1 ... hash16 + . = . + 64 // sig1 + . = . + 64 // sig2 + . = . + 64 // sig3 + .byte 0 // sigindex1 + .byte 0 // sigindex2 + .byte 0 // sigindex3 + . = . + 220 // reserved + . = . + 65 // reserved +g_header_end: diff --git a/legacy/firmware/layout2.c b/legacy/firmware/layout2.c new file mode 100644 index 0000000000..61510249e8 --- /dev/null +++ b/legacy/firmware/layout2.c @@ -0,0 +1,945 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include + +#include "bignum.h" +#include "bitmaps.h" +#include "config.h" +#include "gettext.h" +#include "layout2.h" +#include "memzero.h" +#include "nem2.h" +#include "oled.h" +#include "qrcodegen.h" +#include "secp256k1.h" +#include "string.h" +#include "timer.h" +#include "util.h" + +#define BITCOIN_DIVISIBILITY (8) + +static const char *slip44_extras(uint32_t coin_type) { + if ((coin_type & 0x80000000) == 0) { + return 0; + } + switch (coin_type & 0x7fffffff) { + case 40: + return "EXP"; // Expanse + case 43: + return "NEM"; // NEM + case 60: + return "ETH"; // Ethereum Mainnet + case 61: + return "ETC"; // Ethereum Classic Mainnet + case 108: + return "UBQ"; // UBIQ + case 137: + return "RSK"; // Rootstock Mainnet + case 37310: + return "tRSK"; // Rootstock Testnet + } + return 0; +} + +#define BIP32_MAX_LAST_ELEMENT 1000000 + +static const char *address_n_str(const uint32_t *address_n, + size_t address_n_count, + bool address_is_account) { + if (address_n_count > 8) { + return _("Unknown long path"); + } + if (address_n_count == 0) { + return _("Path: m"); + } + + // known BIP44/49 path + static char path[100]; + if (address_n_count == 5 && + (address_n[0] == (0x80000000 + 44) || address_n[0] == (0x80000000 + 49) || + address_n[0] == (0x80000000 + 84)) && + (address_n[1] & 0x80000000) && (address_n[2] & 0x80000000) && + (address_n[3] <= 1) && (address_n[4] <= BIP32_MAX_LAST_ELEMENT)) { + bool native_segwit = (address_n[0] == (0x80000000 + 84)); + bool p2sh_segwit = (address_n[0] == (0x80000000 + 49)); + bool legacy = false; + const CoinInfo *coin = coinBySlip44(address_n[1]); + const char *abbr = 0; + if (native_segwit) { + if (coin && coin->has_segwit && coin->bech32_prefix) { + abbr = coin->coin_shortcut + 1; + } + } else if (p2sh_segwit) { + if (coin && coin->has_segwit && coin->has_address_type_p2sh) { + abbr = coin->coin_shortcut + 1; + } + } else { + if (coin) { + if (coin->has_segwit && coin->has_address_type_p2sh) { + legacy = true; + } + abbr = coin->coin_shortcut + 1; + } else { + abbr = slip44_extras(address_n[1]); + } + } + const uint32_t accnum = address_is_account + ? ((address_n[4] & 0x7fffffff) + 1) + : (address_n[2] & 0x7fffffff) + 1; + if (abbr && accnum < 100) { + memzero(path, sizeof(path)); + strlcpy(path, abbr, sizeof(path)); + // TODO: how to name accounts? + // currently we have "legacy account", "account" and "segwit account" + // for BIP44/P2PKH, BIP49/P2SH-P2WPKH and BIP84/P2WPKH respectivelly + if (legacy) { + strlcat(path, " legacy", sizeof(path)); + } + if (native_segwit) { + strlcat(path, " segwit", sizeof(path)); + } + if (address_is_account) { + strlcat(path, " address #", sizeof(path)); + } else { + strlcat(path, " account #", sizeof(path)); + } + char acc[3]; + memzero(acc, sizeof(acc)); + if (accnum < 10) { + acc[0] = '0' + accnum; + } else { + acc[0] = '0' + (accnum / 10); + acc[1] = '0' + (accnum % 10); + } + strlcat(path, acc, sizeof(path)); + return path; + } + } + + // "Path: m" / i ' + static char address_str[7 + 8 * (1 + 10 + 1) + 1]; + char *c = address_str + sizeof(address_str) - 1; + + *c = 0; + c--; + + for (int n = (int)address_n_count - 1; n >= 0; n--) { + uint32_t i = address_n[n]; + if (i & 0x80000000) { + *c = '\''; + c--; + } + i = i & 0x7fffffff; + do { + *c = '0' + (i % 10); + c--; + i /= 10; + } while (i > 0); + *c = '/'; + c--; + } + *c = 'm'; + c--; + *c = ' '; + c--; + *c = ':'; + c--; + *c = 'h'; + c--; + *c = 't'; + c--; + *c = 'a'; + c--; + *c = 'P'; + + return c; +} + +// split longer string into 4 rows, rowlen chars each +const char **split_message(const uint8_t *msg, uint32_t len, uint32_t rowlen) { + static char str[4][32 + 1]; + if (rowlen > 32) { + rowlen = 32; + } + memzero(str, sizeof(str)); + strlcpy(str[0], (char *)msg, rowlen + 1); + if (len > rowlen) { + strlcpy(str[1], (char *)msg + rowlen, rowlen + 1); + } + if (len > rowlen * 2) { + strlcpy(str[2], (char *)msg + rowlen * 2, rowlen + 1); + } + if (len > rowlen * 3) { + strlcpy(str[3], (char *)msg + rowlen * 3, rowlen + 1); + } + if (len > rowlen * 4) { + str[3][rowlen - 1] = '.'; + str[3][rowlen - 2] = '.'; + str[3][rowlen - 3] = '.'; + } + static const char *ret[4] = {str[0], str[1], str[2], str[3]}; + return ret; +} + +const char **split_message_hex(const uint8_t *msg, uint32_t len) { + char hex[32 * 2 + 1]; + memzero(hex, sizeof(hex)); + uint32_t size = len; + if (len > 32) { + size = 32; + } + data2hex(msg, size, hex); + if (len > 32) { + hex[63] = '.'; + hex[62] = '.'; + } + return split_message((const uint8_t *)hex, size * 2, 16); +} + +void *layoutLast = layoutHome; + +void layoutDialogSwipe(const BITMAP *icon, const char *btnNo, + const char *btnYes, const char *desc, const char *line1, + const char *line2, const char *line3, const char *line4, + const char *line5, const char *line6) { + layoutLast = layoutDialogSwipe; + layoutSwipe(); + layoutDialog(icon, btnNo, btnYes, desc, line1, line2, line3, line4, line5, + line6); +} + +void layoutProgressSwipe(const char *desc, int permil) { + if (layoutLast == layoutProgressSwipe) { + oledClear(); + } else { + layoutLast = layoutProgressSwipe; + layoutSwipe(); + } + layoutProgress(desc, permil); +} + +void layoutScreensaver(void) { + layoutLast = layoutScreensaver; + oledClear(); + oledRefresh(); +} + +void layoutHome(void) { + if (layoutLast == layoutHome || layoutLast == layoutScreensaver) { + oledClear(); + } else { + layoutSwipe(); + } + layoutLast = layoutHome; + + char label[MAX_LABEL_LEN + 1] = _("Go to trezor.io/start"); + if (config_isInitialized()) { + config_getLabel(label, sizeof(label)); + } + + uint8_t homescreen[HOMESCREEN_SIZE]; + if (config_getHomescreen(homescreen, sizeof(homescreen))) { + BITMAP b; + b.width = 128; + b.height = 64; + b.data = homescreen; + oledDrawBitmap(0, 0, &b); + } else { + if (label[0] != '\0') { + oledDrawBitmap(44, 4, &bmp_logo48); + oledDrawStringCenter(OLED_WIDTH / 2, OLED_HEIGHT - 8, label, + FONT_STANDARD); + } else { + oledDrawBitmap(40, 0, &bmp_logo64); + } + } + + bool no_backup = false; + bool unfinished_backup = false; + bool needs_backup = false; + config_getNoBackup(&no_backup); + config_getUnfinishedBackup(&unfinished_backup); + config_getNeedsBackup(&needs_backup); + if (no_backup) { + oledBox(0, 0, 127, 8, false); + oledDrawStringCenter(OLED_WIDTH / 2, 0, "SEEDLESS", FONT_STANDARD); + } else if (unfinished_backup) { + oledBox(0, 0, 127, 8, false); + oledDrawStringCenter(OLED_WIDTH / 2, 0, "BACKUP FAILED!", FONT_STANDARD); + } else if (needs_backup) { + oledBox(0, 0, 127, 8, false); + oledDrawStringCenter(OLED_WIDTH / 2, 0, "NEEDS BACKUP!", FONT_STANDARD); + } + oledRefresh(); + + // Reset lock screen timeout + system_millis_lock_start = timer_ms(); +} + +static void render_address_dialog(const CoinInfo *coin, const char *address, + const char *line1, const char *line2, + const char *extra_line) { + if (coin && coin->cashaddr_prefix) { + /* If this is a cashaddr address, remove the prefix from the + * string presented to the user + */ + int prefix_len = strlen(coin->cashaddr_prefix); + if (strncmp(address, coin->cashaddr_prefix, prefix_len) == 0 && + address[prefix_len] == ':') { + address += prefix_len + 1; + } + } + int addrlen = strlen(address); + int numlines = addrlen <= 42 ? 2 : 3; + int linelen = (addrlen - 1) / numlines + 1; + if (linelen > 21) { + linelen = 21; + } + const char **str = split_message((const uint8_t *)address, addrlen, linelen); + layoutLast = layoutDialogSwipe; + layoutSwipe(); + oledClear(); + oledDrawBitmap(0, 0, &bmp_icon_question); + oledDrawString(20, 0 * 9, line1, FONT_STANDARD); + oledDrawString(20, 1 * 9, line2, FONT_STANDARD); + int left = linelen > 18 ? 0 : 20; + oledDrawString(left, 2 * 9, str[0], FONT_FIXED); + oledDrawString(left, 3 * 9, str[1], FONT_FIXED); + oledDrawString(left, 4 * 9, str[2], FONT_FIXED); + oledDrawString(left, 5 * 9, str[3], FONT_FIXED); + if (!str[3][0]) { + if (extra_line) { + oledDrawString(0, 5 * 9, extra_line, FONT_STANDARD); + } else { + oledHLine(OLED_HEIGHT - 13); + } + } + layoutButtonNo(_("Cancel")); + layoutButtonYes(_("Confirm")); + oledRefresh(); +} + +void layoutConfirmOutput(const CoinInfo *coin, const TxOutputType *out) { + char str_out[32 + 3]; + bn_format_uint64(out->amount, NULL, coin->coin_shortcut, BITCOIN_DIVISIBILITY, + 0, false, str_out, sizeof(str_out) - 3); + strlcat(str_out, " to", sizeof(str_out)); + const char *address = out->address; + const char *extra_line = + (out->address_n_count > 0) + ? address_n_str(out->address_n, out->address_n_count, false) + : 0; + render_address_dialog(coin, address, _("Confirm sending"), str_out, + extra_line); +} + +void layoutConfirmOmni(const uint8_t *data, uint32_t size) { + const char *desc; + char str_out[32]; + uint32_t tx_type, currency; + REVERSE32(*(const uint32_t *)(data + 4), tx_type); + if (tx_type == 0x00000000 && size == 20) { // OMNI simple send + desc = _("Simple send of "); + REVERSE32(*(const uint32_t *)(data + 8), currency); + const char *suffix = " UNKN"; + switch (currency) { + case 1: + suffix = " OMNI"; + break; + case 2: + suffix = " tOMNI"; + break; + case 3: + suffix = " MAID"; + break; + case 31: + suffix = " USDT"; + break; + } + uint64_t amount_be, amount; + memcpy(&amount_be, data + 12, sizeof(uint64_t)); + REVERSE64(amount_be, amount); + bn_format_uint64(amount, NULL, suffix, BITCOIN_DIVISIBILITY, 0, false, + str_out, sizeof(str_out)); + } else { + desc = _("Unknown transaction"); + str_out[0] = 0; + } + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Confirm OMNI Transaction:"), NULL, desc, NULL, str_out, + NULL); +} + +static bool is_valid_ascii(const uint8_t *data, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + if (data[i] < ' ' || data[i] > '~') { + return false; + } + } + return true; +} + +void layoutConfirmOpReturn(const uint8_t *data, uint32_t size) { + const char **str; + if (!is_valid_ascii(data, size)) { + str = split_message_hex(data, size); + } else { + str = split_message(data, size, 20); + } + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Confirm OP_RETURN:"), str[0], str[1], str[2], str[3], + NULL); +} + +void layoutConfirmTx(const CoinInfo *coin, uint64_t amount_out, + uint64_t amount_fee) { + char str_out[32], str_fee[32]; + bn_format_uint64(amount_out, NULL, coin->coin_shortcut, BITCOIN_DIVISIBILITY, + 0, false, str_out, sizeof(str_out)); + bn_format_uint64(amount_fee, NULL, coin->coin_shortcut, BITCOIN_DIVISIBILITY, + 0, false, str_fee, sizeof(str_fee)); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Really send"), str_out, _("from your wallet?"), + _("Fee included:"), str_fee, NULL); +} + +void layoutFeeOverThreshold(const CoinInfo *coin, uint64_t fee) { + char str_fee[32]; + bn_format_uint64(fee, NULL, coin->coin_shortcut, BITCOIN_DIVISIBILITY, 0, + false, str_fee, sizeof(str_fee)); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Fee"), str_fee, _("is unexpectedly high."), NULL, + _("Send anyway?"), NULL); +} + +void layoutSignMessage(const uint8_t *msg, uint32_t len) { + const char **str; + if (!is_valid_ascii(msg, len)) { + str = split_message_hex(msg, len); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), + _("Sign binary message?"), str[0], str[1], str[2], str[3], + NULL, NULL); + } else { + str = split_message(msg, len, 20); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), + _("Sign message?"), str[0], str[1], str[2], str[3], NULL, + NULL); + } +} + +void layoutVerifyMessage(const uint8_t *msg, uint32_t len) { + const char **str; + if (!is_valid_ascii(msg, len)) { + str = split_message_hex(msg, len); + layoutDialogSwipe(&bmp_icon_info, _("Cancel"), _("Confirm"), + _("Verified binary message"), str[0], str[1], str[2], + str[3], NULL, NULL); + } else { + str = split_message(msg, len, 20); + layoutDialogSwipe(&bmp_icon_info, _("Cancel"), _("Confirm"), + _("Verified message"), str[0], str[1], str[2], str[3], + NULL, NULL); + } +} + +void layoutVerifyAddress(const CoinInfo *coin, const char *address) { + render_address_dialog(coin, address, _("Confirm address?"), + _("Message signed by:"), 0); +} + +void layoutCipherKeyValue(bool encrypt, const char *key) { + const char **str = split_message((const uint8_t *)key, strlen(key), 16); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), + encrypt ? _("Encrypt value of this key?") + : _("Decrypt value of this key?"), + str[0], str[1], str[2], str[3], NULL, NULL); +} + +void layoutEncryptMessage(const uint8_t *msg, uint32_t len, bool signing) { + const char **str = split_message(msg, len, 16); + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Confirm"), + signing ? _("Encrypt+Sign message?") : _("Encrypt message?"), str[0], + str[1], str[2], str[3], NULL, NULL); +} + +void layoutDecryptMessage(const uint8_t *msg, uint32_t len, + const char *address) { + const char **str = split_message(msg, len, 16); + layoutDialogSwipe( + &bmp_icon_info, NULL, _("OK"), + address ? _("Decrypted signed message") : _("Decrypted message"), str[0], + str[1], str[2], str[3], NULL, NULL); +} + +void layoutResetWord(const char *word, int pass, int word_pos, bool last) { + layoutLast = layoutResetWord; + layoutSwipe(); + + const char *btnYes; + if (last) { + if (pass == 1) { + btnYes = _("Finish"); + } else { + btnYes = _("Again"); + } + } else { + btnYes = _("Next"); + } + + const char *action; + if (pass == 1) { + action = _("Please check the seed"); + } else { + action = _("Write down the seed"); + } + + char index_str[] = "##th word is:"; + if (word_pos < 10) { + index_str[0] = ' '; + } else { + index_str[0] = '0' + word_pos / 10; + } + index_str[1] = '0' + word_pos % 10; + if (word_pos == 1 || word_pos == 21) { + index_str[2] = 's'; + index_str[3] = 't'; + } else if (word_pos == 2 || word_pos == 22) { + index_str[2] = 'n'; + index_str[3] = 'd'; + } else if (word_pos == 3 || word_pos == 23) { + index_str[2] = 'r'; + index_str[3] = 'd'; + } + + int left = 0; + oledClear(); + oledDrawBitmap(0, 0, &bmp_icon_info); + left = bmp_icon_info.width + 4; + + oledDrawString(left, 0 * 9, action, FONT_STANDARD); + oledDrawString(left, 2 * 9, word_pos < 10 ? index_str + 1 : index_str, + FONT_STANDARD); + oledDrawString(left, 3 * 9, word, FONT_STANDARD | FONT_DOUBLE); + oledHLine(OLED_HEIGHT - 13); + layoutButtonYes(btnYes); + oledRefresh(); +} + +#define QR_MAX_VERSION 9 + +void layoutAddress(const char *address, const char *desc, bool qrcode, + bool ignorecase, const uint32_t *address_n, + size_t address_n_count, bool address_is_account) { + if (layoutLast != layoutAddress) { + layoutSwipe(); + } else { + oledClear(); + } + layoutLast = layoutAddress; + + uint32_t addrlen = strlen(address); + if (qrcode) { + char address_upcase[addrlen + 1]; + if (ignorecase) { + for (uint32_t i = 0; i < addrlen + 1; i++) { + address_upcase[i] = address[i] >= 'a' && address[i] <= 'z' + ? address[i] + 'A' - 'a' + : address[i]; + } + } + uint8_t codedata[qrcodegen_BUFFER_LEN_FOR_VERSION(QR_MAX_VERSION)]; + uint8_t tempdata[qrcodegen_BUFFER_LEN_FOR_VERSION(QR_MAX_VERSION)]; + + int side = 0; + if (qrcodegen_encodeText(ignorecase ? address_upcase : address, tempdata, + codedata, qrcodegen_Ecc_LOW, qrcodegen_VERSION_MIN, + QR_MAX_VERSION, qrcodegen_Mask_AUTO, true)) { + side = qrcodegen_getSize(codedata); + } + + oledInvert(0, 0, 63, 63); + if (side > 0 && side <= 29) { + int offset = 32 - side; + for (int i = 0; i < side; i++) { + for (int j = 0; j < side; j++) { + if (qrcodegen_getModule(codedata, i, j)) { + oledBox(offset + i * 2, offset + j * 2, offset + 1 + i * 2, + offset + 1 + j * 2, false); + } + } + } + } else if (side > 0 && side <= 60) { + int offset = 32 - (side / 2); + for (int i = 0; i < side; i++) { + for (int j = 0; j < side; j++) { + if (qrcodegen_getModule(codedata, i, j)) { + oledClearPixel(offset + i, offset + j); + } + } + } + } + } else { + if (desc) { + oledDrawString(0, 0 * 9, desc, FONT_STANDARD); + } + if (addrlen > 10) { // don't split short addresses + uint32_t rowlen = + (addrlen - 1) / (addrlen <= 42 ? 2 : addrlen <= 63 ? 3 : 4) + 1; + const char **str = + split_message((const uint8_t *)address, addrlen, rowlen); + for (int i = 0; i < 4; i++) { + oledDrawString(0, (i + 1) * 9 + 4, str[i], FONT_FIXED); + } + } else { + oledDrawString(0, (0 + 1) * 9 + 4, address, FONT_FIXED); + } + oledDrawString( + 0, 42, address_n_str(address_n, address_n_count, address_is_account), + FONT_STANDARD); + } + + if (!qrcode) { + layoutButtonNo(_("QR Code")); + } + + layoutButtonYes(_("Continue")); + oledRefresh(); +} + +void layoutPublicKey(const uint8_t *pubkey) { + char desc[16]; + strlcpy(desc, "Public Key: 00", sizeof(desc)); + if (pubkey[0] == 1) { + /* ed25519 public key */ + // pass - leave 00 + } else { + data2hex(pubkey, 1, desc + 12); + } + const char **str = split_message_hex(pubkey + 1, 32 * 2); + layoutDialogSwipe(&bmp_icon_question, NULL, _("Continue"), NULL, desc, str[0], + str[1], str[2], str[3], NULL); +} + +void layoutSignIdentity(const IdentityType *identity, const char *challenge) { + char row_proto[8 + 11 + 1]; + char row_hostport[64 + 6 + 1]; + char row_user[64 + 8 + 1]; + + bool is_gpg = (strcmp(identity->proto, "gpg") == 0); + + if (identity->has_proto && identity->proto[0]) { + if (strcmp(identity->proto, "https") == 0) { + strlcpy(row_proto, _("Web sign in to:"), sizeof(row_proto)); + } else if (is_gpg) { + strlcpy(row_proto, _("GPG sign for:"), sizeof(row_proto)); + } else { + strlcpy(row_proto, identity->proto, sizeof(row_proto)); + char *p = row_proto; + while (*p) { + *p = toupper((int)*p); + p++; + } + strlcat(row_proto, _(" login to:"), sizeof(row_proto)); + } + } else { + strlcpy(row_proto, _("Login to:"), sizeof(row_proto)); + } + + if (identity->has_host && identity->host[0]) { + strlcpy(row_hostport, identity->host, sizeof(row_hostport)); + if (identity->has_port && identity->port[0]) { + strlcat(row_hostport, ":", sizeof(row_hostport)); + strlcat(row_hostport, identity->port, sizeof(row_hostport)); + } + } else { + row_hostport[0] = 0; + } + + if (identity->has_user && identity->user[0]) { + strlcpy(row_user, _("user: "), sizeof(row_user)); + strlcat(row_user, identity->user, sizeof(row_user)); + } else { + row_user[0] = 0; + } + + if (is_gpg) { + // Split "First Last " into 2 lines: + // "First Last" + // "first@last.com" + char *email_start = strchr(row_hostport, '<'); + if (email_start) { + strlcpy(row_user, email_start + 1, sizeof(row_user)); + *email_start = 0; + char *email_end = strchr(row_user, '>'); + if (email_end) { + *email_end = 0; + } + } + } + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), + _("Do you want to sign in?"), + row_proto[0] ? row_proto : NULL, + row_hostport[0] ? row_hostport : NULL, + row_user[0] ? row_user : NULL, challenge, NULL, NULL); +} + +void layoutDecryptIdentity(const IdentityType *identity) { + char row_proto[8 + 11 + 1]; + char row_hostport[64 + 6 + 1]; + char row_user[64 + 8 + 1]; + + if (identity->has_proto && identity->proto[0]) { + strlcpy(row_proto, identity->proto, sizeof(row_proto)); + char *p = row_proto; + while (*p) { + *p = toupper((int)*p); + p++; + } + strlcat(row_proto, _(" decrypt for:"), sizeof(row_proto)); + } else { + strlcpy(row_proto, _("Decrypt for:"), sizeof(row_proto)); + } + + if (identity->has_host && identity->host[0]) { + strlcpy(row_hostport, identity->host, sizeof(row_hostport)); + if (identity->has_port && identity->port[0]) { + strlcat(row_hostport, ":", sizeof(row_hostport)); + strlcat(row_hostport, identity->port, sizeof(row_hostport)); + } + } else { + row_hostport[0] = 0; + } + + if (identity->has_user && identity->user[0]) { + strlcpy(row_user, _("user: "), sizeof(row_user)); + strlcat(row_user, identity->user, sizeof(row_user)); + } else { + row_user[0] = 0; + } + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), + _("Do you want to decrypt?"), + row_proto[0] ? row_proto : NULL, + row_hostport[0] ? row_hostport : NULL, + row_user[0] ? row_user : NULL, NULL, NULL, NULL); +} + +void layoutU2FDialog(const char *verb, const char *appname) { + layoutDialog(&bmp_webauthn, NULL, verb, NULL, verb, _("U2F security key?"), + NULL, appname, NULL, NULL); +} + +void layoutNEMDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, + const char *desc, const char *line1, const char *address) { + static char first_third[NEM_ADDRESS_SIZE / 3 + 1]; + strlcpy(first_third, address, sizeof(first_third)); + + static char second_third[NEM_ADDRESS_SIZE / 3 + 1]; + strlcpy(second_third, &address[NEM_ADDRESS_SIZE / 3], sizeof(second_third)); + + const char *third_third = &address[NEM_ADDRESS_SIZE * 2 / 3]; + + layoutDialogSwipe(icon, btnNo, btnYes, desc, line1, first_third, second_third, + third_third, NULL, NULL); +} + +void layoutNEMTransferXEM(const char *desc, uint64_t quantity, + const bignum256 *multiplier, uint64_t fee) { + char str_out[32], str_fee[32]; + + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, quantity, multiplier, + str_out, sizeof(str_out)); + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, fee, NULL, str_fee, + sizeof(str_fee)); + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), desc, + _("Confirm transfer of"), str_out, _("and network fee of"), + str_fee, NULL, NULL); +} + +void layoutNEMNetworkFee(const char *desc, bool confirm, const char *fee1_desc, + uint64_t fee1, const char *fee2_desc, uint64_t fee2) { + char str_fee1[32], str_fee2[32]; + + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, fee1, NULL, str_fee1, + sizeof(str_fee1)); + + if (fee2_desc) { + nem_mosaicFormatAmount(NEM_MOSAIC_DEFINITION_XEM, fee2, NULL, str_fee2, + sizeof(str_fee2)); + } + + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), confirm ? _("Confirm") : _("Next"), desc, + fee1_desc, str_fee1, fee2_desc, fee2_desc ? str_fee2 : NULL, NULL, NULL); +} + +void layoutNEMTransferMosaic(const NEMMosaicDefinition *definition, + uint64_t quantity, const bignum256 *multiplier, + uint8_t network) { + char str_out[32], str_levy[32]; + + nem_mosaicFormatAmount(definition, quantity, multiplier, str_out, + sizeof(str_out)); + + if (definition->has_levy) { + nem_mosaicFormatLevy(definition, quantity, multiplier, network, str_levy, + sizeof(str_levy)); + } + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), + definition->has_name ? definition->name : _("Mosaic"), + _("Confirm transfer of"), str_out, + definition->has_levy ? _("and levy of") : NULL, + definition->has_levy ? str_levy : NULL, NULL, NULL); +} + +void layoutNEMTransferUnknownMosaic(const char *namespace, const char *mosaic, + uint64_t quantity, + const bignum256 *multiplier) { + char mosaic_name[32]; + nem_mosaicFormatName(namespace, mosaic, mosaic_name, sizeof(mosaic_name)); + + char str_out[32]; + nem_mosaicFormatAmount(NULL, quantity, multiplier, str_out, sizeof(str_out)); + + char *decimal = strchr(str_out, '.'); + if (decimal != NULL) { + *decimal = '\0'; + } + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("I take the risk"), + _("Unknown Mosaic"), _("Confirm transfer of"), str_out, + _("raw units of"), mosaic_name, NULL, NULL); +} + +void layoutNEMTransferPayload(const uint8_t *payload, size_t length, + bool encrypted) { + if (length >= 1 && payload[0] == 0xFE) { + char encoded[(length - 1) * 2 + 1]; + data2hex(&payload[1], length - 1, encoded); + + const char **str = + split_message((uint8_t *)encoded, sizeof(encoded) - 1, 16); + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), + encrypted ? _("Encrypted hex data") : _("Unencrypted hex data"), str[0], + str[1], str[2], str[3], NULL, NULL); + } else { + const char **str = split_message(payload, length, 16); + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), + encrypted ? _("Encrypted message") : _("Unencrypted message"), str[0], + str[1], str[2], str[3], NULL, NULL); + } +} + +void layoutNEMMosaicDescription(const char *description) { + const char **str = + split_message((uint8_t *)description, strlen(description), 16); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), + _("Mosaic Description"), str[0], str[1], str[2], str[3], + NULL, NULL); +} + +void layoutNEMLevy(const NEMMosaicDefinition *definition, uint8_t network) { + const NEMMosaicDefinition *mosaic; + if (nem_mosaicMatches(definition, definition->levy_namespace, + definition->levy_mosaic, network)) { + mosaic = definition; + } else { + mosaic = nem_mosaicByName(definition->levy_namespace, + definition->levy_mosaic, network); + } + + char mosaic_name[32]; + if (mosaic == NULL) { + nem_mosaicFormatName(definition->levy_namespace, definition->levy_mosaic, + mosaic_name, sizeof(mosaic_name)); + } + + char str_out[32]; + + switch (definition->levy) { + case NEMMosaicLevy_MosaicLevy_Percentile: + bn_format_uint64(definition->fee, NULL, NULL, 0, 0, false, str_out, + sizeof(str_out)); + + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), _("Percentile Levy"), + _("Raw levy value is"), str_out, _("in"), + mosaic ? (mosaic == definition ? _("the same mosaic") : mosaic->name) + : mosaic_name, + NULL, NULL); + break; + + case NEMMosaicLevy_MosaicLevy_Absolute: + default: + nem_mosaicFormatAmount(mosaic, definition->fee, NULL, str_out, + sizeof(str_out)); + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), _("Absolute Levy"), + _("Levy is"), str_out, + mosaic ? (mosaic == definition ? _("in the same mosaic") : NULL) + : _("in raw units of"), + mosaic ? NULL : mosaic_name, NULL, NULL); + break; + } +} + +static inline bool is_slip18(const uint32_t *address_n, + size_t address_n_count) { + return address_n_count == 2 && address_n[0] == (0x80000000 + 10018) && + (address_n[1] & 0x80000000) && (address_n[1] & 0x7FFFFFFF) <= 9; +} + +void layoutCosiCommitSign(const uint32_t *address_n, size_t address_n_count, + const uint8_t *data, uint32_t len, bool final_sign) { + char *desc = final_sign ? _("CoSi sign message?") : _("CoSi commit message?"); + char desc_buf[32]; + if (is_slip18(address_n, address_n_count)) { + if (final_sign) { + strlcpy(desc_buf, _("CoSi sign index #?"), sizeof(desc_buf)); + desc_buf[16] = '0' + (address_n[1] & 0x7FFFFFFF); + } else { + strlcpy(desc_buf, _("CoSi commit index #?"), sizeof(desc_buf)); + desc_buf[18] = '0' + (address_n[1] & 0x7FFFFFFF); + } + desc = desc_buf; + } + char str[4][17]; + if (len == 32) { + data2hex(data, 8, str[0]); + data2hex(data + 8, 8, str[1]); + data2hex(data + 16, 8, str[2]); + data2hex(data + 24, 8, str[3]); + } else { + strlcpy(str[0], "Data", sizeof(str[0])); + strlcpy(str[1], "of", sizeof(str[1])); + strlcpy(str[2], "unsupported", sizeof(str[2])); + strlcpy(str[3], "length", sizeof(str[3])); + } + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), desc, str[0], + str[1], str[2], str[3], NULL, NULL); +} diff --git a/legacy/firmware/layout2.h b/legacy/firmware/layout2.h new file mode 100644 index 0000000000..54b32baee6 --- /dev/null +++ b/legacy/firmware/layout2.h @@ -0,0 +1,94 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __LAYOUT2_H__ +#define __LAYOUT2_H__ + +#include "bignum.h" +#include "bitmaps.h" +#include "coins.h" +#include "layout.h" +#include "trezor.h" + +#include "messages-bitcoin.pb.h" +#include "messages-crypto.pb.h" +#include "messages-nem.pb.h" + +extern void *layoutLast; + +#if DEBUG_LINK +#define layoutSwipe oledClear +#else +#define layoutSwipe oledSwipeLeft +#endif + +void layoutDialogSwipe(const BITMAP *icon, const char *btnNo, + const char *btnYes, const char *desc, const char *line1, + const char *line2, const char *line3, const char *line4, + const char *line5, const char *line6); +void layoutProgressSwipe(const char *desc, int permil); + +void layoutScreensaver(void); +void layoutHome(void); +void layoutConfirmOutput(const CoinInfo *coin, const TxOutputType *out); +void layoutConfirmOmni(const uint8_t *data, uint32_t size); +void layoutConfirmOpReturn(const uint8_t *data, uint32_t size); +void layoutConfirmTx(const CoinInfo *coin, uint64_t amount_out, + uint64_t amount_fee); +void layoutFeeOverThreshold(const CoinInfo *coin, uint64_t fee); +void layoutSignMessage(const uint8_t *msg, uint32_t len); +void layoutVerifyAddress(const CoinInfo *coin, const char *address); +void layoutVerifyMessage(const uint8_t *msg, uint32_t len); +void layoutCipherKeyValue(bool encrypt, const char *key); +void layoutEncryptMessage(const uint8_t *msg, uint32_t len, bool signing); +void layoutDecryptMessage(const uint8_t *msg, uint32_t len, + const char *address); +void layoutResetWord(const char *word, int pass, int word_pos, bool last); +void layoutAddress(const char *address, const char *desc, bool qrcode, + bool ignorecase, const uint32_t *address_n, + size_t address_n_count, bool address_is_account); +void layoutPublicKey(const uint8_t *pubkey); +void layoutSignIdentity(const IdentityType *identity, const char *challenge); +void layoutDecryptIdentity(const IdentityType *identity); +void layoutU2FDialog(const char *verb, const char *appname); + +void layoutNEMDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, + const char *desc, const char *line1, const char *address); +void layoutNEMTransferXEM(const char *desc, uint64_t quantity, + const bignum256 *multiplier, uint64_t fee); +void layoutNEMNetworkFee(const char *desc, bool confirm, const char *fee1_desc, + uint64_t fee1, const char *fee2_desc, uint64_t fee2); +void layoutNEMTransferMosaic(const NEMMosaicDefinition *definition, + uint64_t quantity, const bignum256 *multiplier, + uint8_t network); +void layoutNEMTransferUnknownMosaic(const char *namespace, const char *mosaic, + uint64_t quantity, + const bignum256 *multiplier); +void layoutNEMTransferPayload(const uint8_t *payload, size_t length, + bool encrypted); +void layoutNEMMosaicDescription(const char *description); +void layoutNEMLevy(const NEMMosaicDefinition *definition, uint8_t network); + +void layoutCosiCommitSign(const uint32_t *address_n, size_t address_n_count, + const uint8_t *data, uint32_t len, bool final_sign); + +const char **split_message(const uint8_t *msg, uint32_t len, uint32_t rowlen); +const char **split_message_hex(const uint8_t *msg, uint32_t len); + +#endif diff --git a/legacy/firmware/lisk.c b/legacy/firmware/lisk.c new file mode 100644 index 0000000000..054b352667 --- /dev/null +++ b/legacy/firmware/lisk.c @@ -0,0 +1,354 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 alepop + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "lisk.h" +#include "bitmaps.h" +#include "crypto.h" +#include "curves.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "messages.pb.h" +#include "protect.h" +#include "util.h" + +void lisk_get_address_from_public_key(const uint8_t *public_key, + char *address) { + uint64_t digest[4]; + sha256_Raw(public_key, 32, (uint8_t *)digest); + bn_format_uint64(digest[0], NULL, "L", 0, 0, false, address, + MAX_LISK_ADDRESS_SIZE); +} + +void lisk_message_hash(const uint8_t *message, size_t message_len, + uint8_t hash[32]) { + SHA256_CTX ctx; + sha256_Init(&ctx); + sha256_Update(&ctx, (const uint8_t *)"\x15" "Lisk Signed Message:\n", 22); + uint8_t varint[5]; + uint32_t l = ser_length(message_len, varint); + sha256_Update(&ctx, varint, l); + sha256_Update(&ctx, message, message_len); + sha256_Final(&ctx, hash); + sha256_Raw(hash, 32, hash); +} + +void lisk_sign_message(const HDNode *node, const LiskSignMessage *msg, + LiskMessageSignature *resp) { + layoutSignMessage(msg->message.bytes, msg->message.size); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + layoutProgressSwipe(_("Signing"), 0); + + uint8_t signature[64]; + uint8_t hash[32]; + lisk_message_hash(msg->message.bytes, msg->message.size, hash); + + ed25519_sign(hash, 32, node->private_key, &node->public_key[1], signature); + + memcpy(resp->signature.bytes, signature, sizeof(signature)); + memcpy(resp->public_key.bytes, &node->public_key[1], 32); + + resp->has_signature = true; + resp->signature.size = 64; + resp->has_public_key = true; + resp->public_key.size = 32; +} + +bool lisk_verify_message(const LiskVerifyMessage *msg) { + uint8_t hash[32]; + lisk_message_hash(msg->message.bytes, msg->message.size, hash); + return 0 == ed25519_sign_open(hash, 32, msg->public_key.bytes, + msg->signature.bytes); +} + +static void lisk_update_raw_tx(const HDNode *node, LiskSignTx *msg) { + if (!msg->transaction.has_sender_public_key) { + memcpy(msg->transaction.sender_public_key.bytes, &node->public_key[1], 32); + } + + // For CastVotes transactions, recipientId should be equal to transaction + // creator address. + if (msg->transaction.type == LiskTransactionType_CastVotes && + !msg->transaction.has_recipient_id) { + msg->transaction.has_recipient_id = true; + lisk_get_address_from_public_key(&node->public_key[1], + msg->transaction.recipient_id); + } +} + +static void lisk_hashupdate_uint32(SHA256_CTX *ctx, uint32_t value) { + uint8_t data[4]; + write_le(data, value); + sha256_Update(ctx, data, sizeof(data)); +} + +static void lisk_hashupdate_uint64_le(SHA256_CTX *ctx, uint64_t value) { + sha256_Update(ctx, (uint8_t *)&value, sizeof(uint64_t)); +} + +static void lisk_hashupdate_uint64_be(SHA256_CTX *ctx, uint64_t value) { + uint8_t data[8]; + data[0] = value >> 56; + data[1] = value >> 48; + data[2] = value >> 40; + data[3] = value >> 32; + data[4] = value >> 24; + data[5] = value >> 16; + data[6] = value >> 8; + data[7] = value; + sha256_Update(ctx, data, sizeof(data)); +} + +static void lisk_hashupdate_asset(SHA256_CTX *ctx, LiskTransactionType type, + LiskTransactionAsset *asset) { + switch (type) { + case LiskTransactionType_Transfer: + if (asset->has_data) { + sha256_Update(ctx, (const uint8_t *)asset->data, strlen(asset->data)); + } + break; + case LiskTransactionType_RegisterDelegate: + if (asset->has_delegate && asset->delegate.has_username) { + sha256_Update(ctx, (const uint8_t *)asset->delegate.username, + strlen(asset->delegate.username)); + } + break; + case LiskTransactionType_CastVotes: { + for (int i = 0; i < asset->votes_count; i++) { + sha256_Update(ctx, (uint8_t *)asset->votes[i], strlen(asset->votes[i])); + } + break; + } + case LiskTransactionType_RegisterSecondPassphrase: + if (asset->has_signature && asset->signature.has_public_key) { + sha256_Update(ctx, asset->signature.public_key.bytes, + asset->signature.public_key.size); + } + break; + case LiskTransactionType_RegisterMultisignatureAccount: + if (asset->has_multisignature) { + sha256_Update(ctx, (uint8_t *)&(asset->multisignature.min), 1); + sha256_Update(ctx, (uint8_t *)&(asset->multisignature.life_time), 1); + for (int i = 0; i < asset->multisignature.keys_group_count; i++) { + sha256_Update(ctx, (uint8_t *)asset->multisignature.keys_group[i], + strlen(asset->multisignature.keys_group[i])); + }; + } + break; + default: + fsm_sendFailure(FailureType_Failure_DataError, + _("Invalid transaction type")); + break; + } +} + +#define MAX_LISK_VALUE_SIZE 20 + +static void lisk_format_value(uint64_t value, char *formated_value) { + bn_format_uint64(value, NULL, " LSK", 8, 0, false, formated_value, + MAX_LISK_VALUE_SIZE); +} + +void lisk_sign_tx(const HDNode *node, LiskSignTx *msg, LiskSignedTx *resp) { + lisk_update_raw_tx(node, msg); + + if (msg->has_transaction) { + SHA256_CTX ctx; + sha256_Init(&ctx); + + switch (msg->transaction.type) { + case LiskTransactionType_Transfer: + layoutRequireConfirmTx(msg->transaction.recipient_id, + msg->transaction.amount); + break; + case LiskTransactionType_RegisterDelegate: + layoutRequireConfirmDelegateRegistration(&msg->transaction.asset); + break; + case LiskTransactionType_CastVotes: + layoutRequireConfirmCastVotes(&msg->transaction.asset); + break; + case LiskTransactionType_RegisterSecondPassphrase: + layoutLiskPublicKey(msg->transaction.asset.signature.public_key.bytes); + break; + case LiskTransactionType_RegisterMultisignatureAccount: + layoutRequireConfirmMultisig(&msg->transaction.asset); + break; + default: + fsm_sendFailure(FailureType_Failure_DataError, + _("Invalid transaction type")); + layoutHome(); + break; + } + if (!protectButton((msg->transaction.type == + LiskTransactionType_RegisterSecondPassphrase + ? ButtonRequestType_ButtonRequest_PublicKey + : ButtonRequestType_ButtonRequest_SignTx), + false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + + layoutRequireConfirmFee(msg->transaction.fee, msg->transaction.amount); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + layoutProgressSwipe(_("Signing transaction"), 0); + + sha256_Update(&ctx, (const uint8_t *)&msg->transaction.type, 1); + + lisk_hashupdate_uint32(&ctx, msg->transaction.timestamp); + + sha256_Update(&ctx, msg->transaction.sender_public_key.bytes, 32); + + if (msg->transaction.has_requester_public_key) { + sha256_Update(&ctx, msg->transaction.requester_public_key.bytes, + msg->transaction.requester_public_key.size); + } + + uint64_t recipient_id = 0; + if (msg->transaction.has_recipient_id && + msg->transaction.recipient_id[0] != 0) { + // parse integer from lisk address ("123L" -> 123) + for (size_t i = 0; i < strlen(msg->transaction.recipient_id) - 1; i++) { + if (msg->transaction.recipient_id[i] < '0' || + msg->transaction.recipient_id[i] > '9') { + fsm_sendFailure(FailureType_Failure_DataError, + _("Invalid recipient_id")); + layoutHome(); + return; + } + recipient_id *= 10; + recipient_id += (msg->transaction.recipient_id[i] - '0'); + } + } + lisk_hashupdate_uint64_be(&ctx, recipient_id); + lisk_hashupdate_uint64_le(&ctx, msg->transaction.amount); + + lisk_hashupdate_asset(&ctx, msg->transaction.type, &msg->transaction.asset); + + // if signature exist calculate second signature + if (msg->transaction.has_signature) { + sha256_Update(&ctx, msg->transaction.signature.bytes, + msg->transaction.signature.size); + } + + uint8_t hash[32]; + sha256_Final(&ctx, hash); + ed25519_sign(hash, 32, node->private_key, &node->public_key[1], + resp->signature.bytes); + + resp->has_signature = true; + resp->signature.size = 64; + } +} + +// Layouts +void layoutLiskPublicKey(const uint8_t *pubkey) { + const char **str = split_message_hex(pubkey, 32); + layoutDialogSwipe(&bmp_icon_question, NULL, _("Continue"), NULL, + _("Public Key:"), str[0], str[1], str[2], str[3], NULL); +} + +void layoutLiskVerifyAddress(const char *address) { + const char **str = + split_message((const uint8_t *)address, strlen(address), 10); + layoutDialogSwipe(&bmp_icon_info, _("Cancel"), _("Confirm"), + _("Confirm address?"), _("Message signed by:"), str[0], + str[1], NULL, NULL, NULL); +} + +void layoutRequireConfirmTx(char *recipient_id, uint64_t amount) { + char formated_amount[MAX_LISK_VALUE_SIZE]; + const char **str = + split_message((const uint8_t *)recipient_id, strlen(recipient_id), 16); + lisk_format_value(amount, formated_amount); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Confirm sending"), formated_amount, _("to:"), str[0], + str[1], NULL); +} + +void layoutRequireConfirmFee(uint64_t fee, uint64_t amount) { + char formated_amount[MAX_LISK_VALUE_SIZE]; + char formated_fee[MAX_LISK_VALUE_SIZE]; + lisk_format_value(amount, formated_amount); + lisk_format_value(fee, formated_fee); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Confirm transaction"), formated_amount, _("fee:"), + formated_fee, NULL, NULL); +} + +void layoutRequireConfirmDelegateRegistration(LiskTransactionAsset *asset) { + if (asset->has_delegate && asset->delegate.has_username) { + const char **str = split_message((const uint8_t *)asset->delegate.username, + strlen(asset->delegate.username), 20); + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Confirm transaction"), _("Do you really want to"), + _("register a delegate?"), str[0], str[1], NULL); + } +} + +void layoutRequireConfirmCastVotes(LiskTransactionAsset *asset) { + uint8_t plus = 0; + uint8_t minus = 0; + char add_votes_txt[13]; + char remove_votes_txt[16]; + + for (int i = 0; i < asset->votes_count; i++) { + if (asset->votes[i][0] == '+') { + plus += 1; + } else { + minus += 1; + } + } + + bn_format_uint64(plus, "Add ", NULL, 0, 0, false, add_votes_txt, + sizeof(add_votes_txt)); + bn_format_uint64(minus, "Remove ", NULL, 0, 0, false, remove_votes_txt, + sizeof(remove_votes_txt)); + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Confirm transaction"), add_votes_txt, remove_votes_txt, + NULL, NULL, NULL); +} + +void layoutRequireConfirmMultisig(LiskTransactionAsset *asset) { + char keys_group_str[25]; + char life_time_str[14]; + char min_str[8]; + + bn_format_uint64(asset->multisignature.keys_group_count, + "Keys group length: ", NULL, 0, 0, false, keys_group_str, + sizeof(keys_group_str)); + bn_format_uint64(asset->multisignature.life_time, "Life time: ", NULL, 0, 0, + false, life_time_str, sizeof(life_time_str)); + bn_format_uint64(asset->multisignature.min, "Min: ", NULL, 0, 0, false, + min_str, sizeof(min_str)); + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Confirm transaction"), keys_group_str, life_time_str, + min_str, NULL, NULL); +} \ No newline at end of file diff --git a/legacy/firmware/lisk.h b/legacy/firmware/lisk.h new file mode 100644 index 0000000000..bf0456b58a --- /dev/null +++ b/legacy/firmware/lisk.h @@ -0,0 +1,46 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 alepop + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __LISK_H__ +#define __LISK_H__ + +#include +#include "bip32.h" +#include "messages-lisk.pb.h" + +#define MAX_LISK_ADDRESS_SIZE 23 + +void lisk_sign_message(const HDNode *node, const LiskSignMessage *msg, + LiskMessageSignature *resp); +bool lisk_verify_message(const LiskVerifyMessage *msg); +void lisk_sign_tx(const HDNode *node, LiskSignTx *msg, LiskSignedTx *resp); + +// Helpers +void lisk_get_address_from_public_key(const uint8_t *public_key, char *address); + +// Layout +void layoutLiskPublicKey(const uint8_t *pubkey); +void layoutLiskVerifyAddress(const char *address); +void layoutRequireConfirmTx(char *recipient_id, uint64_t amount); +void layoutRequireConfirmDelegateRegistration(LiskTransactionAsset *asset); +void layoutRequireConfirmCastVotes(LiskTransactionAsset *asset); +void layoutRequireConfirmMultisig(LiskTransactionAsset *asset); +void layoutRequireConfirmFee(uint64_t fee, uint64_t amount); + +#endif diff --git a/legacy/firmware/messages.c b/legacy/firmware/messages.c new file mode 100644 index 0000000000..f04b07291a --- /dev/null +++ b/legacy/firmware/messages.c @@ -0,0 +1,369 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "debug.h" +#include "fsm.h" +#include "gettext.h" +#include "memzero.h" +#include "messages.h" +#include "trezor.h" +#include "util.h" + +#include "messages.pb.h" +#include "pb_decode.h" +#include "pb_encode.h" + +struct MessagesMap_t { + char type; // n = normal, d = debug + char dir; // i = in, o = out + uint16_t msg_id; + const pb_field_t *fields; + void (*process_func)(const void *ptr); +}; + +static const struct MessagesMap_t MessagesMap[] = { +#include "messages_map.h" + // end + {0, 0, 0, 0, 0}}; + +#include "messages_map_limits.h" + +const pb_field_t *MessageFields(char type, char dir, uint16_t msg_id) { + const struct MessagesMap_t *m = MessagesMap; + while (m->type) { + if (type == m->type && dir == m->dir && msg_id == m->msg_id) { + return m->fields; + } + m++; + } + return 0; +} + +void MessageProcessFunc(char type, char dir, uint16_t msg_id, void *ptr) { + const struct MessagesMap_t *m = MessagesMap; + while (m->type) { + if (type == m->type && dir == m->dir && msg_id == m->msg_id) { + m->process_func(ptr); + return; + } + m++; + } +} + +static uint32_t msg_out_start = 0; +static uint32_t msg_out_end = 0; +static uint32_t msg_out_cur = 0; +static uint8_t msg_out[MSG_OUT_SIZE]; + +#if DEBUG_LINK + +static uint32_t msg_debug_out_start = 0; +static uint32_t msg_debug_out_end = 0; +static uint32_t msg_debug_out_cur = 0; +static uint8_t msg_debug_out[MSG_DEBUG_OUT_SIZE]; + +#endif + +static inline void msg_out_append(uint8_t c) { + if (msg_out_cur == 0) { + msg_out[msg_out_end * 64] = '?'; + msg_out_cur = 1; + } + msg_out[msg_out_end * 64 + msg_out_cur] = c; + msg_out_cur++; + if (msg_out_cur == 64) { + msg_out_cur = 0; + msg_out_end = (msg_out_end + 1) % (MSG_OUT_SIZE / 64); + } +} + +#if DEBUG_LINK + +static inline void msg_debug_out_append(uint8_t c) { + if (msg_debug_out_cur == 0) { + msg_debug_out[msg_debug_out_end * 64] = '?'; + msg_debug_out_cur = 1; + } + msg_debug_out[msg_debug_out_end * 64 + msg_debug_out_cur] = c; + msg_debug_out_cur++; + if (msg_debug_out_cur == 64) { + msg_debug_out_cur = 0; + msg_debug_out_end = (msg_debug_out_end + 1) % (MSG_DEBUG_OUT_SIZE / 64); + } +} + +#endif + +static inline void msg_out_pad(void) { + if (msg_out_cur == 0) return; + while (msg_out_cur < 64) { + msg_out[msg_out_end * 64 + msg_out_cur] = 0; + msg_out_cur++; + } + msg_out_cur = 0; + msg_out_end = (msg_out_end + 1) % (MSG_OUT_SIZE / 64); +} + +#if DEBUG_LINK + +static inline void msg_debug_out_pad(void) { + if (msg_debug_out_cur == 0) return; + while (msg_debug_out_cur < 64) { + msg_debug_out[msg_debug_out_end * 64 + msg_debug_out_cur] = 0; + msg_debug_out_cur++; + } + msg_debug_out_cur = 0; + msg_debug_out_end = (msg_debug_out_end + 1) % (MSG_DEBUG_OUT_SIZE / 64); +} + +#endif + +static bool pb_callback_out(pb_ostream_t *stream, const uint8_t *buf, + size_t count) { + (void)stream; + for (size_t i = 0; i < count; i++) { + msg_out_append(buf[i]); + } + return true; +} + +#if DEBUG_LINK + +static bool pb_debug_callback_out(pb_ostream_t *stream, const uint8_t *buf, + size_t count) { + (void)stream; + for (size_t i = 0; i < count; i++) { + msg_debug_out_append(buf[i]); + } + return true; +} + +#endif + +bool msg_write_common(char type, uint16_t msg_id, const void *msg_ptr) { + const pb_field_t *fields = MessageFields(type, 'o', msg_id); + if (!fields) { // unknown message + return false; + } + + size_t len; + if (!pb_get_encoded_size(&len, fields, msg_ptr)) { + return false; + } + + void (*append)(uint8_t); + bool (*pb_callback)(pb_ostream_t *, const uint8_t *, size_t); + + if (type == 'n') { + append = msg_out_append; + pb_callback = pb_callback_out; + } else +#if DEBUG_LINK + if (type == 'd') { + append = msg_debug_out_append; + pb_callback = pb_debug_callback_out; + } else +#endif + { + return false; + } + + append('#'); + append('#'); + append((msg_id >> 8) & 0xFF); + append(msg_id & 0xFF); + append((len >> 24) & 0xFF); + append((len >> 16) & 0xFF); + append((len >> 8) & 0xFF); + append(len & 0xFF); + pb_ostream_t stream = {pb_callback, 0, SIZE_MAX, 0, 0}; + bool status = pb_encode(&stream, fields, msg_ptr); + if (type == 'n') { + msg_out_pad(); + } +#if DEBUG_LINK + else if (type == 'd') { + msg_debug_out_pad(); + } +#endif + return status; +} + +enum { + READSTATE_IDLE, + READSTATE_READING, +}; + +void msg_process(char type, uint16_t msg_id, const pb_field_t *fields, + uint8_t *msg_raw, uint32_t msg_size) { + static uint8_t msg_data[MSG_IN_SIZE]; + memzero(msg_data, sizeof(msg_data)); + pb_istream_t stream = pb_istream_from_buffer(msg_raw, msg_size); + bool status = pb_decode(&stream, fields, msg_data); + if (status) { + MessageProcessFunc(type, 'i', msg_id, msg_data); + } else { + fsm_sendFailure(FailureType_Failure_DataError, stream.errmsg); + } +} + +void msg_read_common(char type, const uint8_t *buf, uint32_t len) { + static char read_state = READSTATE_IDLE; + static uint8_t msg_in[MSG_IN_SIZE]; + static uint16_t msg_id = 0xFFFF; + static uint32_t msg_size = 0; + static uint32_t msg_pos = 0; + static const pb_field_t *fields = 0; + + if (len != 64) return; + + if (read_state == READSTATE_IDLE) { + if (buf[0] != '?' || buf[1] != '#' || + buf[2] != '#') { // invalid start - discard + return; + } + msg_id = (buf[3] << 8) + buf[4]; + msg_size = + ((uint32_t)buf[5] << 24) + (buf[6] << 16) + (buf[7] << 8) + buf[8]; + + fields = MessageFields(type, 'i', msg_id); + if (!fields) { // unknown message + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, + _("Unknown message")); + return; + } + if (msg_size > MSG_IN_SIZE) { // message is too big :( + fsm_sendFailure(FailureType_Failure_DataError, _("Message too big")); + return; + } + + read_state = READSTATE_READING; + + memcpy(msg_in, buf + 9, len - 9); + msg_pos = len - 9; + } else if (read_state == READSTATE_READING) { + if (buf[0] != '?') { // invalid contents + read_state = READSTATE_IDLE; + return; + } + /* raw data starts at buf + 1 with len - 1 bytes */ + buf++; + len = MIN(len - 1, MSG_IN_SIZE - msg_pos); + + memcpy(msg_in + msg_pos, buf, len); + msg_pos += len; + } + + if (msg_pos >= msg_size) { + msg_process(type, msg_id, fields, msg_in, msg_size); + msg_pos = 0; + read_state = READSTATE_IDLE; + } +} + +const uint8_t *msg_out_data(void) { + if (msg_out_start == msg_out_end) return 0; + uint8_t *data = msg_out + (msg_out_start * 64); + msg_out_start = (msg_out_start + 1) % (MSG_OUT_SIZE / 64); + debugLog(0, "", "msg_out_data"); + return data; +} + +#if DEBUG_LINK + +const uint8_t *msg_debug_out_data(void) { + if (msg_debug_out_start == msg_debug_out_end) return 0; + uint8_t *data = msg_debug_out + (msg_debug_out_start * 64); + msg_debug_out_start = (msg_debug_out_start + 1) % (MSG_DEBUG_OUT_SIZE / 64); + debugLog(0, "", "msg_debug_out_data"); + return data; +} + +#endif + +CONFIDENTIAL uint8_t msg_tiny[128]; +_Static_assert(sizeof(msg_tiny) >= sizeof(Cancel), "msg_tiny too tiny"); +_Static_assert(sizeof(msg_tiny) >= sizeof(Initialize), "msg_tiny too tiny"); +_Static_assert(sizeof(msg_tiny) >= sizeof(PassphraseAck), "msg_tiny too tiny"); +_Static_assert(sizeof(msg_tiny) >= sizeof(ButtonAck), "msg_tiny too tiny"); +_Static_assert(sizeof(msg_tiny) >= sizeof(PinMatrixAck), "msg_tiny too tiny"); +#if DEBUG_LINK +_Static_assert(sizeof(msg_tiny) >= sizeof(DebugLinkDecision), + "msg_tiny too tiny"); +_Static_assert(sizeof(msg_tiny) >= sizeof(DebugLinkGetState), + "msg_tiny too tiny"); +#endif +uint16_t msg_tiny_id = 0xFFFF; + +void msg_read_tiny(const uint8_t *buf, int len) { + if (len != 64) return; + if (buf[0] != '?' || buf[1] != '#' || buf[2] != '#') { + return; + } + uint16_t msg_id = (buf[3] << 8) + buf[4]; + uint32_t msg_size = + ((uint32_t)buf[5] << 24) + (buf[6] << 16) + (buf[7] << 8) + buf[8]; + if (msg_size > 64 || len - msg_size < 9) { + return; + } + + const pb_field_t *fields = 0; + pb_istream_t stream = pb_istream_from_buffer(buf + 9, msg_size); + + switch (msg_id) { + case MessageType_MessageType_PinMatrixAck: + fields = PinMatrixAck_fields; + break; + case MessageType_MessageType_ButtonAck: + fields = ButtonAck_fields; + break; + case MessageType_MessageType_PassphraseAck: + fields = PassphraseAck_fields; + break; + case MessageType_MessageType_Cancel: + fields = Cancel_fields; + break; + case MessageType_MessageType_Initialize: + fields = Initialize_fields; + break; +#if DEBUG_LINK + case MessageType_MessageType_DebugLinkDecision: + fields = DebugLinkDecision_fields; + break; + case MessageType_MessageType_DebugLinkGetState: + fields = DebugLinkGetState_fields; + break; +#endif + } + if (fields) { + bool status = pb_decode(&stream, fields, msg_tiny); + if (status) { + msg_tiny_id = msg_id; + } else { + fsm_sendFailure(FailureType_Failure_DataError, stream.errmsg); + msg_tiny_id = 0xFFFF; + } + } else { + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, + _("Unknown message")); + msg_tiny_id = 0xFFFF; + } +} diff --git a/legacy/firmware/messages.h b/legacy/firmware/messages.h new file mode 100644 index 0000000000..fe3ca2898b --- /dev/null +++ b/legacy/firmware/messages.h @@ -0,0 +1,52 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __MESSAGES_H__ +#define __MESSAGES_H__ + +#include +#include +#include "trezor.h" + +#define MSG_IN_SIZE (15 * 1024) + +#define MSG_OUT_SIZE (3 * 1024) + +#define msg_read(buf, len) msg_read_common('n', (buf), (len)) +#define msg_write(id, ptr) msg_write_common('n', (id), (ptr)) +const uint8_t *msg_out_data(void); + +#if DEBUG_LINK + +#define MSG_DEBUG_OUT_SIZE (2 * 1024) + +#define msg_debug_read(buf, len) msg_read_common('d', (buf), (len)) +#define msg_debug_write(id, ptr) msg_write_common('d', (id), (ptr)) +const uint8_t *msg_debug_out_data(void); + +#endif + +void msg_read_common(char type, const uint8_t *buf, uint32_t len); +bool msg_write_common(char type, uint16_t msg_id, const void *msg_ptr); + +void msg_read_tiny(const uint8_t *buf, int len); +extern uint8_t msg_tiny[128]; +extern uint16_t msg_tiny_id; + +#endif diff --git a/legacy/firmware/nem2.c b/legacy/firmware/nem2.c new file mode 100644 index 0000000000..b9b3a61444 --- /dev/null +++ b/legacy/firmware/nem2.c @@ -0,0 +1,790 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "nem2.h" + +#include "aes/aes.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "memzero.h" +#include "protect.h" +#include "rng.h" +#include "secp256k1.h" + +const char *nem_validate_common(NEMTransactionCommon *common, bool inner) { + if (!common->has_network) { + common->has_network = true; + common->network = NEM_NETWORK_MAINNET; + } + + if (common->network > 0xFF || nem_network_name(common->network) == NULL) { + return inner ? _("Invalid NEM network in inner transaction") + : _("Invalid NEM network"); + } + + if (!common->has_timestamp) { + return inner ? _("No timestamp provided in inner transaction") + : _("No timestamp provided"); + } + + if (!common->has_fee) { + return inner ? _("No fee provided in inner transaction") + : _("No fee provided"); + } + + if (!common->has_deadline) { + return inner ? _("No deadline provided in inner transaction") + : _("No deadline provided"); + } + + if (inner != common->has_signer) { + return inner ? _("No signer provided in inner transaction") + : _("Signer not allowed in outer transaction"); + } + + if (common->has_signer && common->signer.size != sizeof(ed25519_public_key)) { + return _("Invalid signer public key in inner transaction"); + } + + return NULL; +} + +const char *nem_validate_transfer(const NEMTransfer *transfer, + uint8_t network) { + if (!transfer->has_recipient) return _("No recipient provided"); + if (!transfer->has_amount) return _("No amount provided"); + + if (transfer->has_public_key && + transfer->public_key.size != sizeof(ed25519_public_key)) { + return _("Invalid recipient public key"); + } + + if (!nem_validate_address(transfer->recipient, network)) + return _("Invalid recipient address"); + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + if (!mosaic->has_namespace) return _("No mosaic namespace provided"); + if (!mosaic->has_mosaic) return _("No mosaic name provided"); + if (!mosaic->has_quantity) return _("No mosaic quantity provided"); + } + + return NULL; +} + +const char *nem_validate_provision_namespace( + const NEMProvisionNamespace *provision_namespace, uint8_t network) { + if (!provision_namespace->has_namespace) return _("No namespace provided"); + if (!provision_namespace->has_sink) return _("No rental sink provided"); + if (!provision_namespace->has_fee) return _("No rental sink fee provided"); + + if (!nem_validate_address(provision_namespace->sink, network)) + return _("Invalid rental sink address"); + + return NULL; +} + +const char *nem_validate_mosaic_creation( + const NEMMosaicCreation *mosaic_creation, uint8_t network) { + if (!mosaic_creation->has_definition) + return _("No mosaic definition provided"); + if (!mosaic_creation->has_sink) return _("No creation sink provided"); + if (!mosaic_creation->has_fee) return _("No creation sink fee provided"); + + if (!nem_validate_address(mosaic_creation->sink, network)) + return _("Invalid creation sink address"); + + if (mosaic_creation->definition.has_name) + return _("Name not allowed in mosaic creation transactions"); + if (mosaic_creation->definition.has_ticker) + return _("Ticker not allowed in mosaic creation transactions"); + if (mosaic_creation->definition.networks_count) + return _("Networks not allowed in mosaic creation transactions"); + + if (!mosaic_creation->definition.has_namespace) + return _("No mosaic namespace provided"); + if (!mosaic_creation->definition.has_mosaic) + return _("No mosaic name provided"); + + if (mosaic_creation->definition.has_levy) { + if (!mosaic_creation->definition.has_fee) + return _("No levy address provided"); + if (!mosaic_creation->definition.has_levy_address) + return _("No levy address provided"); + if (!mosaic_creation->definition.has_levy_namespace) + return _("No levy namespace provided"); + if (!mosaic_creation->definition.has_levy_mosaic) + return _("No levy mosaic name provided"); + + if (!mosaic_creation->definition.has_divisibility) + return _("No divisibility provided"); + if (!mosaic_creation->definition.has_supply) return _("No supply provided"); + if (!mosaic_creation->definition.has_mutable_supply) + return _("No supply mutability provided"); + if (!mosaic_creation->definition.has_transferable) + return _("No mosaic transferability provided"); + if (!mosaic_creation->definition.has_description) + return _("No description provided"); + + if (mosaic_creation->definition.divisibility > NEM_MAX_DIVISIBILITY) + return _("Invalid divisibility provided"); + if (mosaic_creation->definition.supply > NEM_MAX_SUPPLY) + return _("Invalid supply provided"); + + if (!nem_validate_address(mosaic_creation->definition.levy_address, + network)) + return _("Invalid levy address"); + } + + return NULL; +} + +const char *nem_validate_supply_change( + const NEMMosaicSupplyChange *supply_change) { + if (!supply_change->has_namespace) return _("No namespace provided"); + if (!supply_change->has_mosaic) return _("No mosaic provided"); + if (!supply_change->has_type) return _("No type provided"); + if (!supply_change->has_delta) return _("No delta provided"); + + return NULL; +} + +const char *nem_validate_aggregate_modification( + const NEMAggregateModification *aggregate_modification, bool creation) { + if (creation && aggregate_modification->modifications_count == 0) { + return _("No modifications provided"); + } + + for (size_t i = 0; i < aggregate_modification->modifications_count; i++) { + const NEMCosignatoryModification *modification = + &aggregate_modification->modifications[i]; + + if (!modification->has_type) return _("No modification type provided"); + if (!modification->has_public_key) + return _("No cosignatory public key provided"); + if (modification->public_key.size != 32) + return _("Invalid cosignatory public key provided"); + + if (creation && modification->type == + NEMModificationType_CosignatoryModification_Delete) { + return _("Cannot remove cosignatory when converting account"); + } + } + + return NULL; +} + +const char *nem_validate_importance_transfer( + const NEMImportanceTransfer *importance_transfer) { + if (!importance_transfer->has_mode) return _("No mode provided"); + if (!importance_transfer->has_public_key) + return _("No remote account provided"); + if (importance_transfer->public_key.size != 32) + return _("Invalid remote account provided"); + + return NULL; +} + +bool nem_askTransfer(const NEMTransactionCommon *common, + const NEMTransfer *transfer, const char *desc) { + if (transfer->mosaics_count) { + const NEMMosaic *xem = NULL; + bool unknownMosaic = false; + + const NEMMosaicDefinition *definitions[transfer->mosaics_count]; + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + definitions[i] = + nem_mosaicByName(mosaic->namespace, mosaic->mosaic, common->network); + + if (definitions[i] == NEM_MOSAIC_DEFINITION_XEM) { + xem = mosaic; + } else if (definitions[i] == NULL) { + unknownMosaic = true; + } + } + + bignum256 multiplier; + bn_read_uint64(transfer->amount, &multiplier); + + if (unknownMosaic) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("I take the risk"), + _("Unknown Mosaics"), _("Divisibility and levy"), + _("cannot be shown for"), _("unknown mosaics!"), NULL, + NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, + false)) { + return false; + } + } + + layoutNEMTransferXEM(desc, xem ? xem->quantity : 0, &multiplier, + common->fee); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + if (mosaic == xem) { + continue; + } + + if (definitions[i]) { + layoutNEMTransferMosaic(definitions[i], mosaic->quantity, &multiplier, + common->network); + } else { + layoutNEMTransferUnknownMosaic(mosaic->namespace, mosaic->mosaic, + mosaic->quantity, &multiplier); + } + + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, + false)) { + return false; + } + } + } else { + layoutNEMTransferXEM(desc, transfer->amount, NULL, common->fee); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + if (transfer->has_payload) { + layoutNEMTransferPayload(transfer->payload.bytes, transfer->payload.size, + transfer->has_public_key); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + layoutNEMDialog(&bmp_icon_question, _("Cancel"), _("Confirm"), desc, + _("Confirm transfer to"), transfer->recipient); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + return false; + } + + return true; +} + +bool nem_fsmTransfer(nem_transaction_ctx *context, const HDNode *node, + const NEMTransactionCommon *common, + const NEMTransfer *transfer) { + static uint8_t + encrypted[NEM_ENCRYPTED_PAYLOAD_SIZE(sizeof(transfer->payload.bytes))]; + + const uint8_t *payload = transfer->payload.bytes; + size_t size = transfer->payload.size; + + if (transfer->has_public_key) { + if (node == NULL) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Private key unavailable for encrypted message")); + return false; + } + + random_buffer(encrypted, NEM_SALT_SIZE + AES_BLOCK_SIZE); + + const uint8_t *salt = encrypted; + const uint8_t *iv = &encrypted[NEM_SALT_SIZE]; + uint8_t *buffer = &encrypted[NEM_SALT_SIZE + AES_BLOCK_SIZE]; + + bool ret = hdnode_nem_encrypt(node, transfer->public_key.bytes, iv, salt, + payload, size, buffer); + + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to encrypt payload")); + return false; + } + + payload = encrypted; + size = NEM_ENCRYPTED_PAYLOAD_SIZE(size); + } + + bool ret = nem_transaction_create_transfer( + context, common->network, common->timestamp, NULL, common->fee, + common->deadline, transfer->recipient, transfer->amount, payload, size, + transfer->has_public_key, transfer->mosaics_count); + + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to create transfer transaction")); + return false; + } + + for (size_t i = 0; i < transfer->mosaics_count; i++) { + const NEMMosaic *mosaic = &transfer->mosaics[i]; + + ret = nem_transaction_write_mosaic(context, mosaic->namespace, + mosaic->mosaic, mosaic->quantity); + + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to attach mosaics")); + return false; + } + } + + return true; +} + +bool nem_askProvisionNamespace(const NEMTransactionCommon *common, + const NEMProvisionNamespace *provision_namespace, + const char *desc) { + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), desc, _("Create namespace"), + provision_namespace->namespace, + provision_namespace->has_parent ? _("under namespace") : NULL, + provision_namespace->has_parent ? provision_namespace->parent : NULL, + NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + layoutNEMNetworkFee(desc, true, _("Confirm rental fee of"), + provision_namespace->fee, _("and network fee of"), + common->fee); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + return false; + } + + return true; +} + +bool nem_fsmProvisionNamespace( + nem_transaction_ctx *context, const NEMTransactionCommon *common, + const NEMProvisionNamespace *provision_namespace) { + return nem_transaction_create_provision_namespace( + context, common->network, common->timestamp, NULL, common->fee, + common->deadline, provision_namespace->namespace, + provision_namespace->has_parent ? provision_namespace->parent : NULL, + provision_namespace->sink, provision_namespace->fee); +} + +bool nem_askMosaicCreation(const NEMTransactionCommon *common, + const NEMMosaicCreation *mosaic_creation, + const char *desc, const char *address) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), desc, + _("Create mosaic"), mosaic_creation->definition.mosaic, + _("under namespace"), mosaic_creation->definition.namespace, + NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + layoutNEMMosaicDescription(mosaic_creation->definition.description); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + char str_out[32]; + + bn_format_uint64(mosaic_creation->definition.supply, NULL, NULL, + mosaic_creation->definition.divisibility, + mosaic_creation->definition.divisibility, true, str_out, + sizeof(str_out)); + + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), _("Properties"), + mosaic_creation->definition.mutable_supply ? _("Mutable supply:") + : _("Immutable supply:"), + str_out, _("Mosaic will be"), + mosaic_creation->definition.transferable ? _("transferable") + : _("non-transferable"), + NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + if (mosaic_creation->definition.has_levy) { + layoutNEMLevy(&mosaic_creation->definition, common->network); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + if (strcmp(address, mosaic_creation->definition.levy_address) == 0) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), + _("Levy Recipient"), _("Levy will be paid to"), + _("yourself"), NULL, NULL, NULL, NULL); + } else { + layoutNEMDialog(&bmp_icon_question, _("Cancel"), _("Next"), + _("Levy Recipient"), _("Levy will be paid to"), + mosaic_creation->definition.levy_address); + } + + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + layoutNEMNetworkFee(desc, true, _("Confirm creation fee"), + mosaic_creation->fee, _("and network fee of"), + common->fee); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + return false; + } + + return true; +} + +bool nem_fsmMosaicCreation(nem_transaction_ctx *context, + const NEMTransactionCommon *common, + const NEMMosaicCreation *mosaic_creation) { + return nem_transaction_create_mosaic_creation( + context, common->network, common->timestamp, NULL, common->fee, + common->deadline, mosaic_creation->definition.namespace, + mosaic_creation->definition.mosaic, + mosaic_creation->definition.description, + mosaic_creation->definition.divisibility, + mosaic_creation->definition.supply, + mosaic_creation->definition.mutable_supply, + mosaic_creation->definition.transferable, + mosaic_creation->definition.levy, mosaic_creation->definition.fee, + mosaic_creation->definition.levy_address, + mosaic_creation->definition.levy_namespace, + mosaic_creation->definition.levy_mosaic, mosaic_creation->sink, + mosaic_creation->fee); +} + +bool nem_askSupplyChange(const NEMTransactionCommon *common, + const NEMMosaicSupplyChange *supply_change, + const char *desc) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), desc, + _("Modify supply for"), supply_change->mosaic, + _("under namespace"), supply_change->namespace, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + char str_out[32]; + bn_format_uint64(supply_change->delta, NULL, NULL, 0, 0, false, str_out, + sizeof(str_out)); + + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), desc, + supply_change->type == NEMSupplyChangeType_SupplyChange_Increase + ? _("Increase supply by") + : _("Decrease supply by"), + str_out, _("whole units"), NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + layoutNEMNetworkFee(desc, true, _("Confirm network fee"), common->fee, NULL, + 0); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + return false; + } + + return true; +} + +bool nem_fsmSupplyChange(nem_transaction_ctx *context, + const NEMTransactionCommon *common, + const NEMMosaicSupplyChange *supply_change) { + return nem_transaction_create_mosaic_supply_change( + context, common->network, common->timestamp, NULL, common->fee, + common->deadline, supply_change->namespace, supply_change->mosaic, + supply_change->type, supply_change->delta); +} + +bool nem_askAggregateModification( + const NEMTransactionCommon *common, + const NEMAggregateModification *aggregate_modification, const char *desc, + bool creation) { + if (creation) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), desc, + _("Convert account to"), _("multisig account?"), NULL, + NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + char address[NEM_ADDRESS_SIZE + 1]; + + for (size_t i = 0; i < aggregate_modification->modifications_count; i++) { + const NEMCosignatoryModification *modification = + &aggregate_modification->modifications[i]; + nem_get_address(modification->public_key.bytes, common->network, address); + + layoutNEMDialog( + &bmp_icon_question, _("Cancel"), _("Next"), desc, + modification->type == NEMModificationType_CosignatoryModification_Add + ? _("Add cosignatory") + : _("Remove cosignatory"), + address); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + int32_t relative_change = aggregate_modification->relative_change; + if (relative_change) { + char str_out[32]; + bn_format_uint64(relative_change < 0 ? -relative_change : relative_change, + NULL, NULL, 0, 0, false, str_out, sizeof(str_out)); + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Next"), desc, + creation ? _("Set minimum") + : (relative_change < 0 ? _("Decrease minimum") + : _("Increase minimum")), + creation ? _("cosignatories to") : _("cosignatories by"), + str_out, NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + } + + layoutNEMNetworkFee(desc, true, _("Confirm network fee"), common->fee, NULL, + 0); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + return false; + } + + return true; +} + +bool nem_fsmAggregateModification( + nem_transaction_ctx *context, const NEMTransactionCommon *common, + const NEMAggregateModification *aggregate_modification) { + bool ret = nem_transaction_create_aggregate_modification( + context, common->network, common->timestamp, NULL, common->fee, + common->deadline, aggregate_modification->modifications_count, + aggregate_modification->relative_change != 0); + if (!ret) return false; + + for (size_t i = 0; i < aggregate_modification->modifications_count; i++) { + const NEMCosignatoryModification *modification = + &aggregate_modification->modifications[i]; + + ret = nem_transaction_write_cosignatory_modification( + context, modification->type, modification->public_key.bytes); + if (!ret) return false; + } + + if (aggregate_modification->relative_change) { + ret = nem_transaction_write_minimum_cosignatories( + context, aggregate_modification->relative_change); + if (!ret) return false; + } + + return true; +} + +bool nem_askImportanceTransfer(const NEMTransactionCommon *common, + const NEMImportanceTransfer *importance_transfer, + const char *desc) { + layoutDialogSwipe( + &bmp_icon_question, _("Cancel"), _("Next"), desc, + importance_transfer->mode == + NEMImportanceTransferMode_ImportanceTransfer_Activate + ? _("Activate remote") + : _("Deactivate remote"), + _("harvesting?"), NULL, NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + layoutNEMNetworkFee(desc, true, _("Confirm network fee"), common->fee, NULL, + 0); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + return false; + } + + return true; +} + +bool nem_fsmImportanceTransfer( + nem_transaction_ctx *context, const NEMTransactionCommon *common, + const NEMImportanceTransfer *importance_transfer) { + return nem_transaction_create_importance_transfer( + context, common->network, common->timestamp, NULL, common->fee, + common->deadline, importance_transfer->mode, + importance_transfer->public_key.bytes); +} + +bool nem_askMultisig(const char *address, const char *desc, bool cosigning, + uint64_t fee) { + layoutNEMDialog( + &bmp_icon_question, _("Cancel"), _("Next"), desc, + cosigning ? _("Cosign transaction for") : _("Initiate transaction for"), + address); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + layoutNEMNetworkFee(desc, false, _("Confirm multisig fee"), fee, NULL, 0); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return false; + } + + return true; +} + +bool nem_fsmMultisig(nem_transaction_ctx *context, + const NEMTransactionCommon *common, + const nem_transaction_ctx *inner, bool cosigning) { + bool ret; + if (cosigning) { + ret = nem_transaction_create_multisig_signature( + context, common->network, common->timestamp, NULL, common->fee, + common->deadline, inner); + } else { + ret = nem_transaction_create_multisig(context, common->network, + common->timestamp, NULL, common->fee, + common->deadline, inner); + } + + if (!ret) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to create multisig transaction")); + return false; + } + + return true; +} + +const NEMMosaicDefinition *nem_mosaicByName(const char *namespace, + const char *mosaic, + uint8_t network) { + for (size_t i = 0; i < NEM_MOSAIC_DEFINITIONS_COUNT; i++) { + const NEMMosaicDefinition *definition = &NEM_MOSAIC_DEFINITIONS[i]; + + if (nem_mosaicMatches(definition, namespace, mosaic, network)) { + return definition; + } + } + + return NULL; +} + +static inline size_t format_amount(const NEMMosaicDefinition *definition, + const bignum256 *amnt, + const bignum256 *multiplier, int divisor, + char *str_out, size_t size) { + bignum256 val; + memcpy(&val, amnt, sizeof(bignum256)); + + if (multiplier) { + bn_multiply(multiplier, &val, &secp256k1.prime); + divisor += NEM_MOSAIC_DEFINITION_XEM->divisibility; + } + + return bn_format( + &val, NULL, + definition && definition->has_ticker ? definition->ticker : NULL, + definition && definition->has_divisibility ? definition->divisibility : 0, + -divisor, false, str_out, size); +} + +size_t nem_canonicalizeMosaics(NEMMosaic *mosaics, size_t mosaics_count) { + if (mosaics_count <= 1) { + return mosaics_count; + } + + size_t actual_count = 0; + + bool skip[mosaics_count]; + memzero(skip, sizeof(skip)); + + // Merge duplicates + for (size_t i = 0; i < mosaics_count; i++) { + if (skip[i]) continue; + + NEMMosaic *mosaic = &mosaics[actual_count]; + + if (actual_count++ != i) { + memcpy(mosaic, &mosaics[i], sizeof(NEMMosaic)); + } + + for (size_t j = i + 1; j < mosaics_count; j++) { + if (skip[j]) continue; + + const NEMMosaic *new_mosaic = &mosaics[j]; + + if (nem_mosaicCompare(mosaic, new_mosaic) == 0) { + skip[j] = true; + mosaic->quantity += new_mosaic->quantity; + } + } + } + + NEMMosaic temp; + + // Sort mosaics + for (size_t i = 0; i < actual_count - 1; i++) { + NEMMosaic *a = &mosaics[i]; + + for (size_t j = i + 1; j < actual_count; j++) { + NEMMosaic *b = &mosaics[j]; + + if (nem_mosaicCompare(a, b) > 0) { + memcpy(&temp, a, sizeof(NEMMosaic)); + memcpy(a, b, sizeof(NEMMosaic)); + memcpy(b, &temp, sizeof(NEMMosaic)); + } + } + } + + return actual_count; +} + +void nem_mosaicFormatAmount(const NEMMosaicDefinition *definition, + uint64_t quantity, const bignum256 *multiplier, + char *str_out, size_t size) { + bignum256 amnt; + bn_read_uint64(quantity, &amnt); + + format_amount(definition, &amnt, multiplier, 0, str_out, size); +} + +bool nem_mosaicFormatLevy(const NEMMosaicDefinition *definition, + uint64_t quantity, const bignum256 *multiplier, + uint8_t network, char *str_out, size_t size) { + if (!definition->has_levy || !definition->has_fee) { + return false; + } + + bignum256 amnt, fee; + bn_read_uint64(quantity, &amnt); + bn_read_uint64(definition->fee, &fee); + + const NEMMosaicDefinition *mosaic = nem_mosaicByName( + definition->levy_namespace, definition->levy_mosaic, network); + + switch (definition->levy) { + case NEMMosaicLevy_MosaicLevy_Absolute: + return format_amount(mosaic, &fee, NULL, 0, str_out, size); + + case NEMMosaicLevy_MosaicLevy_Percentile: + bn_multiply(&fee, &amnt, &secp256k1.prime); + return format_amount(mosaic, &amnt, multiplier, + NEM_LEVY_PERCENTILE_DIVISOR, str_out, size); + + default: + return false; + } +} diff --git a/legacy/firmware/nem2.h b/legacy/firmware/nem2.h new file mode 100644 index 0000000000..a4a9597a1c --- /dev/null +++ b/legacy/firmware/nem2.h @@ -0,0 +1,153 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __NEM2_H__ +#define __NEM2_H__ + +#include "nem.h" +#include "nem_mosaics.h" + +#include "messages-nem.pb.h" + +#include + +const char *nem_validate_common(NEMTransactionCommon *common, bool inner); +const char *nem_validate_transfer(const NEMTransfer *transfer, uint8_t network); +const char *nem_validate_provision_namespace( + const NEMProvisionNamespace *provision_namespace, uint8_t network); +const char *nem_validate_mosaic_creation( + const NEMMosaicCreation *mosaic_creation, uint8_t network); +const char *nem_validate_supply_change( + const NEMMosaicSupplyChange *supply_change); +const char *nem_validate_aggregate_modification( + const NEMAggregateModification *aggregate_modification, bool creation); +const char *nem_validate_importance_transfer( + const NEMImportanceTransfer *importance_transfer); + +bool nem_askTransfer(const NEMTransactionCommon *common, + const NEMTransfer *transfer, const char *desc); +bool nem_fsmTransfer(nem_transaction_ctx *context, const HDNode *node, + const NEMTransactionCommon *common, + const NEMTransfer *transfer); + +bool nem_askProvisionNamespace(const NEMTransactionCommon *common, + const NEMProvisionNamespace *provision_namespace, + const char *desc); +bool nem_fsmProvisionNamespace( + nem_transaction_ctx *context, const NEMTransactionCommon *common, + const NEMProvisionNamespace *provision_namespace); + +bool nem_askMosaicCreation(const NEMTransactionCommon *common, + const NEMMosaicCreation *mosaic_creation, + const char *desc, const char *address); +bool nem_fsmMosaicCreation(nem_transaction_ctx *context, + const NEMTransactionCommon *common, + const NEMMosaicCreation *mosaic_creation); + +bool nem_askSupplyChange(const NEMTransactionCommon *common, + const NEMMosaicSupplyChange *supply_change, + const char *desc); +bool nem_fsmSupplyChange(nem_transaction_ctx *context, + const NEMTransactionCommon *common, + const NEMMosaicSupplyChange *supply_change); + +bool nem_askAggregateModification( + const NEMTransactionCommon *common, + const NEMAggregateModification *aggregate_modification, const char *desc, + bool creation); +bool nem_fsmAggregateModification( + nem_transaction_ctx *context, const NEMTransactionCommon *common, + const NEMAggregateModification *aggregate_modification); + +bool nem_askImportanceTransfer(const NEMTransactionCommon *common, + const NEMImportanceTransfer *importance_transfer, + const char *desc); +bool nem_fsmImportanceTransfer( + nem_transaction_ctx *context, const NEMTransactionCommon *common, + const NEMImportanceTransfer *importance_transfer); + +bool nem_askMultisig(const char *address, const char *desc, bool cosigning, + uint64_t fee); +bool nem_fsmMultisig(nem_transaction_ctx *context, + const NEMTransactionCommon *common, + const nem_transaction_ctx *inner, bool cosigning); + +const NEMMosaicDefinition *nem_mosaicByName(const char *namespace, + const char *mosaic, + uint8_t network); + +size_t nem_canonicalizeMosaics(NEMMosaic *mosaics, size_t mosaics_count); +void nem_mosaicFormatAmount(const NEMMosaicDefinition *definition, + uint64_t quantity, const bignum256 *multiplier, + char *str_out, size_t size); +bool nem_mosaicFormatLevy(const NEMMosaicDefinition *definition, + uint64_t quantity, const bignum256 *multiplier, + uint8_t network, char *str_out, size_t size); + +static inline void nem_mosaicFormatName(const char *namespace, + const char *mosaic, char *str_out, + size_t size) { + strlcpy(str_out, namespace, size); + strlcat(str_out, ".", size); + strlcat(str_out, mosaic, size); +} + +static inline bool nem_mosaicMatches(const NEMMosaicDefinition *definition, + const char *namespace, const char *mosaic, + uint8_t network) { + if (strcmp(namespace, definition->namespace) == 0 && + strcmp(mosaic, definition->mosaic) == 0) { + if (definition->networks_count == 0) { + return true; + } + + for (size_t i = 0; i < definition->networks_count; i++) { + if (definition->networks[i] == network) { + return true; + } + } + } + + return false; +} + +static inline int nem_mosaicCompare(const NEMMosaic *a, const NEMMosaic *b) { + size_t namespace_length = strlen(a->namespace); + + // Ensure that strlen(a->namespace) <= strlen(b->namespace) + if (namespace_length > strlen(b->namespace)) { + return -nem_mosaicCompare(b, a); + } + + int r = strncmp(a->namespace, b->namespace, namespace_length); + + if (r == 0 && b->namespace[namespace_length] != '\0') { + // The next character would be the separator + r = (':' - b->namespace[namespace_length]); + } + + if (r == 0) { + // Finally compare the mosaic + r = strcmp(a->mosaic, b->mosaic); + } + + return r; +} + +#endif diff --git a/legacy/firmware/nem_mosaics.c.mako b/legacy/firmware/nem_mosaics.c.mako new file mode 100644 index 0000000000..fffde0499d --- /dev/null +++ b/legacy/firmware/nem_mosaics.c.mako @@ -0,0 +1,36 @@ +<% +ATTRIBUTES = ( + ("name", c_str), + ("ticker", lambda s: c_str(" " + s) if s else "NULL"), + ("namespace", c_str), + ("mosaic", c_str), + ("divisibility", int), + ("levy", lambda s: "NEMMosaicLevy_" + s), + ("fee", int), + ("levy_namespace", c_str), + ("levy_mosaic", c_str), +) +%>\ +// This file is automatically generated from nem_mosaics.c.mako +// DO NOT EDIT + +#include "nem_mosaics.h" + +const NEMMosaicDefinition NEM_MOSAIC_DEFINITIONS[NEM_MOSAIC_DEFINITIONS_COUNT] = { +% for m in supported_on("trezor1", nem): +{ + % for attr, func in ATTRIBUTES: + % if attr in m: + .has_${attr} = true, + .${attr} = ${func(m[attr])}, + % endif + % endfor + % if "networks" in m: + .networks_count = ${len(m["networks"])}, + .networks = { ${", ".join(map(str, m["networks"]))} }, + % endif +}, +% endfor +}; + +const NEMMosaicDefinition *NEM_MOSAIC_DEFINITION_XEM = NEM_MOSAIC_DEFINITIONS; diff --git a/legacy/firmware/nem_mosaics.h.mako b/legacy/firmware/nem_mosaics.h.mako new file mode 100644 index 0000000000..5667713b9c --- /dev/null +++ b/legacy/firmware/nem_mosaics.h.mako @@ -0,0 +1,15 @@ +// This file is automatically generated from nem_mosaics.h.mako +// DO NOT EDIT + +#ifndef __NEM_MOSAICS_H__ +#define __NEM_MOSAICS_H__ + +#include "messages-nem.pb.h" + +<% nem_list = list(supported_on("trezor1", nem)) %>\ +#define NEM_MOSAIC_DEFINITIONS_COUNT (${len(nem_list)}) + +extern const NEMMosaicDefinition NEM_MOSAIC_DEFINITIONS[NEM_MOSAIC_DEFINITIONS_COUNT]; +extern const NEMMosaicDefinition *NEM_MOSAIC_DEFINITION_XEM; + +#endif diff --git a/legacy/firmware/otp.c b/legacy/firmware/otp.c new file mode 100644 index 0000000000..195b977697 --- /dev/null +++ b/legacy/firmware/otp.c @@ -0,0 +1,67 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2019 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "otp.h" +#include + +#define FLASH_OTP_BASE 0x1FFF7800U +#define FLASH_OTP_LOCK_BASE 0x1FFF7A00U + +bool flash_otp_is_locked(uint8_t block) { + return 0x00 == *(volatile uint8_t *)(FLASH_OTP_LOCK_BASE + block); +} + +bool flash_otp_lock(uint8_t block) { + if (block >= FLASH_OTP_NUM_BLOCKS) { + return false; + } + flash_unlock(); + flash_program_byte(FLASH_OTP_LOCK_BASE + block, 0x00); + flash_lock(); + return true; +} + +bool flash_otp_read(uint8_t block, uint8_t offset, uint8_t *data, + uint8_t datalen) { + if (block >= FLASH_OTP_NUM_BLOCKS || + offset + datalen > FLASH_OTP_BLOCK_SIZE) { + return false; + } + for (uint8_t i = 0; i < datalen; i++) { + data[i] = *(volatile uint8_t *)(FLASH_OTP_BASE + + block * FLASH_OTP_BLOCK_SIZE + offset + i); + } + return true; +} + +bool flash_otp_write(uint8_t block, uint8_t offset, const uint8_t *data, + uint8_t datalen) { + if (block >= FLASH_OTP_NUM_BLOCKS || + offset + datalen > FLASH_OTP_BLOCK_SIZE) { + return false; + } + flash_unlock(); + for (uint8_t i = 0; i < datalen; i++) { + uint32_t address = + FLASH_OTP_BASE + block * FLASH_OTP_BLOCK_SIZE + offset + i; + flash_program_byte(address, data[i]); + } + flash_lock(); + return true; +} diff --git a/legacy/firmware/otp.h b/legacy/firmware/otp.h new file mode 100644 index 0000000000..d329f94251 --- /dev/null +++ b/legacy/firmware/otp.h @@ -0,0 +1,38 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2019 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __OTP_H__ +#define __OTP_H__ + +#include +#include + +#define FLASH_OTP_NUM_BLOCKS 16 +#define FLASH_OTP_BLOCK_SIZE 32 + +#define FLASH_OTP_BLOCK_RANDOMNESS 3 + +bool flash_otp_is_locked(uint8_t block); +bool flash_otp_lock(uint8_t block); +bool flash_otp_read(uint8_t block, uint8_t offset, uint8_t *data, + uint8_t datalen); +bool flash_otp_write(uint8_t block, uint8_t offset, const uint8_t *data, + uint8_t datalen); + +#endif diff --git a/legacy/firmware/pinmatrix.c b/legacy/firmware/pinmatrix.c new file mode 100644 index 0000000000..f45fa20244 --- /dev/null +++ b/legacy/firmware/pinmatrix.c @@ -0,0 +1,78 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "layout2.h" +#include "oled.h" +#include "pinmatrix.h" +#include "rng.h" + +static char pinmatrix_perm[10] = "XXXXXXXXX"; + +void pinmatrix_draw(const char *text) { + const BITMAP *bmp_digits[10] = { + &bmp_digit0, &bmp_digit1, &bmp_digit2, &bmp_digit3, &bmp_digit4, + &bmp_digit5, &bmp_digit6, &bmp_digit7, &bmp_digit8, &bmp_digit9, + }; + layoutSwipe(); + const int w = bmp_digit0.width, h = bmp_digit0.height, pad = 2; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + // use (2 - j) instead of j to achieve 789456123 layout + int k = pinmatrix_perm[i + (2 - j) * 3] - '0'; + if (text) { + oledDrawStringCenter(OLED_WIDTH / 2, 0, text, FONT_STANDARD); + } + oledDrawBitmap((OLED_WIDTH - 3 * w - 2 * pad) / 2 + i * (w + pad), + OLED_HEIGHT - 3 * h - 2 * pad + j * (h + pad), + bmp_digits[k]); + } + } + oledRefresh(); +} + +void pinmatrix_start(const char *text) { + for (int i = 0; i < 9; i++) { + pinmatrix_perm[i] = '1' + i; + } + pinmatrix_perm[9] = 0; + random_permute(pinmatrix_perm, 9); + pinmatrix_draw(text); +} + +void pinmatrix_done(char *pin) { + int k, i = 0; + while (pin && pin[i]) { + k = pin[i] - '1'; + if (k >= 0 && k <= 8) { + pin[i] = pinmatrix_perm[k]; + } else { + pin[i] = 'X'; + } + i++; + } + memset(pinmatrix_perm, 'X', sizeof(pinmatrix_perm) - 1); +} + +#if DEBUG_LINK + +const char *pinmatrix_get(void) { return pinmatrix_perm; } + +#endif diff --git a/legacy/firmware/pinmatrix.h b/legacy/firmware/pinmatrix.h new file mode 100644 index 0000000000..a1c70bd9f7 --- /dev/null +++ b/legacy/firmware/pinmatrix.h @@ -0,0 +1,27 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __PINMATRIX_H__ +#define __PINMATRIX_H__ + +void pinmatrix_start(const char *text); +void pinmatrix_done(char *pin); +const char *pinmatrix_get(void); + +#endif diff --git a/legacy/firmware/protect.c b/legacy/firmware/protect.c new file mode 100644 index 0000000000..7617d2ab2c --- /dev/null +++ b/legacy/firmware/protect.c @@ -0,0 +1,320 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "protect.h" +#include "buttons.h" +#include "config.h" +#include "debug.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "memory.h" +#include "memzero.h" +#include "messages.h" +#include "messages.pb.h" +#include "oled.h" +#include "pinmatrix.h" +#include "usb.h" +#include "util.h" + +#define MAX_WRONG_PINS 15 + +bool protectAbortedByCancel = false; +bool protectAbortedByInitialize = false; + +bool protectButton(ButtonRequestType type, bool confirm_only) { + ButtonRequest resp; + bool result = false; + bool acked = false; +#if DEBUG_LINK + bool debug_decided = false; +#endif + + memzero(&resp, sizeof(ButtonRequest)); + resp.has_code = true; + resp.code = type; + usbTiny(1); + buttonUpdate(); // Clear button state + msg_write(MessageType_MessageType_ButtonRequest, &resp); + + for (;;) { + usbPoll(); + + // check for ButtonAck + if (msg_tiny_id == MessageType_MessageType_ButtonAck) { + msg_tiny_id = 0xFFFF; + acked = true; + } + + // button acked - check buttons + if (acked) { + usbSleep(5); + buttonUpdate(); + if (button.YesUp) { + result = true; + break; + } + if (!confirm_only && button.NoUp) { + result = false; + break; + } + } + + // check for Cancel / Initialize + protectAbortedByCancel = (msg_tiny_id == MessageType_MessageType_Cancel); + protectAbortedByInitialize = + (msg_tiny_id == MessageType_MessageType_Initialize); + if (protectAbortedByCancel || protectAbortedByInitialize) { + msg_tiny_id = 0xFFFF; + result = false; + break; + } + +#if DEBUG_LINK + // check DebugLink + if (msg_tiny_id == MessageType_MessageType_DebugLinkDecision) { + msg_tiny_id = 0xFFFF; + DebugLinkDecision *dld = (DebugLinkDecision *)msg_tiny; + result = dld->yes_no; + debug_decided = true; + } + + if (acked && debug_decided) { + break; + } + + if (msg_tiny_id == MessageType_MessageType_DebugLinkGetState) { + msg_tiny_id = 0xFFFF; + fsm_msgDebugLinkGetState((DebugLinkGetState *)msg_tiny); + } +#endif + } + + usbTiny(0); + + return result; +} + +const char *requestPin(PinMatrixRequestType type, const char *text) { + PinMatrixRequest resp; + memzero(&resp, sizeof(PinMatrixRequest)); + resp.has_type = true; + resp.type = type; + usbTiny(1); + msg_write(MessageType_MessageType_PinMatrixRequest, &resp); + pinmatrix_start(text); + for (;;) { + usbPoll(); + if (msg_tiny_id == MessageType_MessageType_PinMatrixAck) { + msg_tiny_id = 0xFFFF; + PinMatrixAck *pma = (PinMatrixAck *)msg_tiny; + pinmatrix_done(pma->pin); // convert via pinmatrix + usbTiny(0); + return pma->pin; + } + // check for Cancel / Initialize + protectAbortedByCancel = (msg_tiny_id == MessageType_MessageType_Cancel); + protectAbortedByInitialize = + (msg_tiny_id == MessageType_MessageType_Initialize); + if (protectAbortedByCancel || protectAbortedByInitialize) { + pinmatrix_done(0); + msg_tiny_id = 0xFFFF; + usbTiny(0); + return 0; + } +#if DEBUG_LINK + if (msg_tiny_id == MessageType_MessageType_DebugLinkGetState) { + msg_tiny_id = 0xFFFF; + fsm_msgDebugLinkGetState((DebugLinkGetState *)msg_tiny); + } +#endif + } +} + +secbool protectPinUiCallback(uint32_t wait, uint32_t progress, + const char *message) { + // Convert wait to secstr string. + char secstrbuf[] = _("________0 seconds"); + char *secstr = secstrbuf + 9; + uint32_t secs = wait; + do { + secstr--; + *secstr = (secs % 10) + '0'; + secs /= 10; + } while (secs > 0 && secstr >= secstrbuf); + if (wait == 1) { + // Change "seconds" to "second". + secstrbuf[16] = 0; + } + oledClear(); + oledDrawStringCenter(OLED_WIDTH / 2, 0 * 9, message, FONT_STANDARD); + oledDrawStringCenter(OLED_WIDTH / 2, 2 * 9, _("Please wait"), FONT_STANDARD); + oledDrawStringCenter(OLED_WIDTH / 2, 3 * 9, secstr, FONT_STANDARD); + oledDrawStringCenter(OLED_WIDTH / 2, 4 * 9, _("to continue ..."), + FONT_STANDARD); + // progressbar + oledFrame(0, OLED_HEIGHT - 8, OLED_WIDTH - 1, OLED_HEIGHT - 1); + oledBox(1, OLED_HEIGHT - 7, OLED_WIDTH - 2, OLED_HEIGHT - 2, 0); + progress = progress * (OLED_WIDTH - 4) / 1000; + if (progress > OLED_WIDTH - 4) { + progress = OLED_WIDTH - 4; + } + oledBox(2, OLED_HEIGHT - 6, 1 + progress, OLED_HEIGHT - 3, 1); + oledRefresh(); + // Check for Cancel / Initialize. + protectAbortedByCancel = (msg_tiny_id == MessageType_MessageType_Cancel); + protectAbortedByInitialize = + (msg_tiny_id == MessageType_MessageType_Initialize); + if (protectAbortedByCancel || protectAbortedByInitialize) { + msg_tiny_id = 0xFFFF; + usbTiny(0); + fsm_sendFailure(FailureType_Failure_PinCancelled, NULL); + return sectrue; + } + + return secfalse; +} + +bool protectPin(bool use_cached) { + if (use_cached && session_isUnlocked()) { + return true; + } + + const char *pin = ""; + if (config_hasPin()) { + pin = requestPin(PinMatrixRequestType_PinMatrixRequestType_Current, + _("Please enter current PIN:")); + if (!pin) { + fsm_sendFailure(FailureType_Failure_PinCancelled, NULL); + return false; + } + } + + bool ret = config_unlock(pin); + if (!ret) { + fsm_sendFailure(FailureType_Failure_PinInvalid, NULL); + } + return ret; +} + +bool protectChangePin(bool removal) { + static CONFIDENTIAL char old_pin[MAX_PIN_LEN + 1] = ""; + static CONFIDENTIAL char new_pin[MAX_PIN_LEN + 1] = ""; + const char *pin = NULL; + + if (config_hasPin()) { + pin = requestPin(PinMatrixRequestType_PinMatrixRequestType_Current, + _("Please enter current PIN:")); + if (pin == NULL) { + fsm_sendFailure(FailureType_Failure_PinCancelled, NULL); + return false; + } + + // If removing, defer the check to config_changePin(). + if (!removal) { + usbTiny(1); + bool ret = config_unlock(pin); + usbTiny(0); + if (ret == false) { + fsm_sendFailure(FailureType_Failure_PinInvalid, NULL); + return false; + } + } + + strlcpy(old_pin, pin, sizeof(old_pin)); + } + + if (!removal) { + pin = requestPin(PinMatrixRequestType_PinMatrixRequestType_NewFirst, + _("Please enter new PIN:")); + if (pin == NULL) { + memzero(old_pin, sizeof(old_pin)); + fsm_sendFailure(FailureType_Failure_PinCancelled, NULL); + return false; + } + strlcpy(new_pin, pin, sizeof(new_pin)); + + pin = requestPin(PinMatrixRequestType_PinMatrixRequestType_NewSecond, + _("Please re-enter new PIN:")); + if (pin == NULL) { + memzero(old_pin, sizeof(old_pin)); + memzero(new_pin, sizeof(new_pin)); + fsm_sendFailure(FailureType_Failure_PinCancelled, NULL); + return false; + } + + if (strncmp(new_pin, pin, sizeof(new_pin)) != 0) { + memzero(old_pin, sizeof(old_pin)); + memzero(new_pin, sizeof(new_pin)); + fsm_sendFailure(FailureType_Failure_PinMismatch, NULL); + return false; + } + } + + bool ret = config_changePin(old_pin, new_pin); + memzero(old_pin, sizeof(old_pin)); + memzero(new_pin, sizeof(new_pin)); + if (ret == false) { + fsm_sendFailure(FailureType_Failure_PinInvalid, NULL); + } + return ret; +} + +bool protectPassphrase(void) { + bool passphrase_protection = false; + config_getPassphraseProtection(&passphrase_protection); + if (!passphrase_protection || session_isPassphraseCached()) { + return true; + } + + PassphraseRequest resp; + memzero(&resp, sizeof(PassphraseRequest)); + usbTiny(1); + msg_write(MessageType_MessageType_PassphraseRequest, &resp); + + layoutDialogSwipe(&bmp_icon_info, NULL, NULL, NULL, _("Please enter your"), + _("passphrase using"), _("the computer's"), _("keyboard."), + NULL, NULL); + + bool result; + for (;;) { + usbPoll(); + // TODO: correctly process PassphraseAck with state field set (mismatch => + // Failure) + if (msg_tiny_id == MessageType_MessageType_PassphraseAck) { + msg_tiny_id = 0xFFFF; + PassphraseAck *ppa = (PassphraseAck *)msg_tiny; + session_cachePassphrase(ppa->has_passphrase ? ppa->passphrase : ""); + result = true; + break; + } + // check for Cancel / Initialize + protectAbortedByCancel = (msg_tiny_id == MessageType_MessageType_Cancel); + protectAbortedByInitialize = + (msg_tiny_id == MessageType_MessageType_Initialize); + if (protectAbortedByCancel || protectAbortedByInitialize) { + msg_tiny_id = 0xFFFF; + result = false; + break; + } + } + usbTiny(0); + layoutHome(); + return result; +} diff --git a/legacy/firmware/protect.h b/legacy/firmware/protect.h new file mode 100644 index 0000000000..b948d8e6f8 --- /dev/null +++ b/legacy/firmware/protect.h @@ -0,0 +1,37 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __PROTECT_H__ +#define __PROTECT_H__ + +#include +#include "messages-common.pb.h" +#include "secbool.h" + +bool protectButton(ButtonRequestType type, bool confirm_only); +secbool protectPinUiCallback(uint32_t wait, uint32_t progress, + const char* message); +bool protectPin(bool use_cached); +bool protectChangePin(bool removal); +bool protectPassphrase(void); + +extern bool protectAbortedByCancel; +extern bool protectAbortedByInitialize; + +#endif diff --git a/legacy/firmware/protob/.gitignore b/legacy/firmware/protob/.gitignore new file mode 100644 index 0000000000..9d1fced364 --- /dev/null +++ b/legacy/firmware/protob/.gitignore @@ -0,0 +1,8 @@ +*.pb +*_pb2.py +*.pb.c +*.pb.h +*.pyc +messages_map.h +messages_map_limits.h +__pycache__/ diff --git a/legacy/firmware/protob/Makefile b/legacy/firmware/protob/Makefile new file mode 100644 index 0000000000..e456572d5f --- /dev/null +++ b/legacy/firmware/protob/Makefile @@ -0,0 +1,32 @@ +ifneq ($(V),1) +Q := @ +endif + +all: messages_map.h messages_map_limits.h messages-bitcoin.pb.c messages-common.pb.c messages-crypto.pb.c messages-debug.pb.c messages-ethereum.pb.c messages-management.pb.c messages-nem.pb.c messages.pb.c messages-stellar.pb.c messages-lisk.pb.c messages_nem_pb2.py + +PYTHON ?= python + +%.pb.c: %.pb %.options + @printf " NANOPB $@\n" + $(Q)$(PYTHON) ../../vendor/nanopb/generator/nanopb_generator.py $< \ + -L '#include "%s"' \ + -T \ + -s "mangle_names:M_FLATTEN" + +%.pb: %.proto + @printf " PROTOC $@\n" + $(Q)protoc -I/usr/include -I. $< -o $@ + +messages_%_pb2.py: messages-%.proto + @printf " PROTOC $@\n" + $(Q)protoc -I/usr/include -I. $< --python_out=. + +%_pb2.py: %.proto + @printf " PROTOC $@\n" + $(Q)protoc -I/usr/include -I. $< --python_out=. + +messages_map.h messages_map_limits.h: messages_map.py messages_pb2.py + $(Q)$(PYTHON) $< Cardano Tezos Ripple Monero DebugMonero Ontology Tron Eos Binance + +clean: + rm -f *.pb *.o *.d *.pb.c *.pb.h *_pb2.py messages_map.h messages_map_limits.h diff --git a/legacy/firmware/protob/messages-bitcoin.options b/legacy/firmware/protob/messages-bitcoin.options new file mode 100644 index 0000000000..f04d01f93e --- /dev/null +++ b/legacy/firmware/protob/messages-bitcoin.options @@ -0,0 +1,53 @@ +GetPublicKey.address_n max_count:8 +GetPublicKey.ecdsa_curve_name max_size:32 +GetPublicKey.coin_name max_size:21 + +PublicKey.xpub max_size:113 + +GetAddress.address_n max_count:8 +GetAddress.coin_name max_size:21 + +Address.address max_size:130 + +SignTx.coin_name max_size:21 + +SignMessage.address_n max_count:8 +SignMessage.message max_size:1024 +SignMessage.coin_name max_size:21 + +VerifyMessage.address max_size:130 +VerifyMessage.signature max_size:65 +VerifyMessage.message max_size:1024 +VerifyMessage.coin_name max_size:21 + +MessageSignature.address max_size:130 +MessageSignature.signature max_size:65 + +TransactionType.inputs max_count:1 +TransactionType.bin_outputs max_count:1 +TransactionType.outputs max_count:1 +TransactionType.extra_data max_size:1024 + +TxInputType.address_n max_count:8 +TxInputType.prev_hash max_size:32 +TxInputType.script_sig max_size:1650 +TxInputType.prev_block_hash_bip115 max_size:32 + +TxOutputType.address max_size:130 +TxOutputType.address_n max_count:8 +TxOutputType.op_return_data max_size:80 +TxOutputType.block_hash_bip115 max_size:32 + +TxOutputBinType.script_pubkey max_size:520 + +TxRequestDetailsType.tx_hash max_size:32 + +TxRequestSerializedType.signature max_size:73 +TxRequestSerializedType.serialized_tx max_size:2048 + +MultisigRedeemScriptType.pubkeys max_count:15 +MultisigRedeemScriptType.signatures max_count:15 max_size:73 +MultisigRedeemScriptType.nodes max_count:15 +MultisigRedeemScriptType.address_n max_count:8 + +HDNodePathType.address_n max_count:8 diff --git a/legacy/firmware/protob/messages-bitcoin.proto b/legacy/firmware/protob/messages-bitcoin.proto new file mode 120000 index 0000000000..5775266a0a --- /dev/null +++ b/legacy/firmware/protob/messages-bitcoin.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-bitcoin.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-common.options b/legacy/firmware/protob/messages-common.options new file mode 100644 index 0000000000..31a54c3fa0 --- /dev/null +++ b/legacy/firmware/protob/messages-common.options @@ -0,0 +1,16 @@ +Success.message max_size:256 + +Failure.message max_size:256 + +ButtonRequest.data max_size:256 + +PinMatrixAck.pin max_size:10 + +PassphraseAck.passphrase max_size:51 +PassphraseAck.state max_size:64 + +PassphraseStateRequest.state max_size:64 + +HDNodeType.chain_code max_size:32 +HDNodeType.private_key max_size:32 +HDNodeType.public_key max_size:33 diff --git a/legacy/firmware/protob/messages-common.proto b/legacy/firmware/protob/messages-common.proto new file mode 120000 index 0000000000..b16f68e613 --- /dev/null +++ b/legacy/firmware/protob/messages-common.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-common.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-crypto.options b/legacy/firmware/protob/messages-crypto.options new file mode 100644 index 0000000000..5825b4d350 --- /dev/null +++ b/legacy/firmware/protob/messages-crypto.options @@ -0,0 +1,38 @@ +CipherKeyValue.address_n max_count:8 +CipherKeyValue.key max_size:256 +CipherKeyValue.value max_size:1024 +CipherKeyValue.iv max_size:16 + +CipheredKeyValue.value max_size:1024 + +CosiCommit.address_n max_count:8 +CosiCommit.data max_size:32 + +CosiCommitment.commitment max_size:32 +CosiCommitment.pubkey max_size:32 + +CosiSign.address_n max_count:8 +CosiSign.data max_size:32 +CosiSign.global_commitment max_size:32 +CosiSign.global_pubkey max_size:32 + +CosiSignature.signature max_size:32 + +SignIdentity.challenge_hidden max_size:256 +SignIdentity.challenge_visual max_size:256 +SignIdentity.ecdsa_curve_name max_size:32 + +SignedIdentity.address max_size:130 +SignedIdentity.public_key max_size:33 +SignedIdentity.signature max_size:65 + +IdentityType.proto max_size:9 +IdentityType.user max_size:64 +IdentityType.host max_size:64 +IdentityType.port max_size:6 +IdentityType.path max_size:256 + +GetECDHSessionKey.peer_public_key max_size:65 +GetECDHSessionKey.ecdsa_curve_name max_size:32 + +ECDHSessionKey.session_key max_size:65 diff --git a/legacy/firmware/protob/messages-crypto.proto b/legacy/firmware/protob/messages-crypto.proto new file mode 120000 index 0000000000..9461d737a3 --- /dev/null +++ b/legacy/firmware/protob/messages-crypto.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-crypto.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-debug.options b/legacy/firmware/protob/messages-debug.options new file mode 100644 index 0000000000..0561336225 --- /dev/null +++ b/legacy/firmware/protob/messages-debug.options @@ -0,0 +1,15 @@ +DebugLinkDecision.input max_size:33 + +DebugLinkState.layout max_size:1024 +DebugLinkState.pin max_size:10 +DebugLinkState.matrix max_size:10 +DebugLinkState.mnemonic_secret max_size:240 +DebugLinkState.reset_word max_size:12 +DebugLinkState.reset_entropy max_size:128 +DebugLinkState.recovery_fake_word max_size:12 + +DebugLinkLog.bucket max_size:33 +DebugLinkLog.text max_size:256 + +DebugLinkMemory.memory max_size:1024 +DebugLinkMemoryWrite.memory max_size:1024 diff --git a/legacy/firmware/protob/messages-debug.proto b/legacy/firmware/protob/messages-debug.proto new file mode 120000 index 0000000000..52a032f939 --- /dev/null +++ b/legacy/firmware/protob/messages-debug.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-debug.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-ethereum.options b/legacy/firmware/protob/messages-ethereum.options new file mode 100644 index 0000000000..12f658f068 --- /dev/null +++ b/legacy/firmware/protob/messages-ethereum.options @@ -0,0 +1,28 @@ +EthereumSignTx.address_n max_count:8 +EthereumSignTx.nonce max_size:32 +EthereumSignTx.gas_price max_size:32 +EthereumSignTx.gas_limit max_size:32 +EthereumSignTx.to max_size:43 +EthereumSignTx.value max_size:32 +EthereumSignTx.data_initial_chunk max_size:1024 + +EthereumTxRequest.signature_r max_size:32 +EthereumTxRequest.signature_s max_size:32 + +EthereumTxAck.data_chunk max_size:1024 + +EthereumSignMessage.address_n max_count:8 +EthereumSignMessage.message max_size:1024 + +EthereumVerifyMessage.address max_size:43 +EthereumVerifyMessage.signature max_size:65 +EthereumVerifyMessage.message max_size:1024 + +EthereumMessageSignature.address max_size:43 +EthereumMessageSignature.signature max_size:65 + +EthereumGetAddress.address_n max_count:8 +EthereumGetPublicKey.address_n max_count:8 + +EthereumAddress.address max_size:43 +EthereumPublicKey.xpub max_size:113 diff --git a/legacy/firmware/protob/messages-ethereum.proto b/legacy/firmware/protob/messages-ethereum.proto new file mode 120000 index 0000000000..1795e5e496 --- /dev/null +++ b/legacy/firmware/protob/messages-ethereum.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-ethereum.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-lisk.options b/legacy/firmware/protob/messages-lisk.options new file mode 100644 index 0000000000..489c2ac685 --- /dev/null +++ b/legacy/firmware/protob/messages-lisk.options @@ -0,0 +1,30 @@ +LiskGetAddress.address_n max_count:8 +LiskAddress.address max_size:23 + +LiskGetPublicKey.address_n max_count:8 +LiskPublicKey.public_key max_size:32 + +LiskSignMessage.address_n max_count:8 +LiskSignMessage.message max_size:1024 +LiskMessageSignature.public_key max_size:32 +LiskMessageSignature.signature max_size:64 + +LiskVerifyMessage.public_key max_size:33 +LiskVerifyMessage.message max_size:1024 +LiskVerifyMessage.signature max_size:65 + +LiskSignTx.address_n max_count:8 + +LiskSignedTx.signature max_size:64 + +LiskTransactionCommon.recipient_id max_size:23 +LiskTransactionCommon.sender_public_key max_size:32 +LiskTransactionCommon.requester_public_key max_size:32 +LiskTransactionCommon.signature max_size:64 + +LiskTransactionAsset.data max_size:64 +LiskTransactionAsset.votes max_count:33 max_size:66 + +LiskSignatureType.public_key max_size:32 +LiskDelegateType.username max_size:20 +LiskMultisignatureType.keys_group max_count:10 max_size:66 diff --git a/legacy/firmware/protob/messages-lisk.proto b/legacy/firmware/protob/messages-lisk.proto new file mode 120000 index 0000000000..9214fc8b26 --- /dev/null +++ b/legacy/firmware/protob/messages-lisk.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-lisk.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-management.options b/legacy/firmware/protob/messages-management.options new file mode 100644 index 0000000000..b3ee14605d --- /dev/null +++ b/legacy/firmware/protob/messages-management.options @@ -0,0 +1,34 @@ +Initialize.state max_size:64 + +Features.vendor max_size:33 +Features.device_id max_size:25 +Features.language max_size:17 +Features.label max_size:33 +Features.revision max_size:20 +Features.bootloader_hash max_size:32 +Features.model max_size:17 +Features.fw_vendor max_size:256 +Features.fw_vendor_keys max_size:32 + +ApplySettings.language max_size:17 +ApplySettings.label max_size:33 +ApplySettings.homescreen max_size:1024 + +Ping.message max_size:256 + +LoadDevice.mnemonic max_size:241 +LoadDevice.pin max_size:10 +LoadDevice.language max_size:17 +LoadDevice.label max_size:33 + +ResetDevice.language max_size:17 +ResetDevice.label max_size:33 + +Entropy.entropy max_size:1024 + +EntropyAck.entropy max_size:128 + +RecoveryDevice.language max_size:17 +RecoveryDevice.label max_size:33 + +WordAck.word max_size:12 diff --git a/legacy/firmware/protob/messages-management.proto b/legacy/firmware/protob/messages-management.proto new file mode 120000 index 0000000000..046a2a6eb4 --- /dev/null +++ b/legacy/firmware/protob/messages-management.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-management.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-nem.options b/legacy/firmware/protob/messages-nem.options new file mode 100644 index 0000000000..2d7be939fe --- /dev/null +++ b/legacy/firmware/protob/messages-nem.options @@ -0,0 +1,48 @@ +NEMGetAddress.address_n max_count:8 + +NEMAddress.address max_size:41 + +NEMDecryptMessage.address_n max_count:8 +NEMDecryptMessage.public_key max_size:32 +NEMDecryptMessage.payload max_size:1072 + +NEMDecryptedMessage.payload max_size:1024 + +NEMTransactionCommon.address_n max_count:8 +NEMTransactionCommon.signer max_size:32 + +NEMTransfer.recipient max_size:41 +NEMTransfer.public_key max_size:32 +NEMTransfer.payload max_size:1024 +NEMTransfer.mosaics max_count:16 + +NEMMosaic.namespace max_size:145 +NEMMosaic.mosaic max_size:33 + +NEMProvisionNamespace.namespace max_size:65 +NEMProvisionNamespace.parent max_size:81 +NEMProvisionNamespace.sink max_size:41 + +NEMMosaicCreation.sink max_size:41 + +NEMMosaicDefinition.name max_size:32 +NEMMosaicDefinition.ticker max_size:16 +NEMMosaicDefinition.namespace max_size:145 +NEMMosaicDefinition.mosaic max_size:33 +NEMMosaicDefinition.levy_address max_size:41 +NEMMosaicDefinition.levy_namespace max_size:145 +NEMMosaicDefinition.levy_mosaic max_size:33 +NEMMosaicDefinition.description max_size:513 +NEMMosaicDefinition.networks max_count:8 + +NEMMosaicSupplyChange.namespace max_size:145 +NEMMosaicSupplyChange.mosaic max_size:33 + +NEMAggregateModification.modifications max_count:16 + +NEMCosignatoryModification.public_key max_size:32 + +NEMImportanceTransfer.public_key max_size:32 + +NEMSignedTx.data max_size:2048 +NEMSignedTx.signature max_size:64 diff --git a/legacy/firmware/protob/messages-nem.proto b/legacy/firmware/protob/messages-nem.proto new file mode 120000 index 0000000000..228ff1b068 --- /dev/null +++ b/legacy/firmware/protob/messages-nem.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-nem.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages-stellar.options b/legacy/firmware/protob/messages-stellar.options new file mode 100644 index 0000000000..ce3de65c22 --- /dev/null +++ b/legacy/firmware/protob/messages-stellar.options @@ -0,0 +1,50 @@ +StellarAssetType.code max_size:13 +StellarAssetType.issuer max_size:57 +StellarGetAddress.address_n max_count:10 + +StellarAddress.address max_size:57 + + +StellarSignTx.source_account max_size:57 +StellarSignTx.address_n max_count:10 +StellarSignTx.network_passphrase max_size:1024 +StellarSignTx.memo_text max_size:29 +StellarSignTx.memo_hash max_size:32 + +StellarPaymentOp.source_account max_size:57 +StellarPaymentOp.destination_account max_size:57 + +StellarCreateAccountOp.source_account max_size:57 +StellarCreateAccountOp.new_account max_size:57 + +StellarPathPaymentOp.source_account max_size:57 +StellarPathPaymentOp.destination_account max_size:57 +StellarPathPaymentOp.paths max_count:5 + + +StellarManageOfferOp.source_account max_size:57 + +StellarCreatePassiveOfferOp.source_account max_size:57 + +StellarSetOptionsOp.source_account max_size:57 +StellarSetOptionsOp.inflation_destination_account max_size:57 +StellarSetOptionsOp.home_domain max_size:33 +StellarSetOptionsOp.signer_key max_size:32 + +StellarChangeTrustOp.source_account max_size:57 + +StellarAllowTrustOp.source_account max_size:57 +StellarAllowTrustOp.trusted_account max_size:57 +StellarAllowTrustOp.asset_code max_size:13 + +StellarAccountMergeOp.source_account max_size:57 +StellarAccountMergeOp.destination_account max_size:57 + +StellarManageDataOp.source_account max_size:57 +StellarManageDataOp.key max_size:65 +StellarManageDataOp.value max_size:65 + +StellarBumpSequenceOp.source_account max_size:57 + +StellarSignedTx.public_key max_size:32 +StellarSignedTx.signature max_size:64 # ed25519 signatures are 64 bytes, this does not include the hint diff --git a/legacy/firmware/protob/messages-stellar.proto b/legacy/firmware/protob/messages-stellar.proto new file mode 120000 index 0000000000..79eb91585c --- /dev/null +++ b/legacy/firmware/protob/messages-stellar.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-stellar.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages.options b/legacy/firmware/protob/messages.options new file mode 100644 index 0000000000..e69de29bb2 diff --git a/legacy/firmware/protob/messages.proto b/legacy/firmware/protob/messages.proto new file mode 120000 index 0000000000..4f4140fbd0 --- /dev/null +++ b/legacy/firmware/protob/messages.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages.proto \ No newline at end of file diff --git a/legacy/firmware/protob/messages_map.py b/legacy/firmware/protob/messages_map.py new file mode 100755 index 0000000000..c8c763047f --- /dev/null +++ b/legacy/firmware/protob/messages_map.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +import sys + +from collections import defaultdict +from messages_pb2 import MessageType +from messages_pb2 import wire_in, wire_out +from messages_pb2 import wire_debug_in, wire_debug_out +from messages_pb2 import wire_bootloader, wire_no_fsm + +fh = open("messages_map.h", "wt") +fl = open("messages_map_limits.h", "wt") + +# len("MessageType_MessageType_") - len("_fields") == 17 +TEMPLATE = "\t{{ {type} {dir} {msg_id:46} {fields:29} {process_func} }},\n" + +LABELS = { + wire_in: "in messages", + wire_out: "out messages", + wire_debug_in: "debug in messages", + wire_debug_out: "debug out messages", +} + + +def handle_message(fh, fl, skipped, message, extension): + name = message.name + short_name = name.split("MessageType_", 1).pop() + assert(short_name != name) + + for s in skipped: + if short_name.startswith(s): + return + + interface = "d" if extension in (wire_debug_in, wire_debug_out) else "n" + direction = "i" if extension in (wire_in, wire_debug_in) else "o" + + options = message.GetOptions() + bootloader = options.Extensions[wire_bootloader] + no_fsm = options.Extensions[wire_no_fsm] + + if getattr(options, 'deprecated', None): + fh.write('\t// Message %s is deprecated\n' % short_name) + return + if bootloader: + fh.write('\t// Message %s is used in bootloader mode only\n' % short_name) + return + if no_fsm: + fh.write('\t// Message %s is not used in FSM\n' % short_name) + return + + if direction == "i": + process_func = "(void (*)(const void *))fsm_msg%s" % short_name + else: + process_func = "0" + + fh.write(TEMPLATE.format( + type="'%c'," % interface, + dir="'%c'," % direction, + msg_id="MessageType_%s," % name, + fields="%s_fields," % short_name, + process_func=process_func, + )) + + bufsize = None + t = interface + direction + if t == "ni": + bufsize = "MSG_IN_SIZE" + elif t == "no": + bufsize = "MSG_OUT_SIZE" + elif t == "do": + bufsize = "MSG_DEBUG_OUT_SIZE" + if bufsize: + fl.write("_Static_assert(%s >= sizeof(%s), \"msg buffer too small\");\n" % (bufsize, short_name)) + + +skipped = sys.argv[1:] + +fh.write("\t// This file is automatically generated by messages_map.py -- DO NOT EDIT!\n") +fl.write("// This file is automatically generated by messages_map.py -- DO NOT EDIT!\n\n") + +messages = defaultdict(list) + +for message in MessageType.DESCRIPTOR.values: + extensions = message.GetOptions().Extensions + + for extension in (wire_in, wire_out, wire_debug_in, wire_debug_out): + if extensions[extension]: + messages[extension].append(message) + +for extension in (wire_in, wire_out, wire_debug_in, wire_debug_out): + if extension == wire_debug_in: + fh.write("\n#if DEBUG_LINK\n") + fl.write("\n#if DEBUG_LINK\n") + + fh.write("\n\t// {label}\n\n".format(label=LABELS[extension])) + + for message in messages[extension]: + handle_message(fh, fl, skipped, message, extension) + + if extension == wire_debug_out: + fh.write("\n#endif\n") + fl.write("#endif\n") + +fh.close() +fl.close() diff --git a/legacy/firmware/recovery-table.h b/legacy/firmware/recovery-table.h new file mode 100644 index 0000000000..05cb2334b0 --- /dev/null +++ b/legacy/firmware/recovery-table.h @@ -0,0 +1,113 @@ +// clang-format off +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2016 Jochen Hoenicke + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* DO NOT EDIT: This file is automatically generated by + * cd ../gen/wordlist + * perl build-recoverytable.pl recovery_english.txt + */ + +static const uint16_t word_table1[82] = +{ + 8192, 8200, 8208, 8217, 8225, 8234, 8243, 8250, 8259, + 12361, 12367, 8280, 8285, 8292, 8297, 8302, 8311, 8318, + 8325, 12429, 12437, 8347, 8356, 8365, 8373, 8382, 8391, + 8400, 8409, 8412, 8417, 8426, 8432, 8439, 8447, 8455, + 8463, 8472, 8480, 8487, 8495, 4408, 4416, 8521, 8530, + 8539, 8548, 8557, 8564, 8573, 8582, 8589, 8597, 8601, + 8609, 8618, 8627, 8634, 4545, 8645, 12750, 12759, 8672, + 8681, 8690, 8695, 8703, 8712, 8721, 8730, 8738, 8746, + 8751, 8757, 8766, 8775, 8782, 4690, 4699, 8804, 8813, + 630, +}; + +static const uint16_t word_table2[631] = +{ + 12288, 12293, 12297, 12298, 12302, 12304, 12306, 12307, 12312, + 12313, 12316, 8225, 8226, 8229, 8233, 8234, 12334, 12337, + 12342, 12345, 8253, 12354, 12357, 12361, 12365, 8274, 12376, + 12378, 12380, 12381, 12385, 12386, 12390, 12394, 12396, 12400, + 8305, 12407, 12410, 8318, 8321, 8327, 12424, 12428, 12433, + 12439, 12443, 12448, 12451, 12456, 12459, 12463, 12465, 12468, + 12471, 12476, 12479, 12482, 12485, 12489, 12494, 12498, 12502, + 12507, 12509, 12515, 12521, 12522, 12527, 12530, 12532, 12535, + 12538, 12541, 12544, 12545, 12546, 12547, 12549, 16647, 16651, + 12559, 16658, 16662, 12569, 12574, 12579, 12582, 12583, 12584, + 12585, 12586, 12588, 16686, 16689, 12599, 12605, 12609, 12611, + 12612, 12615, 12616, 12617, 12618, 12620, 12621, 12626, 12629, + 12635, 12641, 12645, 12650, 12655, 16757, 16761, 12669, 12673, + 12679, 12684, 16782, 16786, 12695, 12699, 12703, 12707, 12713, + 12715, 12716, 12717, 12719, 12723, 12725, 8630, 12727, 12728, + 12730, 12732, 12733, 12734, 12735, 12736, 12737, 12738, 12740, + 12746, 12747, 12750, 12751, 12753, 12755, 12758, 12763, 12764, + 12770, 12772, 12775, 12779, 12782, 12786, 12788, 16886, 16891, + 12798, 12801, 12802, 12804, 12805, 12807, 12808, 12811, 12812, + 12813, 12814, 12815, 12820, 12822, 12827, 12830, 12832, 8741, + 8742, 12839, 12841, 8751, 8757, 12857, 12859, 12864, 12866, + 12870, 12874, 12876, 12879, 12884, 12887, 12891, 8799, 8802, + 8808, 8812, 8814, 12914, 12916, 12921, 12925, 12929, 12935, + 8841, 12939, 12942, 12943, 12945, 12947, 12950, 12953, 12955, + 12959, 12961, 12964, 12967, 12973, 12977, 12980, 12985, 12988, + 12993, 12998, 13001, 13005, 13008, 13012, 17112, 17116, 13023, + 13027, 13029, 13031, 13033, 13038, 8943, 13045, 13047, 13049, + 13051, 13056, 13058, 13060, 8966, 8972, 13069, 13070, 13071, + 13072, 13073, 13075, 13076, 13080, 13082, 13087, 13088, 13090, + 13093, 13096, 17194, 17197, 13105, 13107, 13110, 13113, 9018, + 9024, 13121, 13123, 13126, 13128, 13132, 13136, 13140, 13142, + 13145, 13147, 13149, 13151, 13154, 13156, 13160, 13164, 13167, + 13172, 13173, 13174, 13177, 13180, 13183, 9088, 9089, 9091, + 9094, 9095, 13194, 13195, 13196, 13198, 13202, 13206, 13210, + 13213, 13216, 13219, 13223, 13228, 9138, 9144, 9148, 9152, + 13253, 13254, 13255, 13256, 13259, 9164, 9165, 13265, 13266, + 13268, 13270, 13271, 13275, 9180, 13280, 13285, 13288, 13292, + 13295, 13300, 13304, 13309, 13314, 13318, 13322, 13326, 13330, + 13335, 13340, 13344, 9253, 9259, 13356, 13360, 13363, 13366, + 13372, 13373, 13379, 13382, 13387, 13389, 13393, 13394, 13396, + 13398, 13400, 13402, 13406, 13408, 13410, 13412, 13414, 13415, + 13419, 13421, 13424, 13427, 13432, 13436, 13440, 13444, 13448, + 13452, 13457, 9362, 13461, 13463, 13465, 13468, 13470, 13473, + 13475, 13477, 13480, 9387, 13485, 13489, 13491, 13492, 13496, + 9402, 9406, 13503, 13506, 9414, 9417, 9418, 9422, 9423, + 9424, 9427, 9428, 9433, 13534, 13540, 9447, 9448, 9449, + 9453, 9456, 9458, 13557, 13563, 13568, 13574, 13579, 13582, + 13586, 13591, 9500, 13600, 13604, 13609, 13614, 13619, 13624, + 13627, 13632, 13638, 13643, 13645, 17747, 17750, 17755, 17759, + 17764, 13672, 13674, 13677, 13679, 13681, 13685, 9592, 13689, + 13692, 13693, 13696, 13697, 13698, 13701, 13703, 13706, 13708, + 13711, 13713, 13715, 13718, 13721, 13723, 13728, 13729, 13732, + 13735, 13736, 13740, 13744, 13747, 13748, 13752, 13753, 13759, + 13762, 13763, 13765, 9670, 13767, 13773, 13778, 13783, 13788, + 13793, 13798, 13801, 13805, 13810, 13815, 13818, 13822, 13824, + 13828, 13831, 13835, 13839, 13843, 13847, 13853, 13856, 13862, + 13866, 13869, 17970, 17972, 13881, 13883, 13884, 13885, 13889, + 13895, 13899, 13904, 13906, 13911, 13915, 13919, 9827, 9832, + 13933, 13934, 13937, 13939, 13944, 13947, 13949, 13954, 13958, + 13963, 13964, 13969, 13970, 13975, 13978, 9883, 18078, 18084, + 13992, 13997, 14000, 14006, 14011, 14014, 14015, 14018, 14020, + 14024, 14026, 14029, 14032, 14038, 14040, 14044, 14046, 14050, + 9955, 14055, 14059, 14064, 14068, 14071, 14075, 14078, 14080, + 14085, 14088, 14091, 14093, 14095, 14099, 14101, 14104, 14108, + 14111, 14114, 14117, 14119, 14122, 14125, 14128, 18228, 18233, + 14142, 14145, 14151, 14153, 14159, 14160, 14165, 10072, 10078, + 10080, 14178, 14182, 14187, 14191, 10100, 10106, 10108, 10114, + 14211, 14217, 14223, 14228, 14234, 14239, 14245, 14250, 14253, + 14257, 14260, 14264, 14267, 14273, 14278, 14281, 14285, 14291, + 14293, 18394, 18399, 14305, 14310, 14315, 10224, 6134, 6140, + 2048, +}; diff --git a/legacy/firmware/recovery.c b/legacy/firmware/recovery.c new file mode 100644 index 0000000000..e9f1c5e0cb --- /dev/null +++ b/legacy/firmware/recovery.c @@ -0,0 +1,593 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * Copyright (C) 2016 Jochen Hoenicke + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "recovery.h" +#include +#include "bip39.h" +#include "config.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "memzero.h" +#include "messages.h" +#include "messages.pb.h" +#include "oled.h" +#include "protect.h" +#include "recovery-table.h" +#include "rng.h" +#include "usb.h" + +/* number of words expected in the new seed */ +static uint32_t word_count; + +/* recovery mode: + * 0: not recovering + * 1: recover by scrambled plain text words + * 2: recover by matrix entry + */ +static int awaiting_word = 0; + +/* True if we should not write anything back to config + * (can be used for testing seed for correctness). + */ +static bool dry_run; + +/* True if we should check that seed corresponds to bip39. + */ +static bool enforce_wordlist; + +/* For scrambled recovery Trezor may ask for faked words if + * seed is short. This contains the fake word. + */ +static char fake_word[12]; + +/* Word position in the seed that we are currently asking for. + * This is 0 if we ask for a fake word. Only for scrambled recovery. + */ +static uint32_t word_pos; + +/* Scrambled recovery: How many words has the user already entered. + * Matrix recovery: How many digits has the user already entered. + */ +static uint32_t word_index; + +/* Order in which we ask for the words. It holds that + * word_order[word_index] == word_pos. Only for scrambled recovery. + */ +static char word_order[24]; + +/* The recovered seed. This is filled during the recovery process. + */ +static char words[24][12]; + +/* The "pincode" of the current word. This is basically the "pin" + * that the user would have entered for the current word if the words + * were displayed in alphabetical order. Note that it is base 9, not + * base 10. Only for matrix recovery. + */ +static uint16_t word_pincode; + +/* The pinmatrix currently displayed on screen. + * Only for matrix recovery. + */ +static uint8_t word_matrix[9]; + +/* The words are stored in two tables. + * + * The low bits of the first table (TABLE1) store the index into the + * second table, for each of the 81 choices for the first two levels + * of the matrix. The final entry points to the final entry of the + * second table. The difference TABLE1(idx+1)-TABLE1(idx) gives the + * number of choices for the third level. The value + * TABLE2(TABLE1(idx)) gives the index of the first word in the range + * and TABLE2(TABLE1(idx+1))-1 gives the index of the last word. + * + * The low bits of the second table (TABLE2) store the index into the + * word list for each of the choices for the first three levels. The + * final entry stores the value 2048 (number of bip39 words). table. + * The difference TABLE2(idx+1)-TABLE2(idx) gives the number of + * choices for the last level. The value TABLE2(idx) gives the index + * of the first word in the range and TABLE2(idx)-1 gives the index of + * the last word. + * + * The high bits in each table is the "prefix length", i.e. the number + * of significant letters for the corresponding choice. There is no + * prefix length or table for the very first level, as the prefix length + * is always one and there are always nine choices on the second level. + */ +#define MASK_IDX(x) ((x)&0xfff) +#define TABLE1(x) MASK_IDX(word_table1[x]) +#define TABLE2(x) MASK_IDX(word_table2[x]) + +/* Helper function to format a two digit number. + * Parameter dest is buffer containing the string. It should already + * start with "##th". The number is written in place. + * Parameter number gives the number that we should format. + */ +static void format_number(char *dest, int number) { + if (number < 10) { + dest[0] = ' '; + } else { + dest[0] = '0' + number / 10; + } + dest[1] = '0' + number % 10; + if (number == 1 || number == 21) { + dest[2] = 's'; + dest[3] = 't'; + } else if (number == 2 || number == 22) { + dest[2] = 'n'; + dest[3] = 'd'; + } else if (number == 3 || number == 23) { + dest[2] = 'r'; + dest[3] = 'd'; + } +} + +/* Send a request for a new word/matrix code to the PC. + */ +static void recovery_request(void) { + WordRequest resp; + memzero(&resp, sizeof(WordRequest)); + resp.has_type = true; + resp.type = awaiting_word == 1 + ? WordRequestType_WordRequestType_Plain + : (word_index % 4 == 3) + ? WordRequestType_WordRequestType_Matrix6 + : WordRequestType_WordRequestType_Matrix9; + msg_write(MessageType_MessageType_WordRequest, &resp); +} + +/* Called when the last word was entered. + * Check mnemonic and send success/failure. + */ +static void recovery_done(void) { + char new_mnemonic[MAX_MNEMONIC_LEN + 1] = {0}; + + strlcpy(new_mnemonic, words[0], sizeof(new_mnemonic)); + for (uint32_t i = 1; i < word_count; i++) { + strlcat(new_mnemonic, " ", sizeof(new_mnemonic)); + strlcat(new_mnemonic, words[i], sizeof(new_mnemonic)); + } + if (!enforce_wordlist || mnemonic_check(new_mnemonic)) { + // New mnemonic is valid. + if (!dry_run) { + // Update mnemonic on config. + if (config_setMnemonic(new_mnemonic)) { + if (!enforce_wordlist) { + // not enforcing => mark config as imported + config_setImported(true); + } + fsm_sendSuccess(_("Device recovered")); + } else { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to store mnemonic")); + } + memzero(new_mnemonic, sizeof(new_mnemonic)); + } else { + // Inform the user about new mnemonic correctness (as well as whether it + // is the same as the current one). + bool match = + (config_isInitialized() && config_containsMnemonic(new_mnemonic)); + memzero(new_mnemonic, sizeof(new_mnemonic)); + if (match) { + layoutDialog(&bmp_icon_ok, NULL, _("Confirm"), NULL, + _("The seed is valid"), _("and MATCHES"), + _("the one in the device."), NULL, NULL, NULL); + protectButton(ButtonRequestType_ButtonRequest_Other, true); + fsm_sendSuccess( + _("The seed is valid and matches the one in the device")); + } else { + layoutDialog(&bmp_icon_error, NULL, _("Confirm"), NULL, + _("The seed is valid"), _("but does NOT MATCH"), + _("the one in the device."), NULL, NULL, NULL); + protectButton(ButtonRequestType_ButtonRequest_Other, true); + fsm_sendFailure( + FailureType_Failure_DataError, + _("The seed is valid but does not match the one in the device")); + } + } + } else { + // New mnemonic is invalid. + memzero(new_mnemonic, sizeof(new_mnemonic)); + if (!dry_run) { + session_clear(true); + } else { + layoutDialog(&bmp_icon_error, NULL, _("Confirm"), NULL, _("The seed is"), + _("INVALID!"), NULL, NULL, NULL, NULL); + protectButton(ButtonRequestType_ButtonRequest_Other, true); + } + fsm_sendFailure(FailureType_Failure_DataError, + _("Invalid seed, are words in correct order?")); + } + awaiting_word = 0; + layoutHome(); +} + +/* Helper function for matrix recovery: + * Formats a string describing the word range from first to last where + * prefixlen gives the number of characters in first and last that are + * significant, i.e. the word before first or the word after last differ + * exactly at the prefixlen-th character. + * + * Invariants: + * memcmp("first - 1", first, prefixlen) != 0 + * memcmp(last, "last + 1", prefixlen) != 0 + * first[prefixlen-2] == last[prefixlen-2] except for range WI-Z. + */ +static void add_choice(char choice[12], int prefixlen, const char *first, + const char *last) { + // assert 1 <= prefixlen <= 4 + char *dest = choice; + for (int i = 0; i < prefixlen; i++) { + *dest++ = toupper((int)first[i]); + } + if (first[0] != last[0]) { + /* special case WI-Z; also used for T-Z, etc. */ + *dest++ = '-'; + *dest++ = toupper((int)last[0]); + } else if (last[prefixlen - 1] == first[prefixlen - 1]) { + /* single prefix */ + } else if (prefixlen < 3) { + /* AB-AC, etc. */ + *dest++ = '-'; + for (int i = 0; i < prefixlen; i++) { + *dest++ = toupper((int)last[i]); + } + } else { + /* RE[A-M] etc. */ + /* remove last and replace with space */ + dest[-1] = ' '; + if (first[prefixlen - 1]) { + /* handle special case: CAN[-D] */ + *dest++ = toupper((int)first[prefixlen - 1]); + } + *dest++ = '-'; + *dest++ = toupper((int)last[prefixlen - 1]); + } + *dest++ = 0; +} + +/* Helper function for matrix recovery: + * Display the recovery matrix given in choices. If twoColumn is set + * use 2x3 layout, otherwise 3x3 layout. Also generates a random + * scrambling and stores it in word_matrix. + */ +static void display_choices(bool twoColumn, char choices[9][12], int num) { + const int nColumns = twoColumn ? 2 : 3; + const int displayedChoices = nColumns * 3; + for (int i = 0; i < displayedChoices; i++) { + word_matrix[i] = i; + } + /* scramble matrix */ + random_permute((char *)word_matrix, displayedChoices); + + if (word_index % 4 == 0) { + char desc[] = "##th word"; + int nr = (word_index / 4) + 1; + format_number(desc, nr); + layoutDialogSwipe(&bmp_icon_info, NULL, NULL, NULL, _("Please enter the"), + (nr < 10 ? desc + 1 : desc), _("of your mnemonic"), NULL, + NULL, NULL); + } else { + oledBox(0, 27, 127, 63, false); + } + + for (int row = 0; row < 3; row++) { + int y = 55 - row * 11; + for (int col = 0; col < nColumns; col++) { + int x = twoColumn ? 64 * col + 32 : 42 * col + 22; + int choice = word_matrix[nColumns * row + col]; + const char *text = choice < num ? choices[choice] : "-"; + oledDrawString(x - oledStringWidth(text, FONT_STANDARD) / 2, y, text, + FONT_STANDARD); + if (twoColumn) { + oledInvert(x - 32 + 1, y - 1, x - 32 + 63 - 1, y + 8); + } else { + oledInvert(x - 22 + 1, y - 1, x - 22 + 41 - 1, y + 8); + } + } + } + oledRefresh(); + + /* avoid picking out of range numbers */ + for (int i = 0; i < displayedChoices; i++) { + if (word_matrix[i] >= num) word_matrix[i] = 0; + } + /* two column layout: middle column = right column */ + if (twoColumn) { + static const uint8_t twolayout[9] = {0, 1, 1, 2, 3, 3, 4, 5, 5}; + for (int i = 8; i >= 2; i--) { + word_matrix[i] = word_matrix[twolayout[i]]; + } + } +} + +/* Helper function for matrix recovery: + * Generates a new matrix and requests the next pin. + */ +static void next_matrix(void) { + const char *const *wl = mnemonic_wordlist(); + char word_choices[9][12]; + uint32_t idx, num; + bool last = (word_index % 4) == 3; + + /* Build the matrix: + * num: number of choices + * word_choices[][]: the strings containing the choices + */ + switch (word_index % 4) { + case 3: + /* last level: show up to six words */ + /* idx: index in table2 for the entered choice. */ + /* first: the first word. */ + /* num: the number of words to choose from. */ + idx = TABLE1(word_pincode / 9) + word_pincode % 9; + const uint32_t first = TABLE2(idx); + num = TABLE2(idx + 1) - first; + for (uint32_t i = 0; i < num; i++) { + strlcpy(word_choices[i], wl[first + i], sizeof(word_choices[i])); + } + break; + + case 2: + /* third level: show up to nine ranges (using table2) */ + /* idx: first index in table2 corresponding to pin code. */ + /* num: the number of choices. */ + idx = TABLE1(word_pincode); + num = TABLE1(word_pincode + 1) - idx; + for (uint32_t i = 0; i < num; i++) { + add_choice(word_choices[i], (word_table2[idx + i] >> 12), + wl[TABLE2(idx + i)], wl[TABLE2(idx + i + 1) - 1]); + } + break; + + case 1: + /* second level: exactly nine ranges (using table1) */ + /* idx: first index in table1 corresponding to pin code. */ + /* num: the number of choices. */ + idx = word_pincode * 9; + num = 9; + for (uint32_t i = 0; i < num; i++) { + add_choice(word_choices[i], (word_table1[idx + i] >> 12), + wl[TABLE2(TABLE1(idx + i))], + wl[TABLE2(TABLE1(idx + i + 1)) - 1]); + } + break; + + case 0: + /* first level: exactly nine ranges */ + /* num: the number of choices. */ + num = 9; + for (uint32_t i = 0; i < num; i++) { + add_choice(word_choices[i], 1, wl[TABLE2(TABLE1(9 * i))], + wl[TABLE2(TABLE1(9 * (i + 1))) - 1]); + } + break; + } + display_choices(last, word_choices, num); + + recovery_request(); +} + +/* Function called when a digit was entered by user. + * digit: ascii code of the entered digit ('1'-'9') or + * '\x08' for backspace. + */ +static void recovery_digit(const char digit) { + if (digit == 8) { + /* backspace: undo */ + if ((word_index % 4) == 0) { + /* undo complete word */ + if (word_index > 0) word_index -= 4; + } else { + word_index--; + word_pincode /= 9; + } + next_matrix(); + return; + } + + if (digit < '1' || digit > '9') { + recovery_request(); + return; + } + + int choice = word_matrix[digit - '1']; + if ((word_index % 4) == 3) { + /* received final word */ + + /* Mark the chosen word for 250 ms */ + int y = 54 - ((digit - '1') / 3) * 11; + int x = 64 * (((digit - '1') % 3) > 0); + oledInvert(x + 1, y, x + 62, y + 9); + oledRefresh(); + usbTiny(1); + usbSleep(250); + usbTiny(0); + + /* index of the chosen word */ + int idx = TABLE2(TABLE1(word_pincode / 9) + (word_pincode % 9)) + choice; + uint32_t widx = word_index / 4; + + word_pincode = 0; + strlcpy(words[widx], mnemonic_wordlist()[idx], sizeof(words[widx])); + if (widx + 1 == word_count) { + recovery_done(); + return; + } + /* next word */ + } else { + word_pincode = word_pincode * 9 + choice; + } + word_index++; + next_matrix(); +} + +/* Helper function for scrambled recovery: + * Ask the user for the next word. + */ +void next_word(void) { + word_pos = word_order[word_index]; + if (word_pos == 0) { + const char *const *wl = mnemonic_wordlist(); + strlcpy(fake_word, wl[random_uniform(2048)], sizeof(fake_word)); + layoutDialogSwipe(&bmp_icon_info, NULL, NULL, NULL, + _("Please enter the word"), NULL, fake_word, NULL, + _("on your computer"), NULL); + } else { + fake_word[0] = 0; + char desc[] = "##th word"; + format_number(desc, word_pos); + layoutDialogSwipe(&bmp_icon_info, NULL, NULL, NULL, _("Please enter the"), + NULL, (word_pos < 10 ? desc + 1 : desc), NULL, + _("of your mnemonic"), NULL); + } + recovery_request(); +} + +void recovery_init(uint32_t _word_count, bool passphrase_protection, + bool pin_protection, const char *language, const char *label, + bool _enforce_wordlist, uint32_t type, uint32_t u2f_counter, + bool _dry_run) { + if (_word_count != 12 && _word_count != 18 && _word_count != 24) return; + + word_count = _word_count; + enforce_wordlist = _enforce_wordlist; + dry_run = _dry_run; + + if (!dry_run) { + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("recover the device?"), + NULL, NULL, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + if (!dry_run) { + if (pin_protection && !protectChangePin(false)) { + layoutHome(); + return; + } + + config_setPassphraseProtection(passphrase_protection); + config_setLanguage(language); + config_setLabel(label); + config_setU2FCounter(u2f_counter); + } + + if ((type & RecoveryDeviceType_RecoveryDeviceType_Matrix) != 0) { + awaiting_word = 2; + word_index = 0; + word_pincode = 0; + next_matrix(); + } else { + for (uint32_t i = 0; i < word_count; i++) { + word_order[i] = i + 1; + } + for (uint32_t i = word_count; i < 24; i++) { + word_order[i] = 0; + } + random_permute(word_order, 24); + awaiting_word = 1; + word_index = 0; + next_word(); + } +} + +static void recovery_scrambledword(const char *word) { + if (word_pos == 0) { // fake word + if (strcmp(word, fake_word) != 0) { + if (!dry_run) { + session_clear(true); + } + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Wrong word retyped")); + layoutHome(); + return; + } + } else { // real word + if (enforce_wordlist) { // check if word is valid + const char *const *wl = mnemonic_wordlist(); + bool found = false; + while (*wl) { + if (strcmp(word, *wl) == 0) { + found = true; + break; + } + wl++; + } + if (!found) { + if (!dry_run) { + session_clear(true); + } + fsm_sendFailure(FailureType_Failure_DataError, + _("Word not found in a wordlist")); + layoutHome(); + return; + } + } + strlcpy(words[word_pos - 1], word, sizeof(words[word_pos - 1])); + } + + if (word_index + 1 == 24) { // last one + recovery_done(); + } else { + word_index++; + next_word(); + } +} + +/* Function called when a word was entered by user. Used + * for scrambled recovery. + */ +void recovery_word(const char *word) { + switch (awaiting_word) { + case 2: + recovery_digit(word[0]); + break; + case 1: + recovery_scrambledword(word); + break; + default: + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, + _("Not in Recovery mode")); + break; + } +} + +/* Abort recovery. + */ +void recovery_abort(void) { + if (awaiting_word) { + layoutHome(); + awaiting_word = 0; + } +} + +#if DEBUG_LINK + +const char *recovery_get_fake_word(void) { return fake_word; } + +uint32_t recovery_get_word_pos(void) { return word_pos; } + +#endif diff --git a/legacy/firmware/recovery.h b/legacy/firmware/recovery.h new file mode 100644 index 0000000000..84a261aa05 --- /dev/null +++ b/legacy/firmware/recovery.h @@ -0,0 +1,35 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __RECOVERY_H__ +#define __RECOVERY_H__ + +#include +#include + +void recovery_init(uint32_t _word_count, bool passphrase_protection, + bool pin_protection, const char *language, const char *label, + bool _enforce_wordlist, uint32_t type, uint32_t u2f_counter, + bool _dry_run); +void recovery_word(const char *word); +void recovery_abort(void); +const char *recovery_get_fake_word(void); +uint32_t recovery_get_word_pos(void); + +#endif diff --git a/legacy/firmware/reset.c b/legacy/firmware/reset.c new file mode 100644 index 0000000000..1faef79ee9 --- /dev/null +++ b/legacy/firmware/reset.c @@ -0,0 +1,206 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "reset.h" +#include "bip39.h" +#include "config.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "memzero.h" +#include "messages.h" +#include "messages.pb.h" +#include "protect.h" +#include "rng.h" +#include "sha2.h" +#include "util.h" + +static uint32_t strength; +static uint8_t int_entropy[32]; +static bool awaiting_entropy = false; +static bool skip_backup = false; +static bool no_backup = false; + +void reset_init(bool display_random, uint32_t _strength, + bool passphrase_protection, bool pin_protection, + const char *language, const char *label, uint32_t u2f_counter, + bool _skip_backup, bool _no_backup) { + if (_strength != 128 && _strength != 192 && _strength != 256) return; + + strength = _strength; + skip_backup = _skip_backup; + no_backup = _no_backup; + + if (display_random && (skip_backup || no_backup)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + "Can't show internal entropy when backup is skipped"); + layoutHome(); + return; + } + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL, + _("Do you really want to"), _("create a new wallet?"), NULL, + _("By continuing you"), _("agree to trezor.io/tos"), NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + + random_buffer(int_entropy, 32); + + char ent_str[4][17]; + data2hex(int_entropy, 8, ent_str[0]); + data2hex(int_entropy + 8, 8, ent_str[1]); + data2hex(int_entropy + 16, 8, ent_str[2]); + data2hex(int_entropy + 24, 8, ent_str[3]); + + if (display_random) { + layoutDialogSwipe(&bmp_icon_info, _("Cancel"), _("Continue"), NULL, + _("Internal entropy:"), ent_str[0], ent_str[1], + ent_str[2], ent_str[3], NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ResetDevice, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } + } + + if (pin_protection && !protectChangePin(false)) { + layoutHome(); + return; + } + + config_setPassphraseProtection(passphrase_protection); + config_setLanguage(language); + config_setLabel(label); + config_setU2FCounter(u2f_counter); + + EntropyRequest resp; + memzero(&resp, sizeof(EntropyRequest)); + msg_write(MessageType_MessageType_EntropyRequest, &resp); + awaiting_entropy = true; +} + +void reset_entropy(const uint8_t *ext_entropy, uint32_t len) { + if (!awaiting_entropy) { + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, + _("Not in Reset mode")); + return; + } + awaiting_entropy = false; + + SHA256_CTX ctx; + sha256_Init(&ctx); + sha256_Update(&ctx, int_entropy, 32); + sha256_Update(&ctx, ext_entropy, len); + sha256_Final(&ctx, int_entropy); + const char *mnemonic = mnemonic_from_data(int_entropy, strength / 8); + memzero(int_entropy, 32); + + if (skip_backup || no_backup) { + if (no_backup) { + config_setNoBackup(); + } else { + config_setNeedsBackup(true); + } + if (config_setMnemonic(mnemonic)) { + fsm_sendSuccess(_("Device successfully initialized")); + } else { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to store mnemonic")); + } + layoutHome(); + } else { + reset_backup(false, mnemonic); + } + mnemonic_clear(); +} + +static char current_word[10]; + +// separated == true if called as a separate workflow via BackupMessage +void reset_backup(bool separated, const char *mnemonic) { + if (separated) { + bool needs_backup = false; + config_getNeedsBackup(&needs_backup); + if (!needs_backup) { + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, + _("Seed already backed up")); + return; + } + + config_setUnfinishedBackup(true); + config_setNeedsBackup(false); + } + + for (int pass = 0; pass < 2; pass++) { + int i = 0, word_pos = 1; + while (mnemonic[i] != 0) { + // copy current_word + int j = 0; + while (mnemonic[i] != ' ' && mnemonic[i] != 0 && + j + 1 < (int)sizeof(current_word)) { + current_word[j] = mnemonic[i]; + i++; + j++; + } + current_word[j] = 0; + if (mnemonic[i] != 0) { + i++; + } + layoutResetWord(current_word, pass, word_pos, mnemonic[i] == 0); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmWord, true)) { + if (!separated) { + session_clear(true); + } + layoutHome(); + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + return; + } + word_pos++; + } + } + + config_setUnfinishedBackup(false); + + if (separated) { + fsm_sendSuccess(_("Seed successfully backed up")); + } else { + config_setNeedsBackup(false); + if (config_setMnemonic(mnemonic)) { + fsm_sendSuccess(_("Device successfully initialized")); + } else { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to store mnemonic")); + } + } + layoutHome(); +} + +#if DEBUG_LINK + +uint32_t reset_get_int_entropy(uint8_t *entropy) { + memcpy(entropy, int_entropy, 32); + return 32; +} + +const char *reset_get_word(void) { return current_word; } + +#endif diff --git a/legacy/firmware/reset.h b/legacy/firmware/reset.h new file mode 100644 index 0000000000..e9aba0b6d9 --- /dev/null +++ b/legacy/firmware/reset.h @@ -0,0 +1,35 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __RESET_H__ +#define __RESET_H__ + +#include +#include + +void reset_init(bool display_random, uint32_t _strength, + bool passphrase_protection, bool pin_protection, + const char *language, const char *label, uint32_t u2f_counter, + bool _skip_backup, bool _no_backup); +void reset_entropy(const uint8_t *ext_entropy, uint32_t len); +void reset_backup(bool separated, const char *mnemonic); +uint32_t reset_get_int_entropy(uint8_t *entropy); +const char *reset_get_word(void); + +#endif diff --git a/legacy/firmware/signing.c b/legacy/firmware/signing.c new file mode 100644 index 0000000000..c69cccef53 --- /dev/null +++ b/legacy/firmware/signing.c @@ -0,0 +1,1538 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "signing.h" +#include "crypto.h" +#include "ecdsa.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "memzero.h" +#include "messages.h" +#include "messages.pb.h" +#include "protect.h" +#include "secp256k1.h" +#include "transaction.h" + +static uint32_t inputs_count; +static uint32_t outputs_count; +static const CoinInfo *coin; +static CONFIDENTIAL HDNode root; +static CONFIDENTIAL HDNode node; +static bool signing = false; +enum { + STAGE_REQUEST_1_INPUT, + STAGE_REQUEST_2_PREV_META, + STAGE_REQUEST_2_PREV_INPUT, + STAGE_REQUEST_2_PREV_OUTPUT, + STAGE_REQUEST_2_PREV_EXTRADATA, + STAGE_REQUEST_3_OUTPUT, + STAGE_REQUEST_4_INPUT, + STAGE_REQUEST_4_OUTPUT, + STAGE_REQUEST_SEGWIT_INPUT, + STAGE_REQUEST_5_OUTPUT, + STAGE_REQUEST_SEGWIT_WITNESS, + STAGE_REQUEST_DECRED_WITNESS +} signing_stage; +static uint32_t idx1, idx2; +static uint32_t signatures; +static TxRequest resp; +static TxInputType input; +static TxOutputBinType bin_output; +static TxStruct to, tp, ti; +static Hasher hasher_prevouts, hasher_sequence, hasher_outputs, hasher_check; +static uint8_t CONFIDENTIAL privkey[32]; +static uint8_t pubkey[33], sig[64]; +static uint8_t hash_prevouts[32], hash_sequence[32], hash_outputs[32]; +static uint8_t hash_prefix[32]; +static uint8_t hash_check[32]; +static uint64_t to_spend, authorized_amount, spending, change_spend; +static uint32_t version = 1; +static uint32_t lock_time = 0; +static uint32_t expiry = 0; +static bool overwintered = false; +static uint32_t version_group_id = 0; +static uint32_t branch_id = 0; +static uint32_t next_nonsegwit_input; +static uint32_t progress, progress_step, progress_meta_step; +static bool multisig_fp_set, multisig_fp_mismatch; +static uint8_t multisig_fp[32]; +static uint32_t in_address_n[8]; +static size_t in_address_n_count; +static uint32_t tx_weight; + +/* A marker for in_address_n_count to indicate a mismatch in bip32 paths in + input */ +#define BIP32_NOCHANGEALLOWED 1 +/* The number of bip32 levels used in a wallet (chain and address) */ +#define BIP32_WALLET_DEPTH 2 +/* The chain id used for change */ +#define BIP32_CHANGE_CHAIN 1 +/* The maximum allowed change address. This should be large enough for normal + use and still allow to quickly brute-force the correct bip32 path. */ +#define BIP32_MAX_LAST_ELEMENT 1000000 + +/* transaction header size: 4 byte version */ +#define TXSIZE_HEADER 4 +/* transaction footer size: 4 byte lock time */ +#define TXSIZE_FOOTER 4 +/* transaction segwit overhead 2 marker */ +#define TXSIZE_SEGWIT_OVERHEAD 2 + +enum { + SIGHASH_ALL = 1, + SIGHASH_FORKID = 0x40, +}; + +enum { + DECRED_SERIALIZE_FULL = 0, + DECRED_SERIALIZE_NO_WITNESS = 1, + DECRED_SERIALIZE_WITNESS_SIGNING = 3, +}; + +/* progress_step/meta_step are fixed point numbers, giving the + * progress per input in permille with these many additional bits. + */ +#define PROGRESS_PRECISION 16 + +/* + +Workflow of streamed signing +The STAGE_ constants describe the signing_stage when request is sent. + +I - input +O - output + +Phase1 - check inputs, previous transactions, and outputs + - ask for confirmations + - check fee +========================================================= + +foreach I (idx1): + Request I STAGE_REQUEST_1_INPUT Add I to segwit hash_prevouts, hash_sequence + Add I to Decred hash_prefix + Add I to TransactionChecksum (prevout and type) + if (Decred) + Return I + If not segwit, Calculate amount of I: + Request prevhash I, META STAGE_REQUEST_2_PREV_META foreach prevhash I +(idx2): Request prevhash I STAGE_REQUEST_2_PREV_INPUT foreach prevhash O (idx2): + Request prevhash O STAGE_REQUEST_2_PREV_OUTPUT Add amount of +prevhash O (which is amount of I) Request prevhash extra data (if applicable) +STAGE_REQUEST_2_PREV_EXTRADATA Calculate hash of streamed tx, compare to +prevhash I foreach O (idx1): Request O STAGE_REQUEST_3_OUTPUT Add O to Decred +hash_prefix Add O to TransactionChecksum if (Decred) Return O Display output Ask +for confirmation + +Check tx fee +Ask for confirmation + +Phase2: sign inputs, check that nothing changed +=============================================== + +if (Decred) + Skip to STAGE_REQUEST_DECRED_WITNESS + +foreach I (idx1): // input to sign + if (idx1 is segwit) + Request I STAGE_REQUEST_SEGWIT_INPUT Return serialized input chunk + + else + foreach I (idx2): + Request I STAGE_REQUEST_4_INPUT If idx1 == idx2 Fill scriptsig + Remember key for signing + Add I to StreamTransactionSign + Add I to TransactionChecksum + foreach O (idx2): + Request O STAGE_REQUEST_4_OUTPUT Add O to StreamTransactionSign Add +O to TransactionChecksum + + Compare TransactionChecksum with checksum computed in Phase 1 + If different: + Failure + Sign StreamTransactionSign + Return signed chunk + +foreach O (idx1): + Request O STAGE_REQUEST_5_OUTPUT Rewrite change address Return O + + +Phase3: sign segwit inputs, check that nothing changed +=============================================== + +foreach I (idx1): // input to sign + Request I STAGE_REQUEST_SEGWIT_WITNESS Check amount Sign segwit prevhash, +sequence, amount, outputs Return witness + +Phase3: sign Decred inputs +========================== + +foreach I (idx1): // input to sign STAGE_REQUEST_DECRED_WITNESS Request I Fill +scriptSig Compute hash_witness + + Sign (hash_type || hash_prefix || hash_witness) + Return witness +*/ + +void send_req_1_input(void) { + signing_stage = STAGE_REQUEST_1_INPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXINPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx1; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_2_prev_meta(void) { + signing_stage = STAGE_REQUEST_2_PREV_META; + resp.has_request_type = true; + resp.request_type = RequestType_TXMETA; + resp.has_details = true; + resp.details.has_tx_hash = true; + resp.details.tx_hash.size = input.prev_hash.size; + memcpy(resp.details.tx_hash.bytes, input.prev_hash.bytes, + input.prev_hash.size); + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_2_prev_input(void) { + signing_stage = STAGE_REQUEST_2_PREV_INPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXINPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx2; + resp.details.has_tx_hash = true; + resp.details.tx_hash.size = input.prev_hash.size; + memcpy(resp.details.tx_hash.bytes, input.prev_hash.bytes, + resp.details.tx_hash.size); + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_2_prev_output(void) { + signing_stage = STAGE_REQUEST_2_PREV_OUTPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXOUTPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx2; + resp.details.has_tx_hash = true; + resp.details.tx_hash.size = input.prev_hash.size; + memcpy(resp.details.tx_hash.bytes, input.prev_hash.bytes, + resp.details.tx_hash.size); + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_2_prev_extradata(uint32_t chunk_offset, uint32_t chunk_len) { + signing_stage = STAGE_REQUEST_2_PREV_EXTRADATA; + resp.has_request_type = true; + resp.request_type = RequestType_TXEXTRADATA; + resp.has_details = true; + resp.details.has_extra_data_offset = true; + resp.details.extra_data_offset = chunk_offset; + resp.details.has_extra_data_len = true; + resp.details.extra_data_len = chunk_len; + resp.details.has_tx_hash = true; + resp.details.tx_hash.size = input.prev_hash.size; + memcpy(resp.details.tx_hash.bytes, input.prev_hash.bytes, + resp.details.tx_hash.size); + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_3_output(void) { + signing_stage = STAGE_REQUEST_3_OUTPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXOUTPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx1; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_4_input(void) { + signing_stage = STAGE_REQUEST_4_INPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXINPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx2; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_4_output(void) { + signing_stage = STAGE_REQUEST_4_OUTPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXOUTPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx2; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_segwit_input(void) { + signing_stage = STAGE_REQUEST_SEGWIT_INPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXINPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx1; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_segwit_witness(void) { + signing_stage = STAGE_REQUEST_SEGWIT_WITNESS; + resp.has_request_type = true; + resp.request_type = RequestType_TXINPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx1; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_decred_witness(void) { + signing_stage = STAGE_REQUEST_DECRED_WITNESS; + resp.has_request_type = true; + resp.request_type = RequestType_TXINPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx1; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_5_output(void) { + signing_stage = STAGE_REQUEST_5_OUTPUT; + resp.has_request_type = true; + resp.request_type = RequestType_TXOUTPUT; + resp.has_details = true; + resp.details.has_request_index = true; + resp.details.request_index = idx1; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void send_req_finished(void) { + resp.has_request_type = true; + resp.request_type = RequestType_TXFINISHED; + msg_write(MessageType_MessageType_TxRequest, &resp); +} + +void phase1_request_next_input(void) { + if (idx1 < inputs_count - 1) { + idx1++; + send_req_1_input(); + } else { + // compute segwit hashPrevouts & hashSequence + hasher_Final(&hasher_prevouts, hash_prevouts); + hasher_Final(&hasher_sequence, hash_sequence); + hasher_Final(&hasher_check, hash_check); + // init hashOutputs + hasher_Reset(&hasher_outputs); + idx1 = 0; + send_req_3_output(); + } +} + +void phase2_request_next_input(void) { + if (idx1 == next_nonsegwit_input) { + idx2 = 0; + send_req_4_input(); + } else { + send_req_segwit_input(); + } +} + +void extract_input_bip32_path(const TxInputType *tinput) { + if (in_address_n_count == BIP32_NOCHANGEALLOWED) { + return; + } + size_t count = tinput->address_n_count; + if (count < BIP32_WALLET_DEPTH) { + // no change address allowed + in_address_n_count = BIP32_NOCHANGEALLOWED; + return; + } + if (in_address_n_count == 0) { + // initialize in_address_n on first input seen + in_address_n_count = count; + // store the bip32 path up to the account + memcpy(in_address_n, tinput->address_n, + (count - BIP32_WALLET_DEPTH) * sizeof(uint32_t)); + return; + } + // check that all addresses use a path of same length + if (in_address_n_count != count) { + in_address_n_count = BIP32_NOCHANGEALLOWED; + return; + } + // check that the bip32 path up to the account matches + if (memcmp(in_address_n, tinput->address_n, + (count - BIP32_WALLET_DEPTH) * sizeof(uint32_t)) != 0) { + // mismatch -> no change address allowed + in_address_n_count = BIP32_NOCHANGEALLOWED; + return; + } +} + +bool check_change_bip32_path(const TxOutputType *toutput) { + size_t count = toutput->address_n_count; + + // Check that the change path has the same bip32 path length, + // the same path up to the account, and that the wallet components + // (chain id and address) are as expected. + // Note: count >= BIP32_WALLET_DEPTH and count == in_address_n_count + // imply that in_address_n_count != BIP32_NOCHANGEALLOWED + return (count >= BIP32_WALLET_DEPTH && count == in_address_n_count && + 0 == memcmp(in_address_n, toutput->address_n, + (count - BIP32_WALLET_DEPTH) * sizeof(uint32_t)) && + toutput->address_n[count - 2] <= BIP32_CHANGE_CHAIN && + toutput->address_n[count - 1] <= BIP32_MAX_LAST_ELEMENT); +} + +bool compile_input_script_sig(TxInputType *tinput) { + if (!multisig_fp_mismatch) { + // check that this is still multisig + uint8_t h[32]; + if (!tinput->has_multisig || + cryptoMultisigFingerprint(&(tinput->multisig), h) == 0 || + memcmp(multisig_fp, h, 32) != 0) { + // Transaction has changed during signing + return false; + } + } + if (in_address_n_count != BIP32_NOCHANGEALLOWED) { + // check that input address didn't change + size_t count = tinput->address_n_count; + if (count < 2 || count != in_address_n_count || + 0 != memcmp(in_address_n, tinput->address_n, + (count - 2) * sizeof(uint32_t))) { + return false; + } + } + memcpy(&node, &root, sizeof(HDNode)); + if (hdnode_private_ckd_cached(&node, tinput->address_n, + tinput->address_n_count, NULL) == 0) { + // Failed to derive private key + return false; + } + hdnode_fill_public_key(&node); + if (tinput->has_multisig) { + tinput->script_sig.size = compile_script_multisig(coin, &(tinput->multisig), + tinput->script_sig.bytes); + } else { // SPENDADDRESS + uint8_t hash[20]; + ecdsa_get_pubkeyhash(node.public_key, coin->curve->hasher_pubkey, hash); + tinput->script_sig.size = + compile_script_sig(coin->address_type, hash, tinput->script_sig.bytes); + } + return tinput->script_sig.size > 0; +} + +void signing_init(const SignTx *msg, const CoinInfo *_coin, + const HDNode *_root) { + inputs_count = msg->inputs_count; + outputs_count = msg->outputs_count; + coin = _coin; + memcpy(&root, _root, sizeof(HDNode)); + version = msg->version; + lock_time = msg->lock_time; + expiry = msg->expiry; + overwintered = msg->has_overwintered && msg->overwintered; + version_group_id = msg->version_group_id; + branch_id = msg->branch_id; + // set default values for Zcash if branch_id is unset + if (overwintered && (branch_id == 0)) { + switch (version) { + case 3: + branch_id = 0x5BA81B19; // Overwinter + break; + case 4: + branch_id = 0x76B809BB; // Sapling + break; + } + } + + uint32_t size = TXSIZE_HEADER + TXSIZE_FOOTER + + ser_length_size(inputs_count) + + ser_length_size(outputs_count); + if (coin->decred) { + size += 4; // Decred expiry + size += ser_length_size(inputs_count); // Witness inputs count + } + + tx_weight = 4 * size; + + signatures = 0; + idx1 = 0; + to_spend = 0; + spending = 0; + change_spend = 0; + authorized_amount = 0; + memzero(&input, sizeof(TxInputType)); + memzero(&resp, sizeof(TxRequest)); + + signing = true; + progress = 0; + // we step by 500/inputs_count per input in phase1 and phase2 + // this means 50 % per phase. + progress_step = (500 << PROGRESS_PRECISION) / inputs_count; + + in_address_n_count = 0; + multisig_fp_set = false; + multisig_fp_mismatch = false; + next_nonsegwit_input = 0xffffffff; + + tx_init(&to, inputs_count, outputs_count, version, lock_time, expiry, 0, + coin->curve->hasher_sign, overwintered, version_group_id); + + if (coin->decred) { + to.version |= (DECRED_SERIALIZE_FULL << 16); + to.is_decred = true; + + tx_init(&ti, inputs_count, outputs_count, version, lock_time, expiry, 0, + coin->curve->hasher_sign, overwintered, version_group_id); + ti.version |= (DECRED_SERIALIZE_NO_WITNESS << 16); + ti.is_decred = true; + } + + // segwit hashes for hashPrevouts and hashSequence + if (overwintered) { + hasher_InitParam(&hasher_prevouts, HASHER_BLAKE2B_PERSONAL, + "ZcashPrevoutHash", 16); + hasher_InitParam(&hasher_sequence, HASHER_BLAKE2B_PERSONAL, + "ZcashSequencHash", 16); + hasher_InitParam(&hasher_outputs, HASHER_BLAKE2B_PERSONAL, + "ZcashOutputsHash", 16); + hasher_Init(&hasher_check, coin->curve->hasher_sign); + } else { + hasher_Init(&hasher_prevouts, coin->curve->hasher_sign); + hasher_Init(&hasher_sequence, coin->curve->hasher_sign); + hasher_Init(&hasher_outputs, coin->curve->hasher_sign); + hasher_Init(&hasher_check, coin->curve->hasher_sign); + } + + layoutProgressSwipe(_("Signing transaction"), 0); + + send_req_1_input(); +} + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +static bool signing_check_input(const TxInputType *txinput) { + /* compute multisig fingerprint */ + /* (if all input share the same fingerprint, outputs having the same + * fingerprint will be considered as change outputs) */ + if (txinput->has_multisig && !multisig_fp_mismatch) { + uint8_t h[32]; + if (cryptoMultisigFingerprint(&txinput->multisig, h) == 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Error computing multisig fingerprint")); + signing_abort(); + return false; + } + if (multisig_fp_set) { + if (memcmp(multisig_fp, h, 32) != 0) { + multisig_fp_mismatch = true; + } + } else { + memcpy(multisig_fp, h, 32); + multisig_fp_set = true; + } + } else { // single signature + multisig_fp_mismatch = true; + } + // remember the input bip32 path + // change addresses must use the same bip32 path as all inputs + extract_input_bip32_path(txinput); + // compute segwit hashPrevouts & hashSequence + tx_prevout_hash(&hasher_prevouts, txinput); + tx_sequence_hash(&hasher_sequence, txinput); + if (coin->decred) { + if (txinput->decred_script_version > 0) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Decred v1+ scripts are not supported")); + signing_abort(); + return false; + } + + // serialize Decred prefix in Phase 1 + resp.has_serialized = true; + resp.serialized.has_serialized_tx = true; + resp.serialized.serialized_tx.size = + tx_serialize_input(&to, txinput, resp.serialized.serialized_tx.bytes); + + // compute Decred hashPrefix + tx_serialize_input_hash(&ti, txinput); + } + // hash prevout and script type to check it later (relevant for fee + // computation) + tx_prevout_hash(&hasher_check, txinput); + hasher_Update(&hasher_check, (const uint8_t *)&txinput->script_type, + sizeof(&txinput->script_type)); + return true; +} + +// check if the hash of the prevtx matches +static bool signing_check_prevtx_hash(void) { + uint8_t hash[32]; + tx_hash_final(&tp, hash, true); + if (memcmp(hash, input.prev_hash.bytes, 32) != 0) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Encountered invalid prevhash")); + signing_abort(); + return false; + } + phase1_request_next_input(); + return true; +} + +static bool signing_check_output(TxOutputType *txoutput) { + // Phase1: Check outputs + // add it to hash_outputs + // ask user for permission + + // check for change address + bool is_change = false; + if (txoutput->address_n_count > 0) { + if (txoutput->has_address) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Address in change output")); + signing_abort(); + return false; + } + /* + * For multisig check that all inputs are multisig + */ + if (txoutput->has_multisig) { + uint8_t h[32]; + if (multisig_fp_set && !multisig_fp_mismatch && + cryptoMultisigFingerprint(&(txoutput->multisig), h) && + memcmp(multisig_fp, h, 32) == 0) { + is_change = check_change_bip32_path(txoutput); + } + } else { + is_change = check_change_bip32_path(txoutput); + } + /* + * only allow segwit change if amount is smaller than what segwit inputs + * paid. this was added during the times segwit was not yet fully activated + * to make sure the user is not tricked to use witness change output + * instead of regular one therefore creating ANYONECANSPEND output + */ + if ((txoutput->script_type == OutputScriptType_PAYTOWITNESS || + txoutput->script_type == OutputScriptType_PAYTOP2SHWITNESS) && + txoutput->amount > authorized_amount) { + is_change = false; + } + } + + if (is_change) { + if (change_spend == 0) { // not set + change_spend = txoutput->amount; + } else { + /* We only skip confirmation for the first change output */ + is_change = false; + } + } + + if (spending + txoutput->amount < spending) { + fsm_sendFailure(FailureType_Failure_DataError, _("Value overflow")); + signing_abort(); + return false; + } + spending += txoutput->amount; + int co = compile_output(coin, &root, txoutput, &bin_output, !is_change); + if (!is_change) { + layoutProgress(_("Signing transaction"), progress); + } + if (co < 0) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + signing_abort(); + return false; + } else if (co == 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile output")); + signing_abort(); + return false; + } + if (coin->decred) { + // serialize Decred prefix in Phase 1 + resp.has_serialized = true; + resp.serialized.has_serialized_tx = true; + resp.serialized.serialized_tx.size = tx_serialize_output( + &to, &bin_output, resp.serialized.serialized_tx.bytes); + + // compute Decred hashPrefix + tx_serialize_output_hash(&ti, &bin_output); + } + // compute segwit hashOuts + tx_output_hash(&hasher_outputs, &bin_output, coin->decred); + return true; +} + +static bool signing_check_fee(void) { + // check fees + if (spending > to_spend) { + fsm_sendFailure(FailureType_Failure_NotEnoughFunds, _("Not enough funds")); + signing_abort(); + return false; + } + uint64_t fee = to_spend - spending; + if (fee > ((uint64_t)tx_weight * coin->maxfee_kb) / 4000) { + layoutFeeOverThreshold(coin, fee); + if (!protectButton(ButtonRequestType_ButtonRequest_FeeOverThreshold, + false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + signing_abort(); + return false; + } + } + // last confirmation + layoutConfirmTx(coin, to_spend - change_spend, fee); + if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + signing_abort(); + return false; + } + return true; +} + +static uint32_t signing_hash_type(void) { + uint32_t hash_type = SIGHASH_ALL; + + if (coin->has_fork_id) { + hash_type |= (coin->fork_id << 8) | SIGHASH_FORKID; + } + + return hash_type; +} + +static void phase1_request_next_output(void) { + if (idx1 < outputs_count - 1) { + idx1++; + send_req_3_output(); + } else { + if (coin->decred) { + // compute Decred hashPrefix + tx_hash_final(&ti, hash_prefix, false); + } + hasher_Final(&hasher_outputs, hash_outputs); + if (!signing_check_fee()) { + return; + } + // Everything was checked, now phase 2 begins and the transaction is signed. + progress_meta_step = progress_step / (inputs_count + outputs_count); + layoutProgress(_("Signing transaction"), progress); + idx1 = 0; + if (coin->decred) { + // Decred prefix serialized in Phase 1, skip Phase 2 + send_req_decred_witness(); + } else { + phase2_request_next_input(); + } + } +} + +static void signing_hash_bip143(const TxInputType *txinput, uint8_t *hash) { + uint32_t hash_type = signing_hash_type(); + Hasher hasher_preimage; + hasher_Init(&hasher_preimage, coin->curve->hasher_sign); + hasher_Update(&hasher_preimage, (const uint8_t *)&version, 4); // nVersion + hasher_Update(&hasher_preimage, hash_prevouts, 32); // hashPrevouts + hasher_Update(&hasher_preimage, hash_sequence, 32); // hashSequence + tx_prevout_hash(&hasher_preimage, txinput); // outpoint + tx_script_hash(&hasher_preimage, txinput->script_sig.size, + txinput->script_sig.bytes); // scriptCode + hasher_Update(&hasher_preimage, (const uint8_t *)&txinput->amount, + 8); // amount + tx_sequence_hash(&hasher_preimage, txinput); // nSequence + hasher_Update(&hasher_preimage, hash_outputs, 32); // hashOutputs + hasher_Update(&hasher_preimage, (const uint8_t *)&lock_time, 4); // nLockTime + hasher_Update(&hasher_preimage, (const uint8_t *)&hash_type, 4); // nHashType + hasher_Final(&hasher_preimage, hash); +} + +static void signing_hash_zip143(const TxInputType *txinput, uint8_t *hash) { + uint32_t hash_type = signing_hash_type(); + uint8_t personal[16]; + memcpy(personal, "ZcashSigHash", 12); + memcpy(personal + 12, &branch_id, 4); + Hasher hasher_preimage; + hasher_InitParam(&hasher_preimage, HASHER_BLAKE2B_PERSONAL, personal, + sizeof(personal)); + uint32_t ver = version | TX_OVERWINTERED; // 1. nVersion | fOverwintered + hasher_Update(&hasher_preimage, (const uint8_t *)&ver, 4); + hasher_Update(&hasher_preimage, (const uint8_t *)&version_group_id, + 4); // 2. nVersionGroupId + hasher_Update(&hasher_preimage, hash_prevouts, 32); // 3. hashPrevouts + hasher_Update(&hasher_preimage, hash_sequence, 32); // 4. hashSequence + hasher_Update(&hasher_preimage, hash_outputs, 32); // 5. hashOutputs + // 6. hashJoinSplits + hasher_Update(&hasher_preimage, (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32); + hasher_Update(&hasher_preimage, (const uint8_t *)&lock_time, + 4); // 7. nLockTime + hasher_Update(&hasher_preimage, (const uint8_t *)&expiry, + 4); // 8. expiryHeight + hasher_Update(&hasher_preimage, (const uint8_t *)&hash_type, + 4); // 9. nHashType + + tx_prevout_hash(&hasher_preimage, txinput); // 10a. outpoint + tx_script_hash(&hasher_preimage, txinput->script_sig.size, + txinput->script_sig.bytes); // 10b. scriptCode + hasher_Update(&hasher_preimage, (const uint8_t *)&txinput->amount, + 8); // 10c. value + tx_sequence_hash(&hasher_preimage, txinput); // 10d. nSequence + + hasher_Final(&hasher_preimage, hash); +} + +static void signing_hash_zip243(const TxInputType *txinput, uint8_t *hash) { + uint32_t hash_type = signing_hash_type(); + uint8_t personal[16]; + memcpy(personal, "ZcashSigHash", 12); + memcpy(personal + 12, &branch_id, 4); + Hasher hasher_preimage; + hasher_InitParam(&hasher_preimage, HASHER_BLAKE2B_PERSONAL, personal, + sizeof(personal)); + uint32_t ver = version | TX_OVERWINTERED; // 1. nVersion | fOverwintered + hasher_Update(&hasher_preimage, (const uint8_t *)&ver, 4); + hasher_Update(&hasher_preimage, (const uint8_t *)&version_group_id, + 4); // 2. nVersionGroupId + hasher_Update(&hasher_preimage, hash_prevouts, 32); // 3. hashPrevouts + hasher_Update(&hasher_preimage, hash_sequence, 32); // 4. hashSequence + hasher_Update(&hasher_preimage, hash_outputs, 32); // 5. hashOutputs + // 6. hashJoinSplits + hasher_Update(&hasher_preimage, (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32); + // 7. hashShieldedSpends + hasher_Update(&hasher_preimage, (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32); + // 8. hashShieldedOutputs + hasher_Update(&hasher_preimage, (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32); + hasher_Update(&hasher_preimage, (const uint8_t *)&lock_time, + 4); // 9. nLockTime + hasher_Update(&hasher_preimage, (const uint8_t *)&expiry, + 4); // 10. expiryHeight + hasher_Update(&hasher_preimage, + (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00", + 8); // 11. valueBalance + hasher_Update(&hasher_preimage, (const uint8_t *)&hash_type, + 4); // 12. nHashType + + tx_prevout_hash(&hasher_preimage, txinput); // 13a. outpoint + tx_script_hash(&hasher_preimage, txinput->script_sig.size, + txinput->script_sig.bytes); // 13b. scriptCode + hasher_Update(&hasher_preimage, (const uint8_t *)&txinput->amount, + 8); // 13c. value + tx_sequence_hash(&hasher_preimage, txinput); // 13d. nSequence + + hasher_Final(&hasher_preimage, hash); +} + +static void signing_hash_decred(const uint8_t *hash_witness, uint8_t *hash) { + uint32_t hash_type = signing_hash_type(); + Hasher hasher_preimage; + hasher_Init(&hasher_preimage, coin->curve->hasher_sign); + hasher_Update(&hasher_preimage, (const uint8_t *)&hash_type, 4); + hasher_Update(&hasher_preimage, hash_prefix, 32); + hasher_Update(&hasher_preimage, hash_witness, 32); + hasher_Final(&hasher_preimage, hash); +} + +static bool signing_sign_hash(TxInputType *txinput, const uint8_t *private_key, + const uint8_t *public_key, const uint8_t *hash) { + resp.serialized.has_signature_index = true; + resp.serialized.signature_index = idx1; + resp.serialized.has_signature = true; + resp.serialized.has_serialized_tx = true; + if (ecdsa_sign_digest(coin->curve->params, private_key, hash, sig, NULL, + NULL) != 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed")); + signing_abort(); + return false; + } + resp.serialized.signature.size = + ecdsa_sig_to_der(sig, resp.serialized.signature.bytes); + + uint8_t sighash = signing_hash_type() & 0xff; + if (txinput->has_multisig) { + // fill in the signature + int pubkey_idx = + cryptoMultisigPubkeyIndex(coin, &(txinput->multisig), public_key); + if (pubkey_idx < 0) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Pubkey not found in multisig script")); + signing_abort(); + return false; + } + memcpy(txinput->multisig.signatures[pubkey_idx].bytes, + resp.serialized.signature.bytes, resp.serialized.signature.size); + txinput->multisig.signatures[pubkey_idx].size = + resp.serialized.signature.size; + txinput->script_sig.size = serialize_script_multisig( + coin, &(txinput->multisig), sighash, txinput->script_sig.bytes); + if (txinput->script_sig.size == 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to serialize multisig script")); + signing_abort(); + return false; + } + } else { // SPENDADDRESS + txinput->script_sig.size = serialize_script_sig( + resp.serialized.signature.bytes, resp.serialized.signature.size, + public_key, 33, sighash, txinput->script_sig.bytes); + } + return true; +} + +static bool signing_sign_input(void) { + uint8_t hash[32]; + hasher_Final(&hasher_check, hash); + if (memcmp(hash, hash_outputs, 32) != 0) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Transaction has changed during signing")); + signing_abort(); + return false; + } + + uint32_t hash_type = signing_hash_type(); + hasher_Update(&ti.hasher, (const uint8_t *)&hash_type, 4); + tx_hash_final(&ti, hash, false); + resp.has_serialized = true; + if (!signing_sign_hash(&input, privkey, pubkey, hash)) return false; + resp.serialized.serialized_tx.size = + tx_serialize_input(&to, &input, resp.serialized.serialized_tx.bytes); + return true; +} + +static bool signing_sign_segwit_input(TxInputType *txinput) { + // idx1: index to sign + uint8_t hash[32]; + + if (txinput->script_type == InputScriptType_SPENDWITNESS || + txinput->script_type == InputScriptType_SPENDP2SHWITNESS) { + if (!compile_input_script_sig(txinput)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile input")); + signing_abort(); + return false; + } + if (txinput->amount > authorized_amount) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Transaction has changed during signing")); + signing_abort(); + return false; + } + authorized_amount -= txinput->amount; + + signing_hash_bip143(txinput, hash); + + resp.has_serialized = true; + if (!signing_sign_hash(txinput, node.private_key, node.public_key, hash)) + return false; + + uint8_t sighash = signing_hash_type() & 0xff; + if (txinput->has_multisig) { + uint32_t r = 1; // skip number of items (filled in later) + resp.serialized.serialized_tx.bytes[r] = 0; + r++; + int nwitnesses = 2; + for (uint32_t i = 0; i < txinput->multisig.signatures_count; i++) { + if (txinput->multisig.signatures[i].size == 0) { + continue; + } + nwitnesses++; + txinput->multisig.signatures[i] + .bytes[txinput->multisig.signatures[i].size] = sighash; + r += tx_serialize_script(txinput->multisig.signatures[i].size + 1, + txinput->multisig.signatures[i].bytes, + resp.serialized.serialized_tx.bytes + r); + } + uint32_t script_len = + compile_script_multisig(coin, &txinput->multisig, 0); + r += ser_length(script_len, resp.serialized.serialized_tx.bytes + r); + r += compile_script_multisig(coin, &txinput->multisig, + resp.serialized.serialized_tx.bytes + r); + resp.serialized.serialized_tx.bytes[0] = nwitnesses; + resp.serialized.serialized_tx.size = r; + } else { // single signature + uint32_t r = 0; + r += ser_length(2, resp.serialized.serialized_tx.bytes + r); + resp.serialized.signature.bytes[resp.serialized.signature.size] = sighash; + r += tx_serialize_script(resp.serialized.signature.size + 1, + resp.serialized.signature.bytes, + resp.serialized.serialized_tx.bytes + r); + r += tx_serialize_script(33, node.public_key, + resp.serialized.serialized_tx.bytes + r); + resp.serialized.serialized_tx.size = r; + } + } else { + // empty witness + resp.has_serialized = true; + resp.serialized.has_signature_index = false; + resp.serialized.has_signature = false; + resp.serialized.has_serialized_tx = true; + resp.serialized.serialized_tx.bytes[0] = 0; + resp.serialized.serialized_tx.size = 1; + } + // if last witness add tx footer + if (idx1 == inputs_count - 1) { + uint32_t r = resp.serialized.serialized_tx.size; + r += tx_serialize_footer(&to, resp.serialized.serialized_tx.bytes + r); + resp.serialized.serialized_tx.size = r; + } + return true; +} + +static bool signing_sign_decred_input(TxInputType *txinput) { + uint8_t hash[32], hash_witness[32]; + tx_hash_final(&ti, hash_witness, false); + signing_hash_decred(hash_witness, hash); + resp.has_serialized = true; + if (!signing_sign_hash(txinput, node.private_key, node.public_key, hash)) + return false; + resp.serialized.serialized_tx.size = tx_serialize_decred_witness( + &to, txinput, resp.serialized.serialized_tx.bytes); + return true; +} + +#define ENABLE_SEGWIT_NONSEGWIT_MIXING 1 + +void signing_txack(TransactionType *tx) { + if (!signing) { + fsm_sendFailure(FailureType_Failure_UnexpectedMessage, + _("Not in Signing mode")); + layoutHome(); + return; + } + + static int update_ctr = 0; + if (update_ctr++ == 20) { + layoutProgress(_("Signing transaction"), progress); + update_ctr = 0; + } + + memzero(&resp, sizeof(TxRequest)); + + switch (signing_stage) { + case STAGE_REQUEST_1_INPUT: + signing_check_input(&tx->inputs[0]); + + tx_weight += tx_input_weight(coin, &tx->inputs[0]); + if (coin->decred) { + tx_weight += tx_decred_witness_weight(&tx->inputs[0]); + } + + if (tx->inputs[0].script_type == InputScriptType_SPENDMULTISIG || + tx->inputs[0].script_type == InputScriptType_SPENDADDRESS) { + memcpy(&input, tx->inputs, sizeof(TxInputType)); +#if !ENABLE_SEGWIT_NONSEGWIT_MIXING + // don't mix segwit and non-segwit inputs + if (idx1 > 0 && to.is_segwit == true) { + fsm_sendFailure( + FailureType_Failure_DataError, + _("Mixing segwit and non-segwit inputs is not allowed")); + signing_abort(); + return; + } +#endif + + if (coin->force_bip143 || overwintered) { + if (!tx->inputs[0].has_amount) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Expected input with amount")); + signing_abort(); + return; + } + if (to_spend + tx->inputs[0].amount < to_spend) { + fsm_sendFailure(FailureType_Failure_DataError, _("Value overflow")); + signing_abort(); + return; + } + to_spend += tx->inputs[0].amount; + authorized_amount += tx->inputs[0].amount; + phase1_request_next_input(); + } else { + // remember the first non-segwit input -- this is the first input + // we need to sign during phase2 + if (next_nonsegwit_input == 0xffffffff) next_nonsegwit_input = idx1; + send_req_2_prev_meta(); + } + } else if (tx->inputs[0].script_type == InputScriptType_SPENDWITNESS || + tx->inputs[0].script_type == + InputScriptType_SPENDP2SHWITNESS) { + if (coin->decred) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Decred does not support Segwit")); + signing_abort(); + return; + } + if (!coin->has_segwit) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Segwit not enabled on this coin")); + signing_abort(); + return; + } + if (!tx->inputs[0].has_amount) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Segwit input without amount")); + signing_abort(); + return; + } + if (to_spend + tx->inputs[0].amount < to_spend) { + fsm_sendFailure(FailureType_Failure_DataError, _("Value overflow")); + signing_abort(); + return; + } + if (!to.is_segwit) { + tx_weight += TXSIZE_SEGWIT_OVERHEAD + to.inputs_len; + } +#if !ENABLE_SEGWIT_NONSEGWIT_MIXING + // don't mix segwit and non-segwit inputs + if (idx1 == 0) { + to.is_segwit = true; + } else if (to.is_segwit == false) { + fsm_sendFailure( + FailureType_Failure_DataError, + _("Mixing segwit and non-segwit inputs is not allowed")); + signing_abort(); + return; + } +#else + to.is_segwit = true; +#endif + to_spend += tx->inputs[0].amount; + authorized_amount += tx->inputs[0].amount; + phase1_request_next_input(); + } else { + fsm_sendFailure(FailureType_Failure_DataError, + _("Wrong input script type")); + signing_abort(); + return; + } + return; + case STAGE_REQUEST_2_PREV_META: + if (tx->outputs_cnt <= input.prev_index) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Not enough outputs in previous transaction.")); + signing_abort(); + return; + } + if (tx->inputs_cnt + tx->outputs_cnt < tx->inputs_cnt) { + fsm_sendFailure(FailureType_Failure_DataError, _("Value overflow")); + signing_abort(); + return; + } + tx_init(&tp, tx->inputs_cnt, tx->outputs_cnt, tx->version, tx->lock_time, + tx->expiry, tx->extra_data_len, coin->curve->hasher_sign, + overwintered, version_group_id); + if (coin->decred) { + tp.version |= (DECRED_SERIALIZE_NO_WITNESS << 16); + tp.is_decred = true; + } + progress_meta_step = progress_step / (tp.inputs_len + tp.outputs_len); + idx2 = 0; + if (tp.inputs_len > 0) { + send_req_2_prev_input(); + } else { + tx_serialize_header_hash(&tp); + send_req_2_prev_output(); + } + return; + case STAGE_REQUEST_2_PREV_INPUT: + progress = (idx1 * progress_step + idx2 * progress_meta_step) >> + PROGRESS_PRECISION; + if (!tx_serialize_input_hash(&tp, tx->inputs)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to serialize input")); + signing_abort(); + return; + } + if (idx2 < tp.inputs_len - 1) { + idx2++; + send_req_2_prev_input(); + } else { + idx2 = 0; + send_req_2_prev_output(); + } + return; + case STAGE_REQUEST_2_PREV_OUTPUT: + progress = (idx1 * progress_step + + (tp.inputs_len + idx2) * progress_meta_step) >> + PROGRESS_PRECISION; + if (!tx_serialize_output_hash(&tp, tx->bin_outputs)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to serialize output")); + signing_abort(); + return; + } + if (idx2 == input.prev_index) { + if (to_spend + tx->bin_outputs[0].amount < to_spend) { + fsm_sendFailure(FailureType_Failure_DataError, _("Value overflow")); + signing_abort(); + return; + } + if (coin->decred && tx->bin_outputs[0].decred_script_version > 0) { + fsm_sendFailure( + FailureType_Failure_DataError, + _("Decred script version does not match previous output")); + signing_abort(); + return; + } + to_spend += tx->bin_outputs[0].amount; + } + if (idx2 < tp.outputs_len - 1) { + /* Check prevtx of next input */ + idx2++; + send_req_2_prev_output(); + } else if (tp.extra_data_len > 0) { // has extra data + send_req_2_prev_extradata(0, MIN(1024, tp.extra_data_len)); + return; + } else { + /* prevtx is done */ + signing_check_prevtx_hash(); + } + return; + case STAGE_REQUEST_2_PREV_EXTRADATA: + if (!tx_serialize_extra_data_hash(&tp, tx->extra_data.bytes, + tx->extra_data.size)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to serialize extra data")); + signing_abort(); + return; + } + if (tp.extra_data_received < + tp.extra_data_len) { // still some data remanining + send_req_2_prev_extradata( + tp.extra_data_received, + MIN(1024, tp.extra_data_len - tp.extra_data_received)); + } else { + signing_check_prevtx_hash(); + } + return; + case STAGE_REQUEST_3_OUTPUT: + if (!signing_check_output(&tx->outputs[0])) { + return; + } + tx_weight += tx_output_weight(coin, &tx->outputs[0]); + phase1_request_next_output(); + return; + case STAGE_REQUEST_4_INPUT: + progress = + 500 + ((signatures * progress_step + idx2 * progress_meta_step) >> + PROGRESS_PRECISION); + if (idx2 == 0) { + tx_init(&ti, inputs_count, outputs_count, version, lock_time, expiry, 0, + coin->curve->hasher_sign, overwintered, version_group_id); + hasher_Reset(&hasher_check); + } + // check prevouts and script type + tx_prevout_hash(&hasher_check, tx->inputs); + hasher_Update(&hasher_check, (const uint8_t *)&tx->inputs[0].script_type, + sizeof(&tx->inputs[0].script_type)); + if (idx2 == idx1) { + if (!compile_input_script_sig(&tx->inputs[0])) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile input")); + signing_abort(); + return; + } + memcpy(&input, &tx->inputs[0], sizeof(input)); + memcpy(privkey, node.private_key, 32); + memcpy(pubkey, node.public_key, 33); + } else { + if (next_nonsegwit_input == idx1 && idx2 > idx1 && + (tx->inputs[0].script_type == InputScriptType_SPENDADDRESS || + tx->inputs[0].script_type == InputScriptType_SPENDMULTISIG)) { + next_nonsegwit_input = idx2; + } + tx->inputs[0].script_sig.size = 0; + } + if (!tx_serialize_input_hash(&ti, tx->inputs)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to serialize input")); + signing_abort(); + return; + } + if (idx2 < inputs_count - 1) { + idx2++; + send_req_4_input(); + } else { + uint8_t hash[32]; + hasher_Final(&hasher_check, hash); + if (memcmp(hash, hash_check, 32) != 0) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Transaction has changed during signing")); + signing_abort(); + return; + } + hasher_Reset(&hasher_check); + idx2 = 0; + send_req_4_output(); + } + return; + case STAGE_REQUEST_4_OUTPUT: + progress = 500 + ((signatures * progress_step + + (inputs_count + idx2) * progress_meta_step) >> + PROGRESS_PRECISION); + if (compile_output(coin, &root, tx->outputs, &bin_output, false) <= 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile output")); + signing_abort(); + return; + } + // check hashOutputs + tx_output_hash(&hasher_check, &bin_output, coin->decred); + if (!tx_serialize_output_hash(&ti, &bin_output)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to serialize output")); + signing_abort(); + return; + } + if (idx2 < outputs_count - 1) { + idx2++; + send_req_4_output(); + } else { + if (!signing_sign_input()) { + return; + } + // since this took a longer time, update progress + signatures++; + progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); + layoutProgress(_("Signing transaction"), progress); + update_ctr = 0; + if (idx1 < inputs_count - 1) { + idx1++; + phase2_request_next_input(); + } else { + idx1 = 0; + send_req_5_output(); + } + } + return; + + case STAGE_REQUEST_SEGWIT_INPUT: + resp.has_serialized = true; + resp.serialized.has_signature_index = false; + resp.serialized.has_signature = false; + resp.serialized.has_serialized_tx = true; + if (tx->inputs[0].script_type == InputScriptType_SPENDMULTISIG || + tx->inputs[0].script_type == InputScriptType_SPENDADDRESS) { + if (!(coin->force_bip143 || overwintered)) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Transaction has changed during signing")); + signing_abort(); + return; + } + if (!compile_input_script_sig(&tx->inputs[0])) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile input")); + signing_abort(); + return; + } + if (tx->inputs[0].amount > authorized_amount) { + fsm_sendFailure(FailureType_Failure_DataError, + _("Transaction has changed during signing")); + signing_abort(); + return; + } + authorized_amount -= tx->inputs[0].amount; + + uint8_t hash[32]; + if (overwintered) { + switch (version) { + case 3: + signing_hash_zip143(&tx->inputs[0], hash); + break; + case 4: + signing_hash_zip243(&tx->inputs[0], hash); + break; + default: + fsm_sendFailure( + FailureType_Failure_DataError, + _("Unsupported version for overwintered transaction")); + signing_abort(); + return; + } + } else { + signing_hash_bip143(&tx->inputs[0], hash); + } + if (!signing_sign_hash(&tx->inputs[0], node.private_key, + node.public_key, hash)) + return; + // since this took a longer time, update progress + signatures++; + progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); + layoutProgress(_("Signing transaction"), progress); + update_ctr = 0; + } else if (tx->inputs[0].script_type == + InputScriptType_SPENDP2SHWITNESS && + !tx->inputs[0].has_multisig) { + if (!compile_input_script_sig(&tx->inputs[0])) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile input")); + signing_abort(); + return; + } + // fixup normal p2pkh script into witness 0 p2wpkh script for p2sh + // we convert 76 A9 14 88 AC to 16 00 14 + // P2SH input pushes witness 0 script + tx->inputs[0].script_sig.size = 0x17; // drops last 2 bytes. + tx->inputs[0].script_sig.bytes[0] = + 0x16; // push 22 bytes; replaces OP_DUP + tx->inputs[0].script_sig.bytes[1] = + 0x00; // witness 0 script ; replaces OP_HASH160 + // digest is already in right place. + } else if (tx->inputs[0].script_type == + InputScriptType_SPENDP2SHWITNESS) { + // Prepare P2SH witness script. + tx->inputs[0].script_sig.size = 0x23; // 35 bytes long: + tx->inputs[0].script_sig.bytes[0] = + 0x22; // push 34 bytes (full witness script) + tx->inputs[0].script_sig.bytes[1] = 0x00; // witness 0 script + tx->inputs[0].script_sig.bytes[2] = 0x20; // push 32 bytes (digest) + // compute digest of multisig script + if (!compile_script_multisig_hash(coin, &tx->inputs[0].multisig, + tx->inputs[0].script_sig.bytes + 3)) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile input")); + signing_abort(); + return; + } + } else { + // direct witness scripts require zero scriptSig + tx->inputs[0].script_sig.size = 0; + } + resp.serialized.serialized_tx.size = tx_serialize_input( + &to, &tx->inputs[0], resp.serialized.serialized_tx.bytes); + if (idx1 < inputs_count - 1) { + idx1++; + phase2_request_next_input(); + } else { + idx1 = 0; + send_req_5_output(); + } + return; + + case STAGE_REQUEST_5_OUTPUT: + if (compile_output(coin, &root, tx->outputs, &bin_output, false) <= 0) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile output")); + signing_abort(); + return; + } + resp.has_serialized = true; + resp.serialized.has_serialized_tx = true; + resp.serialized.serialized_tx.size = tx_serialize_output( + &to, &bin_output, resp.serialized.serialized_tx.bytes); + if (idx1 < outputs_count - 1) { + idx1++; + send_req_5_output(); + } else if (to.is_segwit) { + idx1 = 0; + send_req_segwit_witness(); + } else { + send_req_finished(); + signing_abort(); + } + return; + + case STAGE_REQUEST_SEGWIT_WITNESS: + if (!signing_sign_segwit_input(&tx->inputs[0])) { + return; + } + signatures++; + progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); + layoutProgress(_("Signing transaction"), progress); + update_ctr = 0; + if (idx1 < inputs_count - 1) { + idx1++; + send_req_segwit_witness(); + } else { + send_req_finished(); + signing_abort(); + } + return; + + case STAGE_REQUEST_DECRED_WITNESS: + progress = + 500 + ((signatures * progress_step + idx2 * progress_meta_step) >> + PROGRESS_PRECISION); + if (idx1 == 0) { + // witness + tx_init(&to, inputs_count, outputs_count, version, lock_time, expiry, 0, + coin->curve->hasher_sign, overwintered, version_group_id); + to.is_decred = true; + } + + // witness hash + tx_init(&ti, inputs_count, outputs_count, version, lock_time, expiry, 0, + coin->curve->hasher_sign, overwintered, version_group_id); + ti.version |= (DECRED_SERIALIZE_WITNESS_SIGNING << 16); + ti.is_decred = true; + if (!compile_input_script_sig(&tx->inputs[0])) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to compile input")); + signing_abort(); + return; + } + + for (idx2 = 0; idx2 < inputs_count; idx2++) { + uint32_t r; + if (idx2 == idx1) { + r = tx_serialize_decred_witness_hash(&ti, &tx->inputs[0]); + } else { + r = tx_serialize_decred_witness_hash(&ti, NULL); + } + + if (!r) { + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Failed to serialize input")); + signing_abort(); + return; + } + } + + if (!signing_sign_decred_input(&tx->inputs[0])) { + return; + } + // since this took a longer time, update progress + signatures++; + progress = 500 + ((signatures * progress_step) >> PROGRESS_PRECISION); + layoutProgress(_("Signing transaction"), progress); + update_ctr = 0; + if (idx1 < inputs_count - 1) { + idx1++; + send_req_decred_witness(); + } else { + send_req_finished(); + signing_abort(); + } + return; + } + + fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing error")); + signing_abort(); +} + +void signing_abort(void) { + if (signing) { + layoutHome(); + signing = false; + } + memzero(&root, sizeof(root)); + memzero(&node, sizeof(node)); +} diff --git a/legacy/firmware/signing.h b/legacy/firmware/signing.h new file mode 100644 index 0000000000..af74d4a6b0 --- /dev/null +++ b/legacy/firmware/signing.h @@ -0,0 +1,35 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __SIGNING_H__ +#define __SIGNING_H__ + +#include +#include +#include "bip32.h" +#include "coins.h" +#include "hasher.h" +#include "messages-bitcoin.pb.h" + +void signing_init(const SignTx *msg, const CoinInfo *_coin, + const HDNode *_root); +void signing_abort(void); +void signing_txack(TransactionType *tx); + +#endif diff --git a/legacy/firmware/stellar.c b/legacy/firmware/stellar.c new file mode 100644 index 0000000000..d4f46e4e84 --- /dev/null +++ b/legacy/firmware/stellar.c @@ -0,0 +1,1831 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 ZuluCrypto + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +// Stellar signing workflow: +// +// 1. Client sends a StellarSignTx method to the device with transaction header +// information +// 2. Device confirms transaction details with the user and requests first +// operation +// 3. Client sends protobuf message with details about the operation to sign +// 4. Device confirms operation with user +// 5a. If there are more operations in the transaction, device responds with +// StellarTxOpRequest. Go to 3 5b. If the operation is the last one, device +// responds with StellarSignedTx + +#include "stellar.h" +#include +#include +#include "base32.h" +#include "bignum.h" +#include "bip32.h" +#include "config.h" +#include "crypto.h" +#include "fonts.h" +#include "fsm.h" +#include "gettext.h" +#include "layout2.h" +#include "memzero.h" +#include "messages.h" +#include "messages.pb.h" +#include "oled.h" +#include "protect.h" +#include "util.h" + +static bool stellar_signing = false; +static StellarTransaction stellar_activeTx; + +/* + * Starts the signing process and parses the transaction header + */ +bool stellar_signingInit(const StellarSignTx *msg) { + memzero(&stellar_activeTx, sizeof(StellarTransaction)); + stellar_signing = true; + // Initialize signing context + sha256_Init(&(stellar_activeTx.sha256_ctx)); + + // Calculate sha256 for network passphrase + // max length defined in messages.options + uint8_t network_hash[32]; + sha256_Raw((uint8_t *)msg->network_passphrase, + strnlen(msg->network_passphrase, 1024), network_hash); + + uint8_t tx_type_bytes[4] = {0x00, 0x00, 0x00, 0x02}; + + // Copy some data into the active tx + stellar_activeTx.num_operations = msg->num_operations; + + // Start building what will be signed: + // sha256 of: + // sha256(network passphrase) + // 4-byte unsigned big-endian int type constant (2 for tx) + // remaining bytes are operations added in subsequent messages + stellar_hashupdate_bytes(network_hash, sizeof(network_hash)); + stellar_hashupdate_bytes(tx_type_bytes, sizeof(tx_type_bytes)); + + // Public key comes from deriving the specified account path + const HDNode *node = stellar_deriveNode(msg->address_n, msg->address_n_count); + if (!node) { + return false; + } + memcpy(&(stellar_activeTx.signing_pubkey), node->public_key + 1, + sizeof(stellar_activeTx.signing_pubkey)); + + stellar_activeTx.address_n_count = msg->address_n_count; + // todo: fix sizeof check + memcpy(&(stellar_activeTx.address_n), &(msg->address_n), + sizeof(stellar_activeTx.address_n)); + + // Hash: public key + stellar_hashupdate_address(node->public_key + 1); + + // Hash: fee + stellar_hashupdate_uint32(msg->fee); + + // Hash: sequence number + stellar_hashupdate_uint64(msg->sequence_number); + + // Timebounds are only present if timebounds_start or timebounds_end is + // non-zero + uint8_t has_timebounds = + (msg->timebounds_start > 0 || msg->timebounds_end > 0); + if (has_timebounds) { + // Hash: the "has timebounds?" boolean + stellar_hashupdate_bool(true); + + // Timebounds are sent as uint32s since that's all we can display, but they + // must be hashed as 64-bit values + stellar_hashupdate_uint32(0); + stellar_hashupdate_uint32(msg->timebounds_start); + + stellar_hashupdate_uint32(0); + stellar_hashupdate_uint32(msg->timebounds_end); + } + // No timebounds, hash a false boolean + else { + stellar_hashupdate_bool(false); + } + + // Hash: memo + stellar_hashupdate_uint32(msg->memo_type); + switch (msg->memo_type) { + // None, nothing else to do + case 0: + break; + // Text: 4 bytes (size) + up to 28 bytes + case 1: + stellar_hashupdate_string((unsigned char *)&(msg->memo_text), + strnlen(msg->memo_text, 28)); + break; + // ID (8 bytes, uint64) + case 2: + stellar_hashupdate_uint64(msg->memo_id); + break; + // Hash and return are the same data structure (32 byte tx hash) + case 3: + case 4: + stellar_hashupdate_bytes(msg->memo_hash.bytes, msg->memo_hash.size); + break; + default: + break; + } + + // Hash: number of operations + stellar_hashupdate_uint32(msg->num_operations); + + // Determine what type of network this transaction is for + if (strncmp("Public Global Stellar Network ; September 2015", + msg->network_passphrase, 1024) == 0) { + stellar_activeTx.network_type = 1; + } else if (strncmp("Test SDF Network ; September 2015", + msg->network_passphrase, 1024) == 0) { + stellar_activeTx.network_type = 2; + } else { + stellar_activeTx.network_type = 3; + } + + return true; +} + +bool stellar_confirmSourceAccount(bool has_source_account, + const char *str_account) { + if (!has_source_account) { + stellar_hashupdate_bool(false); + return true; + } + + // Convert account string to public key bytes + uint8_t bytes[32]; + if (!stellar_getAddressBytes(str_account, bytes)) { + return false; + } + + const char **str_addr_rows = stellar_lineBreakAddress(bytes); + + stellar_layoutTransactionDialog(_("Op src account OK?"), NULL, + str_addr_rows[0], str_addr_rows[1], + str_addr_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash: source account + stellar_hashupdate_address(bytes); + + return true; +} + +bool stellar_confirmCreateAccountOp(const StellarCreateAccountOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(0); + + // Validate new account and convert to bytes + uint8_t new_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->new_account, new_account_bytes)) { + stellar_signingAbort(_("Invalid new account address")); + return false; + } + + const char **str_addr_rows = stellar_lineBreakAddress(new_account_bytes); + + // Amount being funded + char str_amount_line[32]; + char str_amount[32]; + stellar_format_stroops(msg->starting_balance, str_amount, sizeof(str_amount)); + + strlcpy(str_amount_line, _("With "), sizeof(str_amount_line)); + strlcat(str_amount_line, str_amount, sizeof(str_amount_line)); + strlcat(str_amount_line, _(" XLM"), sizeof(str_amount_line)); + + stellar_layoutTransactionDialog(_("Create account: "), str_addr_rows[0], + str_addr_rows[1], str_addr_rows[2], + str_amount_line); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash: address + stellar_hashupdate_address(new_account_bytes); + // Hash: starting amount + stellar_hashupdate_uint64(msg->starting_balance); + + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmPaymentOp(const StellarPaymentOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(1); + + // Validate destination account and convert to bytes + uint8_t destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->destination_account, + destination_account_bytes)) { + stellar_signingAbort(_("Invalid destination account")); + return false; + } + + const char **str_addr_rows = + stellar_lineBreakAddress(destination_account_bytes); + + // To: G... + char str_to[32]; + strlcpy(str_to, _("To: "), sizeof(str_to)); + strlcat(str_to, str_addr_rows[0], sizeof(str_to)); + + char str_asset_row[32]; + memzero(str_asset_row, sizeof(str_asset_row)); + stellar_format_asset(&(msg->asset), str_asset_row, sizeof(str_asset_row)); + + char str_pay_amount[32]; + char str_amount[32]; + stellar_format_stroops(msg->amount, str_amount, sizeof(str_amount)); + + strlcpy(str_pay_amount, _("Pay "), sizeof(str_pay_amount)); + strlcat(str_pay_amount, str_amount, sizeof(str_pay_amount)); + + stellar_layoutTransactionDialog(str_pay_amount, str_asset_row, str_to, + str_addr_rows[1], str_addr_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash destination + stellar_hashupdate_address(destination_account_bytes); + // asset + stellar_hashupdate_asset(&(msg->asset)); + // amount (even though amount is signed it doesn't matter for hashing) + stellar_hashupdate_uint64(msg->amount); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmPathPaymentOp(const StellarPathPaymentOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(2); + + // Validate destination account and convert to bytes + uint8_t destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->destination_account, + destination_account_bytes)) { + stellar_signingAbort(_("Invalid destination account")); + return false; + } + const char **str_dest_rows = + stellar_lineBreakAddress(destination_account_bytes); + + // To: G... + char str_to[32]; + strlcpy(str_to, _("To: "), sizeof(str_to)); + strlcat(str_to, str_dest_rows[0], sizeof(str_to)); + + char str_send_asset[32]; + char str_dest_asset[32]; + stellar_format_asset(&(msg->send_asset), str_send_asset, + sizeof(str_send_asset)); + stellar_format_asset(&(msg->destination_asset), str_dest_asset, + sizeof(str_dest_asset)); + + char str_pay_amount[32]; + char str_amount[32]; + stellar_format_stroops(msg->destination_amount, str_amount, + sizeof(str_amount)); + + strlcpy(str_pay_amount, _("Path Pay "), sizeof(str_pay_amount)); + strlcat(str_pay_amount, str_amount, sizeof(str_pay_amount)); + + // Confirm what the receiver will get + /* + Path Pay 100 + JPY (G1234ABCDEF) + To: G.... + .... + .... + */ + stellar_layoutTransactionDialog(str_pay_amount, str_dest_asset, str_to, + str_dest_rows[1], str_dest_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Confirm what the sender is using to pay + char str_source_amount[32]; + char str_source_number[32]; + stellar_format_stroops(msg->send_max, str_source_number, + sizeof(str_source_number)); + + strlcpy(str_source_amount, _("Pay Using "), sizeof(str_source_amount)); + strlcat(str_source_amount, str_source_number, sizeof(str_source_amount)); + + stellar_layoutTransactionDialog(str_source_amount, str_send_asset, NULL, + _("This is the amount debited"), + _("from your account.")); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + // Note: no confirmation for intermediate steps since they don't impact the + // user + + // Hash send asset + stellar_hashupdate_asset(&(msg->send_asset)); + // send max (signed vs. unsigned doesn't matter wrt hashing) + stellar_hashupdate_uint64(msg->send_max); + // destination account + stellar_hashupdate_address(destination_account_bytes); + // destination asset + stellar_hashupdate_asset(&(msg->destination_asset)); + // destination amount + stellar_hashupdate_uint64(msg->destination_amount); + + // paths are stored as an array so hash the number of elements as a uint32 + stellar_hashupdate_uint32(msg->paths_count); + for (uint8_t i = 0; i < msg->paths_count; i++) { + stellar_hashupdate_asset(&(msg->paths[i])); + } + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmManageOfferOp(const StellarManageOfferOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(3); + + // New Offer / Delete #123 / Update #123 + char str_offer[32]; + if (msg->offer_id == 0) { + strlcpy(str_offer, _("New Offer"), sizeof(str_offer)); + } else { + char str_offer_id[20]; + stellar_format_uint64(msg->offer_id, str_offer_id, sizeof(str_offer_id)); + + if (msg->amount == 0) { + strlcpy(str_offer, _("Delete #"), sizeof(str_offer)); + } else { + strlcpy(str_offer, _("Update #"), sizeof(str_offer)); + } + + strlcat(str_offer, str_offer_id, sizeof(str_offer)); + } + + char str_selling[32]; + char str_sell_amount[32]; + char str_selling_asset[32]; + + stellar_format_asset(&(msg->selling_asset), str_selling_asset, + sizeof(str_selling_asset)); + stellar_format_stroops(msg->amount, str_sell_amount, sizeof(str_sell_amount)); + + /* + Sell 200 + XLM (Native Asset) + */ + strlcpy(str_selling, _("Sell "), sizeof(str_selling)); + strlcat(str_selling, str_sell_amount, sizeof(str_selling)); + + char str_buying[32]; + char str_buying_asset[32]; + char str_price[32]; + + stellar_format_asset(&(msg->buying_asset), str_buying_asset, + sizeof(str_buying_asset)); + stellar_format_price(msg->price_n, msg->price_d, str_price, + sizeof(str_price)); + + /* + For 0.675952 Per + USD (G12345678) + */ + strlcpy(str_buying, _("For "), sizeof(str_buying)); + strlcat(str_buying, str_price, sizeof(str_buying)); + strlcat(str_buying, _(" Per"), sizeof(str_buying)); + + stellar_layoutTransactionDialog(str_offer, str_selling, str_selling_asset, + str_buying, str_buying_asset); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash selling asset + stellar_hashupdate_asset(&(msg->selling_asset)); + // buying asset + stellar_hashupdate_asset(&(msg->buying_asset)); + // amount to sell (signed vs. unsigned doesn't matter wrt hashing) + stellar_hashupdate_uint64(msg->amount); + // numerator + stellar_hashupdate_uint32(msg->price_n); + // denominator + stellar_hashupdate_uint32(msg->price_d); + // offer ID + stellar_hashupdate_uint64(msg->offer_id); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmCreatePassiveOfferOp( + const StellarCreatePassiveOfferOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(4); + + // New Offer / Delete #123 / Update #123 + char str_offer[32]; + if (msg->amount == 0) { + strlcpy(str_offer, _("Delete Passive Offer"), sizeof(str_offer)); + } else { + strlcpy(str_offer, _("New Passive Offer"), sizeof(str_offer)); + } + + char str_selling[32]; + char str_sell_amount[32]; + char str_selling_asset[32]; + + stellar_format_asset(&(msg->selling_asset), str_selling_asset, + sizeof(str_selling_asset)); + stellar_format_stroops(msg->amount, str_sell_amount, sizeof(str_sell_amount)); + + /* + Sell 200 + XLM (Native Asset) + */ + strlcpy(str_selling, _("Sell "), sizeof(str_selling)); + strlcat(str_selling, str_sell_amount, sizeof(str_selling)); + + char str_buying[32]; + char str_buying_asset[32]; + char str_price[32]; + + stellar_format_asset(&(msg->buying_asset), str_buying_asset, + sizeof(str_buying_asset)); + stellar_format_price(msg->price_n, msg->price_d, str_price, + sizeof(str_price)); + + /* + For 0.675952 Per + USD (G12345678) + */ + strlcpy(str_buying, _("For "), sizeof(str_buying)); + strlcat(str_buying, str_price, sizeof(str_buying)); + strlcat(str_buying, _(" Per"), sizeof(str_buying)); + + stellar_layoutTransactionDialog(str_offer, str_selling, str_selling_asset, + str_buying, str_buying_asset); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash selling asset + stellar_hashupdate_asset(&(msg->selling_asset)); + // buying asset + stellar_hashupdate_asset(&(msg->buying_asset)); + // amount to sell (signed vs. unsigned doesn't matter wrt hashing) + stellar_hashupdate_uint64(msg->amount); + // numerator + stellar_hashupdate_uint32(msg->price_n); + // denominator + stellar_hashupdate_uint32(msg->price_d); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmSetOptionsOp(const StellarSetOptionsOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(5); + + // Something like Set Inflation Destination + char str_title[32]; + char rows[4][32]; + int row_idx = 0; + memzero(rows, sizeof(rows)); + + // Inflation destination + stellar_hashupdate_bool(msg->has_inflation_destination_account); + if (msg->has_inflation_destination_account) { + strlcpy(str_title, _("Set Inflation Destination"), sizeof(str_title)); + + // Validate account and convert to bytes + uint8_t inflation_destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->inflation_destination_account, + inflation_destination_account_bytes)) { + stellar_signingAbort(_("Invalid inflation destination account")); + return false; + } + const char **str_addr_rows = + stellar_lineBreakAddress(inflation_destination_account_bytes); + + stellar_layoutTransactionDialog(str_title, NULL, str_addr_rows[0], + str_addr_rows[1], str_addr_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // address + stellar_hashupdate_address(inflation_destination_account_bytes); + } + + // Clear flags + stellar_hashupdate_bool(msg->has_clear_flags); + if (msg->has_clear_flags) { + strlcpy(str_title, _("Clear Flag(s)"), sizeof(str_title)); + + // Auth required + if (msg->clear_flags & 0x01) { + strlcpy(rows[row_idx], _("AUTH_REQUIRED"), sizeof(rows[row_idx])); + row_idx++; + } + // Auth revocable + if (msg->clear_flags & 0x02) { + strlcpy(rows[row_idx], _("AUTH_REVOCABLE"), sizeof(rows[row_idx])); + row_idx++; + } + + stellar_layoutTransactionDialog(str_title, rows[0], rows[1], rows[2], + rows[3]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + memzero(rows, sizeof(rows)); + row_idx = 0; + + // Hash flags + stellar_hashupdate_uint32(msg->clear_flags); + } + + // Set flags + stellar_hashupdate_bool(msg->has_set_flags); + if (msg->has_set_flags) { + strlcpy(str_title, _("Set Flag(s)"), sizeof(str_title)); + + // Auth required + if (msg->set_flags & 0x01) { + strlcpy(rows[row_idx], _("AUTH_REQUIRED"), sizeof(rows[row_idx])); + row_idx++; + } + // Auth revocable + if (msg->set_flags & 0x02) { + strlcpy(rows[row_idx], _("AUTH_REVOCABLE"), sizeof(rows[row_idx])); + row_idx++; + } + + stellar_layoutTransactionDialog(str_title, rows[0], rows[1], rows[2], + rows[3]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + memzero(rows, sizeof(rows)); + row_idx = 0; + + // Hash flags + stellar_hashupdate_uint32(msg->set_flags); + } + + // Account thresholds + bool show_thresholds_confirm = false; + row_idx = 0; + stellar_hashupdate_bool(msg->has_master_weight); + if (msg->has_master_weight) { + char str_master_weight[10 + 1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->master_weight, str_master_weight, + sizeof(str_master_weight)); + strlcpy(rows[row_idx], _("Master Weight: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_master_weight, sizeof(rows[row_idx])); + row_idx++; + + // Hash master weight + stellar_hashupdate_uint32(msg->master_weight); + } + + stellar_hashupdate_bool(msg->has_low_threshold); + if (msg->has_low_threshold) { + char str_low_threshold[10 + 1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->low_threshold, str_low_threshold, + sizeof(str_low_threshold)); + strlcpy(rows[row_idx], _("Low: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_low_threshold, sizeof(rows[row_idx])); + row_idx++; + + // Hash low threshold + stellar_hashupdate_uint32(msg->low_threshold); + } + stellar_hashupdate_bool(msg->has_medium_threshold); + if (msg->has_medium_threshold) { + char str_med_threshold[10 + 1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->medium_threshold, str_med_threshold, + sizeof(str_med_threshold)); + strlcpy(rows[row_idx], _("Medium: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_med_threshold, sizeof(rows[row_idx])); + row_idx++; + + // Hash medium threshold + stellar_hashupdate_uint32(msg->medium_threshold); + } + stellar_hashupdate_bool(msg->has_high_threshold); + if (msg->has_high_threshold) { + char str_high_threshold[10 + 1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->high_threshold, str_high_threshold, + sizeof(str_high_threshold)); + strlcpy(rows[row_idx], _("High: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_high_threshold, sizeof(rows[row_idx])); + row_idx++; + + // Hash high threshold + stellar_hashupdate_uint32(msg->high_threshold); + } + + if (show_thresholds_confirm) { + strlcpy(str_title, _("Account Thresholds"), sizeof(str_title)); + stellar_layoutTransactionDialog(str_title, rows[0], rows[1], rows[2], + rows[3]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + memzero(rows, sizeof(rows)); + row_idx = 0; + } + + // Home domain + stellar_hashupdate_bool(msg->has_home_domain); + if (msg->has_home_domain) { + strlcpy(str_title, _("Home Domain"), sizeof(str_title)); + + // Split home domain if longer than 22 characters + int home_domain_len = strnlen(msg->home_domain, 32); + if (home_domain_len > 22) { + strlcpy(rows[0], msg->home_domain, 22); + strlcpy(rows[1], msg->home_domain + 21, sizeof(rows[1])); + } else { + strlcpy(rows[0], msg->home_domain, sizeof(rows[0])); + } + + stellar_layoutTransactionDialog(str_title, rows[0], rows[1], NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + memzero(rows, sizeof(rows)); + row_idx = 0; + + stellar_hashupdate_string((unsigned char *)&(msg->home_domain), + strnlen(msg->home_domain, 32)); + } + + // Signer + stellar_hashupdate_bool(msg->has_signer_type); + if (msg->has_signer_type) { + if (msg->signer_weight > 0) { + strlcpy(str_title, _("Add Signer: "), sizeof(str_title)); + } else { + strlcpy(str_title, _("REMOVE Signer: "), sizeof(str_title)); + } + + // Format weight as a string + char str_weight[16]; + stellar_format_uint32(msg->signer_weight, str_weight, sizeof(str_weight)); + char str_weight_row[32]; + strlcpy(str_weight_row, _("Weight: "), sizeof(str_weight_row)); + strlcat(str_weight_row, str_weight, sizeof(str_weight_row)); + + // 0 = account, 1 = pre-auth, 2 = hash(x) + char str_signer_type[16]; + bool needs_hash_confirm = false; + if (msg->signer_type == 0) { + strlcpy(str_signer_type, _("account"), sizeof(str_signer_type)); + strlcat(str_title, str_signer_type, sizeof(str_title)); + + const char **str_addr_rows = + stellar_lineBreakAddress(msg->signer_key.bytes); + stellar_layoutTransactionDialog(str_title, str_weight_row, + str_addr_rows[0], str_addr_rows[1], + str_addr_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + } + if (msg->signer_type == 1) { + needs_hash_confirm = true; + strlcpy(str_signer_type, _("pre-auth hash"), sizeof(str_signer_type)); + strlcat(str_title, str_signer_type, sizeof(str_title)); + + stellar_layoutTransactionDialog(str_title, str_weight_row, NULL, + _("(confirm hash on next"), _("screen)")); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + } + if (msg->signer_type == 2) { + needs_hash_confirm = true; + strlcpy(str_signer_type, _("hash(x)"), sizeof(str_signer_type)); + strlcat(str_title, str_signer_type, sizeof(str_title)); + + stellar_layoutTransactionDialog(str_title, str_weight_row, NULL, + _("(confirm hash on next"), _("screen)")); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + } + + // Extra confirmation step for hash signers + if (needs_hash_confirm) { + data2hex(msg->signer_key.bytes + 0, 8, rows[row_idx++]); + data2hex(msg->signer_key.bytes + 8, 8, rows[row_idx++]); + data2hex(msg->signer_key.bytes + 16, 8, rows[row_idx++]); + data2hex(msg->signer_key.bytes + 24, 8, rows[row_idx++]); + + stellar_layoutTransactionDialog(_("Confirm Hash"), rows[0], rows[1], + rows[2], rows[3]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + memzero(rows, sizeof(rows)); + row_idx = 0; + } + + // Hash: signer type + stellar_hashupdate_uint32(msg->signer_type); + // key + stellar_hashupdate_bytes(msg->signer_key.bytes, msg->signer_key.size); + // weight + stellar_hashupdate_uint32(msg->signer_weight); + } + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmChangeTrustOp(const StellarChangeTrustOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(6); + + // Add Trust: USD + char str_title[32]; + if (msg->limit == 0) { + strlcpy(str_title, _("DELETE Trust: "), sizeof(str_title)); + } else { + strlcpy(str_title, _("Add Trust: "), sizeof(str_title)); + } + strlcat(str_title, msg->asset.code, sizeof(str_title)); + + // Amount: MAX (or a number) + char str_amount_row[32]; + strlcpy(str_amount_row, _("Amount: "), sizeof(str_amount_row)); + + if (msg->limit == 9223372036854775807) { + strlcat(str_amount_row, _("[Maximum]"), sizeof(str_amount_row)); + } else { + char str_amount[32]; + stellar_format_stroops(msg->limit, str_amount, sizeof(str_amount)); + strlcat(str_amount_row, str_amount, sizeof(str_amount_row)); + } + + // Validate destination account and convert to bytes + uint8_t asset_issuer_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->asset.issuer, asset_issuer_bytes)) { + stellar_signingAbort(_("User canceled")); + fsm_sendFailure(FailureType_Failure_ProcessError, + _("Invalid asset issuer")); + return false; + } + + // Display full issuer address + const char **str_addr_rows = stellar_lineBreakAddress(asset_issuer_bytes); + + stellar_layoutTransactionDialog(str_title, str_amount_row, str_addr_rows[0], + str_addr_rows[1], str_addr_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash: asset + stellar_hashupdate_asset(&(msg->asset)); + // limit + stellar_hashupdate_uint64(msg->limit); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmAllowTrustOp(const StellarAllowTrustOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(7); + + // Add Trust: USD + char str_title[32]; + if (msg->is_authorized) { + strlcpy(str_title, _("Allow Trust of"), sizeof(str_title)); + } else { + strlcpy(str_title, _("REVOKE Trust of"), sizeof(str_title)); + } + + // Asset code + char str_asset_row[32]; + strlcpy(str_asset_row, msg->asset_code, sizeof(str_asset_row)); + + // Validate account and convert to bytes + uint8_t trusted_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->trusted_account, trusted_account_bytes)) { + stellar_signingAbort(_("Invalid trusted account")); + return false; + } + + const char **str_trustor_rows = + stellar_lineBreakAddress(trusted_account_bytes); + + // By: G... + char str_by[32]; + strlcpy(str_by, _("By: "), sizeof(str_by)); + strlcat(str_by, str_trustor_rows[0], sizeof(str_by)); + + stellar_layoutTransactionDialog(str_title, str_asset_row, str_by, + str_trustor_rows[1], str_trustor_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash: trustor account (the account being allowed to access the asset) + stellar_hashupdate_address(trusted_account_bytes); + // asset type + stellar_hashupdate_uint32(msg->asset_type); + // asset code + if (msg->asset_type == 1) { + char code4[4 + 1]; + memzero(code4, sizeof(code4)); + strlcpy(code4, msg->asset_code, sizeof(code4)); + stellar_hashupdate_bytes((uint8_t *)code4, 4); + } + if (msg->asset_type == 2) { + char code12[12 + 1]; + memzero(code12, sizeof(code12)); + strlcpy(code12, msg->asset_code, sizeof(code12)); + stellar_hashupdate_bytes((uint8_t *)code12, 12); + } + // is authorized + stellar_hashupdate_bool(msg->is_authorized); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmAccountMergeOp(const StellarAccountMergeOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(8); + + // Validate account and convert to bytes + uint8_t destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->destination_account, + destination_account_bytes)) { + stellar_signingAbort(_("Invalid destination account")); + return false; + } + + const char **str_destination_rows = + stellar_lineBreakAddress(destination_account_bytes); + + stellar_layoutTransactionDialog( + _("Merge Account"), _("All XLM will be sent to:"), + str_destination_rows[0], str_destination_rows[1], + str_destination_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash: destination account + stellar_hashupdate_address(destination_account_bytes); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmManageDataOp(const StellarManageDataOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(10); + + char str_title[32]; + if (msg->has_value) { + strlcpy(str_title, _("Set data value key:"), sizeof(str_title)); + } else { + strlcpy(str_title, _("CLEAR data value key:"), sizeof(str_title)); + } + + // Confirm key + const char **str_key_lines = + split_message((const uint8_t *)(msg->key), strnlen(msg->key, 64), 16); + + stellar_layoutTransactionDialog(str_title, str_key_lines[0], str_key_lines[1], + str_key_lines[2], str_key_lines[3]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Confirm value by displaying sha256 hash since this can contain + // non-printable characters + if (msg->has_value) { + strlcpy(str_title, _("Confirm sha256 of value:"), sizeof(str_title)); + + char str_hash_digest[SHA256_DIGEST_STRING_LENGTH]; + sha256_Data(msg->value.bytes, msg->value.size, str_hash_digest); + const char **str_hash_lines = split_message( + (const uint8_t *)str_hash_digest, sizeof(str_hash_digest), 16); + + stellar_layoutTransactionDialog(str_title, str_hash_lines[0], + str_hash_lines[1], str_hash_lines[2], + str_hash_lines[3]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + } + + // Hash: key + stellar_hashupdate_string((unsigned char *)&(msg->key), + strnlen(msg->key, 64)); + // value + if (msg->has_value) { + stellar_hashupdate_bool(true); + // Variable opaque field is length + raw bytes + stellar_hashupdate_uint32(msg->value.size); + stellar_hashupdate_bytes(msg->value.bytes, msg->value.size); + } else { + stellar_hashupdate_bool(false); + } + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +bool stellar_confirmBumpSequenceOp(const StellarBumpSequenceOp *msg) { + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, + msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + + // Hash: operation type + stellar_hashupdate_uint32(11); + + char str_bump_to[20]; + stellar_format_uint64(msg->bump_to, str_bump_to, sizeof(str_bump_to)); + + stellar_layoutTransactionDialog(_("Bump Sequence"), _("Set sequence to:"), + str_bump_to, NULL, NULL); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return false; + } + + // Hash: bump to + stellar_hashupdate_uint64(msg->bump_to); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; + return true; +} + +void stellar_signingAbort(const char *reason) { + if (!reason) { + reason = _("Unknown error"); + } + + stellar_signing = false; + fsm_sendFailure(FailureType_Failure_ProcessError, reason); + layoutHome(); +} + +/** + * Populates the fields of resp with the signature of the active transaction + */ +void stellar_fillSignedTx(StellarSignedTx *resp) { + // Finalize the transaction by hashing 4 null bytes representing a (currently + // unused) empty union + stellar_hashupdate_uint32(0); + + // Add the public key for verification that the right account was used for + // signing + memcpy(resp->public_key.bytes, stellar_activeTx.signing_pubkey, 32); + resp->public_key.size = 32; + resp->has_public_key = true; + + // Add the signature (note that this does not include the 4-byte hint since it + // can be calculated from the public key) + uint8_t signature[64]; + // Note: this calls sha256_Final on the hash context + stellar_getSignatureForActiveTx(signature); + memcpy(resp->signature.bytes, signature, sizeof(signature)); + resp->signature.size = sizeof(signature); + resp->has_signature = true; +} + +bool stellar_allOperationsConfirmed() { + return stellar_activeTx.confirmed_operations == + stellar_activeTx.num_operations; +} + +/* + * Calculates and sets the signature for the active transaction + */ +void stellar_getSignatureForActiveTx(uint8_t *out_signature) { + const HDNode *node = stellar_deriveNode(stellar_activeTx.address_n, + stellar_activeTx.address_n_count); + if (!node) { + // return empty signature when we can't derive node + memzero(out_signature, 64); + return; + } + + // Signature is the ed25519 detached signature of the sha256 of all the bytes + // that have been read so far + uint8_t to_sign[32]; + sha256_Final(&(stellar_activeTx.sha256_ctx), to_sign); + + uint8_t signature[64]; + ed25519_sign(to_sign, sizeof(to_sign), node->private_key, + node->public_key + 1, signature); + + memcpy(out_signature, signature, sizeof(signature)); +} + +/* + * Returns number (representing stroops) formatted as XLM + * For example, if number has value 1000000000 then it will be returned as + * "100.0" + */ +void stellar_format_stroops(uint64_t number, char *out, size_t outlen) { + bn_format_uint64(number, NULL, NULL, 7, 0, false, out, outlen); +} + +/* + * Formats a price represented as a uint32 numerator and uint32 denominator + * + * Note that there may be a loss of precision between the real price value and + * what is shown to the user + * + * Smallest possible price is 1 / 4294967296 which is: + * 0.00000000023283064365386962890625 + * + * largest possible price is: + * 4294967296 + */ +void stellar_format_price(uint32_t numerator, uint32_t denominator, char *out, + size_t outlen) { + memzero(out, outlen); + + // early exit for invalid denominator + if (denominator == 0) { + strlcpy(out, _("[Invalid Price]"), outlen); + return; + } + + // early exit for zero + if (numerator == 0) { + strlcpy(out, "0", outlen); + return; + } + + int scale = 0; + uint64_t value = numerator; + while (value < (UINT64_MAX / 10)) { + value *= 10; + scale++; + } + value /= denominator; + while (value < (UINT64_MAX / 10)) { + value *= 10; + scale++; + } + + // Format with bn_format_uint64 + bn_format_uint64(value, NULL, NULL, scale, 0, false, out, outlen); +} + +/* + * Returns a uint32 formatted as a string + */ +void stellar_format_uint32(uint32_t number, char *out, size_t outlen) { + bignum256 bn_number; + bn_read_uint32(number, &bn_number); + bn_format(&bn_number, NULL, NULL, 0, 0, false, out, outlen); +} + +/* + * Returns a uint64 formatted as a string + */ +void stellar_format_uint64(uint64_t number, char *out, size_t outlen) { + bn_format_uint64(number, NULL, NULL, 0, 0, false, out, outlen); +} + +/* + * Breaks a 56 character address into 3 lines of lengths 16, 20, 20 + * This is to allow a small label to be prepended to the first line + */ +const char **stellar_lineBreakAddress(const uint8_t *addrbytes) { + char str_fulladdr[56 + 1]; + static char rows[3][20 + 1]; + + memzero(rows, sizeof(rows)); + + // get full address string + stellar_publicAddressAsStr(addrbytes, str_fulladdr, sizeof(str_fulladdr)); + + // Break it into 3 lines + strlcpy(rows[0], str_fulladdr + 0, 17); + strlcpy(rows[1], str_fulladdr + 16, 21); + strlcpy(rows[2], str_fulladdr + 16 + 20, 21); + + static const char *ret[3] = {rows[0], rows[1], rows[2]}; + return ret; +} + +/* + * Returns the asset formatted to fit in a single row + * + * Examples: + * XLM (Native Asset) + * MOBI (G123456789000) + * ALPHA12EXAMP (G0987) + */ +void stellar_format_asset(const StellarAssetType *asset, char *str_formatted, + size_t len) { + char str_asset_code[12 + 1]; + // truncated asset issuer, final length depends on length of asset code + char str_asset_issuer_trunc[13 + 1]; + + memzero(str_formatted, len); + memzero(str_asset_code, sizeof(str_asset_code)); + memzero(str_asset_issuer_trunc, sizeof(str_asset_issuer_trunc)); + + // Validate issuer account for non-native assets + if (asset->type != 0 && !stellar_validateAddress(asset->issuer)) { + stellar_signingAbort(_("Invalid asset issuer")); + return; + } + + // Native asset + if (asset->type == 0) { + strlcpy(str_formatted, _("XLM (native asset)"), len); + } + // 4-character custom + if (asset->type == 1) { + memcpy(str_asset_code, asset->code, 4); + strlcpy(str_formatted, str_asset_code, len); + + // Truncate issuer to 13 chars + memcpy(str_asset_issuer_trunc, asset->issuer, 13); + } + // 12-character custom + if (asset->type == 2) { + memcpy(str_asset_code, asset->code, 12); + strlcpy(str_formatted, str_asset_code, len); + + // Truncate issuer to 5 characters + memcpy(str_asset_issuer_trunc, asset->issuer, 5); + } + // Issuer is read the same way for both types of custom assets + if (asset->type == 1 || asset->type == 2) { + strlcat(str_formatted, _(" ("), len); + strlcat(str_formatted, str_asset_issuer_trunc, len); + strlcat(str_formatted, _(")"), len); + } +} + +size_t stellar_publicAddressAsStr(const uint8_t *bytes, char *out, + size_t outlen) { + // version + key bytes + checksum + uint8_t keylen = 1 + 32 + 2; + uint8_t bytes_full[keylen]; + bytes_full[0] = 6 << 3; // 'G' + + memcpy(bytes_full + 1, bytes, 32); + + // Last two bytes are the checksum + uint16_t checksum = stellar_crc16(bytes_full, 33); + bytes_full[keylen - 2] = checksum & 0x00ff; + bytes_full[keylen - 1] = (checksum >> 8) & 0x00ff; + + base32_encode(bytes_full, keylen, out, outlen, BASE32_ALPHABET_RFC4648); + + // Public key will always be 56 characters + return 56; +} + +/** + * Stellar account string is a base32-encoded string that starts with "G" + * + * It decodes to the following format: + * Byte 0 - always 0x30 ("G" when base32 encoded), version byte indicating a + * public key Bytes 1-33 - 32-byte public key bytes Bytes 34-35 - 2-byte CRC16 + * checksum of the version byte + public key bytes (first 33 bytes) + * + * Note that the stellar "seed" (private key) also uses this format except the + * version byte is 0xC0 which encodes to "S" in base32 + */ +bool stellar_validateAddress(const char *str_address) { + bool valid = false; + uint8_t decoded[STELLAR_ADDRESS_SIZE_RAW]; + + if (strlen(str_address) != STELLAR_ADDRESS_SIZE) { + return false; + } + + // Check that it decodes correctly + uint8_t *ret = base32_decode(str_address, STELLAR_ADDRESS_SIZE, decoded, + sizeof(decoded), BASE32_ALPHABET_RFC4648); + valid = (ret != NULL); + + // ... and that version byte is 0x30 + if (valid && decoded[0] != 0x30) { + valid = false; + } + + // ... and that checksums match + uint16_t checksum_expected = stellar_crc16(decoded, 33); + uint16_t checksum_actual = + (decoded[34] << 8) | decoded[33]; // unsigned short (little endian) + if (valid && checksum_expected != checksum_actual) { + valid = false; + } + + memzero(decoded, sizeof(decoded)); + return valid; +} + +/** + * Converts a string address (G...) to the 32-byte raw address + */ +bool stellar_getAddressBytes(const char *str_address, uint8_t *out_bytes) { + uint8_t decoded[STELLAR_ADDRESS_SIZE_RAW]; + + // Ensure address is valid + if (!stellar_validateAddress(str_address)) return false; + + base32_decode(str_address, STELLAR_ADDRESS_SIZE, decoded, sizeof(decoded), + BASE32_ALPHABET_RFC4648); + + // The 32 bytes with offset 1-33 represent the public key + memcpy(out_bytes, &decoded[1], 32); + + memzero(decoded, sizeof(decoded)); + return true; +} + +/* + * CRC16 implementation compatible with the Stellar version + * Ported from this implementation: + * http://introcs.cs.princeton.edu/java/61data/CRC16CCITT.java.html Initial + * value changed to 0x0000 to match Stellar + */ +uint16_t stellar_crc16(uint8_t *bytes, uint32_t length) { + // Calculate checksum for existing bytes + uint16_t crc = 0x0000; + uint16_t polynomial = 0x1021; + uint32_t i; + uint8_t bit; + uint8_t byte; + uint8_t bitidx; + uint8_t c15; + + for (i = 0; i < length; i++) { + byte = bytes[i]; + for (bitidx = 0; bitidx < 8; bitidx++) { + bit = ((byte >> (7 - bitidx) & 1) == 1); + c15 = ((crc >> 15 & 1) == 1); + crc <<= 1; + if (c15 ^ bit) crc ^= polynomial; + } + } + + return crc & 0xffff; +} + +/* + * Derives the HDNode at the given index + * Standard Stellar prefix is m/44'/148'/ and the default account is + * m/44'/148'/0' + * + * All paths must be hardened + */ +const HDNode *stellar_deriveNode(const uint32_t *address_n, + size_t address_n_count) { + static CONFIDENTIAL HDNode node; + const char *curve = "ed25519"; + + // Device not initialized, passphrase request cancelled, or unsupported curve + if (!config_getRootNode(&node, curve, true)) { + return 0; + } + // Failed to derive private key + if (hdnode_private_ckd_cached(&node, address_n, address_n_count, NULL) == 0) { + return 0; + } + + hdnode_fill_public_key(&node); + + return &node; +} + +void stellar_hashupdate_uint32(uint32_t value) { + // Ensure uint32 is big endian +#if BYTE_ORDER == LITTLE_ENDIAN + REVERSE32(value, value); +#endif + + // Byte values must be hashed as big endian + uint8_t data[4]; + data[3] = (value >> 24) & 0xFF; + data[2] = (value >> 16) & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[0] = value & 0xFF; + + stellar_hashupdate_bytes(data, sizeof(data)); +} + +void stellar_hashupdate_uint64(uint64_t value) { + // Ensure uint64 is big endian +#if BYTE_ORDER == LITTLE_ENDIAN + REVERSE64(value, value); +#endif + + // Byte values must be hashed as big endian + uint8_t data[8]; + data[7] = (value >> 56) & 0xFF; + data[6] = (value >> 48) & 0xFF; + data[5] = (value >> 40) & 0xFF; + data[4] = (value >> 32) & 0xFF; + data[3] = (value >> 24) & 0xFF; + data[2] = (value >> 16) & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[0] = value & 0xFF; + + stellar_hashupdate_bytes(data, sizeof(data)); +} + +void stellar_hashupdate_bool(bool value) { + if (value) { + stellar_hashupdate_uint32(1); + } else { + stellar_hashupdate_uint32(0); + } +} + +void stellar_hashupdate_string(const uint8_t *data, size_t len) { + // Hash the length of the string + stellar_hashupdate_uint32((uint32_t)len); + + // Hash the raw bytes of the string + stellar_hashupdate_bytes(data, len); + + // If len isn't a multiple of 4, add padding bytes + int remainder = len % 4; + uint8_t null_byte[1] = {0x00}; + if (remainder) { + while (remainder < 4) { + stellar_hashupdate_bytes(null_byte, 1); + remainder++; + } + } +} + +void stellar_hashupdate_address(const uint8_t *address_bytes) { + // First 4 bytes of an address are the type. There's only one type (0) + stellar_hashupdate_uint32(0); + + // Remaining part of the address is 32 bytes + stellar_hashupdate_bytes(address_bytes, 32); +} + +/* + * Note about string handling below: this field is an XDR "opaque" field and not + * a typical string, so if "TEST" is the asset code then the hashed value needs + * to be 4 bytes and not include the null at the end of the string + */ +void stellar_hashupdate_asset(const StellarAssetType *asset) { + stellar_hashupdate_uint32(asset->type); + + // For non-native assets, validate issuer account and convert to bytes + uint8_t issuer_bytes[STELLAR_KEY_SIZE]; + if (asset->type != 0 && + !stellar_getAddressBytes(asset->issuer, issuer_bytes)) { + stellar_signingAbort(_("Invalid asset issuer")); + return; + } + + // 4-character asset code + if (asset->type == 1) { + char code4[4 + 1]; + memzero(code4, sizeof(code4)); + strlcpy(code4, asset->code, sizeof(code4)); + + stellar_hashupdate_bytes((uint8_t *)code4, 4); + stellar_hashupdate_address(issuer_bytes); + } + + // 12-character asset code + if (asset->type == 2) { + char code12[12 + 1]; + memzero(code12, sizeof(code12)); + strlcpy(code12, asset->code, sizeof(code12)); + + stellar_hashupdate_bytes((uint8_t *)code12, 12); + stellar_hashupdate_address(issuer_bytes); + } +} + +void stellar_hashupdate_bytes(const uint8_t *data, size_t len) { + sha256_Update(&(stellar_activeTx.sha256_ctx), data, len); +} + +/* + * Displays a summary of the overall transaction + */ +void stellar_layoutTransactionSummary(const StellarSignTx *msg) { + char str_lines[5][32]; + memzero(str_lines, sizeof(str_lines)); + + char str_fee[12]; + char str_num_ops[12]; + + // Will be set to true for some large hashes that don't fit on one screen + uint8_t needs_memo_hash_confirm = 0; + + // Format the fee + stellar_format_stroops(msg->fee, str_fee, sizeof(str_fee)); + + strlcpy(str_lines[0], _("Fee: "), sizeof(str_lines[0])); + strlcat(str_lines[0], str_fee, sizeof(str_lines[0])); + strlcat(str_lines[0], _(" XLM"), sizeof(str_lines[0])); + + // add in numOperations + stellar_format_uint32(msg->num_operations, str_num_ops, sizeof(str_num_ops)); + + strlcat(str_lines[0], _(" ("), sizeof(str_lines[0])); + strlcat(str_lines[0], str_num_ops, sizeof(str_lines[0])); + if (msg->num_operations == 1) { + strlcat(str_lines[0], _(" op)"), sizeof(str_lines[0])); + } else { + strlcat(str_lines[0], _(" ops)"), sizeof(str_lines[0])); + } + + // Display full address being used to sign transaction + const char **str_addr_rows = + stellar_lineBreakAddress(stellar_activeTx.signing_pubkey); + + stellar_layoutTransactionDialog(str_lines[0], _("Signing with:"), + str_addr_rows[0], str_addr_rows[1], + str_addr_rows[2]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return; + } + + // Reset lines for displaying memo + memzero(str_lines, sizeof(str_lines)); + + // Memo: none + if (msg->memo_type == 0) { + strlcpy(str_lines[0], _("[No Memo Set]"), sizeof(str_lines[0])); + strlcpy(str_lines[1], _("Important:"), sizeof(str_lines[0])); + strlcpy(str_lines[2], _("Many exchanges require"), sizeof(str_lines[0])); + strlcpy(str_lines[3], _("a memo when depositing."), sizeof(str_lines[0])); + } + // Memo: text + if (msg->memo_type == 1) { + strlcpy(str_lines[0], _("Memo (TEXT)"), sizeof(str_lines[0])); + + // Split 28-character string into two lines of 19 / 9 + // todo: word wrap method? + strlcpy(str_lines[1], (const char *)msg->memo_text, 19 + 1); + strlcpy(str_lines[2], (const char *)(msg->memo_text + 19), 9 + 1); + } + // Memo: ID + if (msg->memo_type == 2) { + strlcpy(str_lines[0], _("Memo (ID)"), sizeof(str_lines[0])); + stellar_format_uint64(msg->memo_id, str_lines[1], sizeof(str_lines[1])); + } + // Memo: hash + if (msg->memo_type == 3) { + needs_memo_hash_confirm = 1; + strlcpy(str_lines[0], _("Memo (HASH)"), sizeof(str_lines[0])); + } + // Memo: return + if (msg->memo_type == 4) { + needs_memo_hash_confirm = 1; + strlcpy(str_lines[0], _("Memo (RETURN)"), sizeof(str_lines[0])); + } + + if (needs_memo_hash_confirm) { + data2hex(msg->memo_hash.bytes + 0, 8, str_lines[1]); + data2hex(msg->memo_hash.bytes + 8, 8, str_lines[2]); + data2hex(msg->memo_hash.bytes + 16, 8, str_lines[3]); + data2hex(msg->memo_hash.bytes + 24, 8, str_lines[4]); + } + + stellar_layoutTransactionDialog(str_lines[0], str_lines[1], str_lines[2], + str_lines[3], str_lines[4]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return; + } + + // Verify timebounds, if present + memzero(str_lines, sizeof(str_lines)); + + // Timebound: lower + if (msg->timebounds_start || msg->timebounds_end) { + time_t timebound; + char str_timebound[32]; + const struct tm *tm; + + timebound = (time_t)msg->timebounds_start; + strlcpy(str_lines[0], _("Valid from:"), sizeof(str_lines[0])); + if (timebound) { + tm = gmtime(&timebound); + strftime(str_timebound, sizeof(str_timebound), "%F %T (UTC)", tm); + strlcpy(str_lines[1], str_timebound, sizeof(str_lines[1])); + } else { + strlcpy(str_lines[1], _("[no restriction]"), sizeof(str_lines[1])); + } + + // Reset for timebound_max + memzero(str_timebound, sizeof(str_timebound)); + + timebound = (time_t)msg->timebounds_end; + strlcpy(str_lines[0], _("Valid from:"), sizeof(str_lines[0])); + if (timebound) { + tm = gmtime(&timebound); + strftime(str_timebound, sizeof(str_timebound), "%F %T (UTC)", tm); + strlcpy(str_lines[1], str_timebound, sizeof(str_lines[1])); + } else { + strlcpy(str_lines[1], _("[no restriction]"), sizeof(str_lines[1])); + } + } + + if (msg->timebounds_start || msg->timebounds_end) { + stellar_layoutTransactionDialog(_("Confirm Time Bounds"), str_lines[0], + str_lines[1], str_lines[2], str_lines[3]); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(_("User canceled")); + return; + } + } +} + +/* + * Most basic dialog used for signing + * - Header indicating which key is being used for signing + * - 5 rows for content + * - Cancel / Next buttons + * - Warning message can appear between cancel/next buttons + */ +void stellar_layoutSigningDialog(const char *line1, const char *line2, + const char *line3, const char *line4, + const char *line5, uint32_t *address_n, + size_t address_n_count, const char *warning, + bool is_final_step) { + // Start with some initial padding and use these to track position as + // rendering moves down the screen + int offset_x = 1; + int offset_y = 1; + int line_height = 9; + + const HDNode *node = stellar_deriveNode(address_n, address_n_count); + if (!node) { + // abort on error + return; + } + + char str_pubaddr_truncated[12]; // G???? + null + memzero(str_pubaddr_truncated, sizeof(str_pubaddr_truncated)); + + layoutLast = layoutDialogSwipe; + layoutSwipe(); + oledClear(); + + // Load up public address + char str_pubaddr[56 + 1]; + memzero(str_pubaddr, sizeof(str_pubaddr)); + stellar_publicAddressAsStr(node->public_key + 1, str_pubaddr, + sizeof(str_pubaddr)); + memcpy(str_pubaddr_truncated, str_pubaddr, sizeof(str_pubaddr_truncated) - 1); + + // Header + // Ends up as: Signing with GABCDEFGHIJKL + char str_header[32]; + memzero(str_header, sizeof(str_header)); + strlcpy(str_header, _("Signing with "), sizeof(str_header)); + strlcat(str_header, str_pubaddr_truncated, sizeof(str_header)); + + oledDrawString(offset_x, offset_y, str_header, FONT_STANDARD); + offset_y += line_height; + // Invert color on header + oledInvert(0, 0, OLED_WIDTH, offset_y - 2); + + // Dialog contents begin + if (line1) { + oledDrawString(offset_x, offset_y, line1, FONT_STANDARD); + } + offset_y += line_height; + if (line2) { + oledDrawString(offset_x, offset_y, line2, FONT_STANDARD); + } + offset_y += line_height; + if (line3) { + oledDrawString(offset_x, offset_y, line3, FONT_STANDARD); + } + offset_y += line_height; + if (line4) { + oledDrawString(offset_x, offset_y, line4, FONT_STANDARD); + } + offset_y += line_height; + if (line5) { + oledDrawString(offset_x, offset_y, line5, FONT_STANDARD); + } + offset_y += line_height; + + // Cancel button + oledDrawString(1, OLED_HEIGHT - 8, "\x15", FONT_STANDARD); + oledDrawString(fontCharWidth(FONT_STANDARD, '\x15') + 3, OLED_HEIGHT - 8, + "Cancel", FONT_STANDARD); + oledInvert(0, OLED_HEIGHT - 9, + fontCharWidth(FONT_STANDARD, '\x15') + + oledStringWidth("Cancel", FONT_STANDARD) + 2, + OLED_HEIGHT - 1); + + // Warnings (drawn centered between the buttons + if (warning) { + oledDrawStringCenter(OLED_WIDTH / 2, OLED_HEIGHT - 8, warning, + FONT_STANDARD); + } + + // Next / sign button + char str_next_label[8]; + if (is_final_step) { + strlcpy(str_next_label, _("SIGN"), sizeof(str_next_label)); + } else { + strlcpy(str_next_label, _("Next"), sizeof(str_next_label)); + } + + oledDrawString(OLED_WIDTH - fontCharWidth(FONT_STANDARD, '\x06') - 1, + OLED_HEIGHT - 8, "\x06", FONT_STANDARD); + oledDrawString(OLED_WIDTH - oledStringWidth(str_next_label, FONT_STANDARD) - + fontCharWidth(FONT_STANDARD, '\x06') - 3, + OLED_HEIGHT - 8, str_next_label, FONT_STANDARD); + oledInvert(OLED_WIDTH - oledStringWidth(str_next_label, FONT_STANDARD) - + fontCharWidth(FONT_STANDARD, '\x06') - 4, + OLED_HEIGHT - 9, OLED_WIDTH - 1, OLED_HEIGHT - 1); + + oledRefresh(); +} + +/* + * Main dialog helper method. Allows displaying 5 lines. + * A title showing the account being used to sign is always displayed. + */ +void stellar_layoutTransactionDialog(const char *line1, const char *line2, + const char *line3, const char *line4, + const char *line5) { + char str_warning[16]; + memzero(str_warning, sizeof(str_warning)); + + if (stellar_activeTx.network_type == 2) { + // Warning: testnet + strlcpy(str_warning, _("WRN:TN"), sizeof(str_warning)); + } + if (stellar_activeTx.network_type == 3) { + // Warning: private network + strlcpy(str_warning, _("WRN:PN"), sizeof(str_warning)); + } + + stellar_layoutSigningDialog( + line1, line2, line3, line4, line5, stellar_activeTx.address_n, + stellar_activeTx.address_n_count, str_warning, false); +} diff --git a/legacy/firmware/stellar.h b/legacy/firmware/stellar.h new file mode 100644 index 0000000000..92eda1d7b2 --- /dev/null +++ b/legacy/firmware/stellar.h @@ -0,0 +1,116 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 ZuluCrypto + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __STELLAR_H__ +#define __STELLAR_H__ + +#include +#include "base32.h" +#include "bip32.h" +#include "crypto.h" +#include "fsm.h" +#include "messages.pb.h" + +// 56 character base-32 encoded string +#define STELLAR_ADDRESS_SIZE 56 +// Decodes to 35 bytes +#define STELLAR_ADDRESS_SIZE_RAW 35 +// Raw key size is 32 bytes +#define STELLAR_KEY_SIZE 32 + +typedef struct { + // BIP32 path to the address being used for signing + uint32_t address_n[10]; + size_t address_n_count; + uint8_t signing_pubkey[32]; + + // 1 - public network, 2 - official testnet, 3 - other private network + uint8_t network_type; + + // Total number of operations expected + uint32_t num_operations; + // Number that have been confirmed by the user + uint32_t confirmed_operations; + + // sha256 context that will eventually be signed + SHA256_CTX sha256_ctx; +} StellarTransaction; + +// Signing process +bool stellar_signingInit(const StellarSignTx *tx); +void stellar_signingAbort(const char *reason); +bool stellar_confirmSourceAccount(bool has_source_account, + const char *str_account); +bool stellar_confirmCreateAccountOp(const StellarCreateAccountOp *msg); +bool stellar_confirmPaymentOp(const StellarPaymentOp *msg); +bool stellar_confirmPathPaymentOp(const StellarPathPaymentOp *msg); +bool stellar_confirmManageOfferOp(const StellarManageOfferOp *msg); +bool stellar_confirmCreatePassiveOfferOp( + const StellarCreatePassiveOfferOp *msg); +bool stellar_confirmSetOptionsOp(const StellarSetOptionsOp *msg); +bool stellar_confirmChangeTrustOp(const StellarChangeTrustOp *msg); +bool stellar_confirmAllowTrustOp(const StellarAllowTrustOp *msg); +bool stellar_confirmAccountMergeOp(const StellarAccountMergeOp *msg); +bool stellar_confirmManageDataOp(const StellarManageDataOp *msg); +bool stellar_confirmBumpSequenceOp(const StellarBumpSequenceOp *msg); + +// Layout +void stellar_layoutTransactionDialog(const char *line1, const char *line2, + const char *line3, const char *line4, + const char *line5); +void stellar_layoutTransactionSummary(const StellarSignTx *msg); +void stellar_layoutSigningDialog(const char *line1, const char *line2, + const char *line3, const char *line4, + const char *line5, uint32_t *address_n, + size_t address_n_count, const char *warning, + bool is_final_step); + +// Helpers +const HDNode *stellar_deriveNode(const uint32_t *address_n, + size_t address_n_count); + +size_t stellar_publicAddressAsStr(const uint8_t *bytes, char *out, + size_t outlen); +const char **stellar_lineBreakAddress(const uint8_t *addrbytes); + +void stellar_hashupdate_uint32(uint32_t value); +void stellar_hashupdate_uint64(uint64_t value); +void stellar_hashupdate_bool(bool value); +void stellar_hashupdate_string(const uint8_t *data, size_t len); +void stellar_hashupdate_address(const uint8_t *address_bytes); +void stellar_hashupdate_asset(const StellarAssetType *asset); +void stellar_hashupdate_bytes(const uint8_t *data, size_t len); + +void stellar_fillSignedTx(StellarSignedTx *resp); +bool stellar_allOperationsConfirmed(void); +void stellar_getSignatureForActiveTx(uint8_t *out_signature); + +void stellar_format_uint32(uint32_t number, char *out, size_t outlen); +void stellar_format_uint64(uint64_t number, char *out, size_t outlen); +void stellar_format_stroops(uint64_t number, char *out, size_t outlen); +void stellar_format_asset(const StellarAssetType *asset, char *str_formatted, + size_t len); +void stellar_format_price(uint32_t numerator, uint32_t denominator, char *out, + size_t outlen); + +bool stellar_validateAddress(const char *str_address); +bool stellar_getAddressBytes(const char *str_address, uint8_t *out_bytes); +uint16_t stellar_crc16(uint8_t *bytes, uint32_t length); + +#endif diff --git a/legacy/firmware/transaction.c b/legacy/firmware/transaction.c new file mode 100644 index 0000000000..dd917291a1 --- /dev/null +++ b/legacy/firmware/transaction.c @@ -0,0 +1,900 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "transaction.h" +#include +#include "address.h" +#include "base58.h" +#include "cash_addr.h" +#include "coins.h" +#include "crypto.h" +#include "debug.h" +#include "ecdsa.h" +#include "layout2.h" +#include "memzero.h" +#include "messages.pb.h" +#include "protect.h" +#include "ripemd160.h" +#include "segwit_addr.h" +#include "util.h" + +#define SEGWIT_VERSION_0 0 + +#define CASHADDR_P2KH (0) +#define CASHADDR_P2SH (8) +#define CASHADDR_160 (0) + +/* transaction input size (without script): 32 prevhash, 4 idx, 4 sequence */ +#define TXSIZE_INPUT 40 +/* transaction output size (without script): 8 amount */ +#define TXSIZE_OUTPUT 8 +/* size of a pubkey */ +#define TXSIZE_PUBKEY 33 +/* size of a DER signature (3 type bytes, 3 len bytes, 33 R, 32 S, 1 sighash */ +#define TXSIZE_SIGNATURE 72 +/* size of a multiscript without pubkey (1 M, 1 N, 1 checksig) */ +#define TXSIZE_MULTISIGSCRIPT 3 +/* size of a p2wpkh script (1 version, 1 push, 20 hash) */ +#define TXSIZE_WITNESSPKHASH 22 +/* size of a p2wsh script (1 version, 1 push, 32 hash) */ +#define TXSIZE_WITNESSSCRIPT 34 +/* size of a p2pkh script (dup, hash, push, 20 pubkeyhash, equal, checksig) */ +#define TXSIZE_P2PKHASH 25 +/* size of a p2sh script (hash, push, 20 scripthash, equal) */ +#define TXSIZE_P2SCRIPT 23 +/* size of a Decred witness (without script): 8 amount, 4 block height, 4 block + * index */ +#define TXSIZE_DECRED_WITNESS 16 + +static const uint8_t segwit_header[2] = {0, 1}; + +static inline uint32_t op_push_size(uint32_t i) { + if (i < 0x4C) { + return 1; + } + if (i < 0x100) { + return 2; + } + if (i < 0x10000) { + return 3; + } + return 5; +} + +uint32_t op_push(uint32_t i, uint8_t *out) { + if (i < 0x4C) { + out[0] = i & 0xFF; + return 1; + } + if (i < 0x100) { + out[0] = 0x4C; + out[1] = i & 0xFF; + return 2; + } + if (i < 0x10000) { + out[0] = 0x4D; + out[1] = i & 0xFF; + out[2] = (i >> 8) & 0xFF; + return 3; + } + out[0] = 0x4E; + out[1] = i & 0xFF; + out[2] = (i >> 8) & 0xFF; + out[3] = (i >> 16) & 0xFF; + out[4] = (i >> 24) & 0xFF; + return 5; +} + +bool compute_address(const CoinInfo *coin, InputScriptType script_type, + const HDNode *node, bool has_multisig, + const MultisigRedeemScriptType *multisig, + char address[MAX_ADDR_SIZE]) { + uint8_t raw[MAX_ADDR_RAW_SIZE]; + uint8_t digest[32]; + size_t prelen; + + if (has_multisig) { + if (cryptoMultisigPubkeyIndex(coin, multisig, node->public_key) < 0) { + return 0; + } + if (compile_script_multisig_hash(coin, multisig, digest) == 0) { + return 0; + } + if (script_type == InputScriptType_SPENDWITNESS) { + // segwit p2wsh: script hash is single sha256 + if (!coin->has_segwit || !coin->bech32_prefix) { + return 0; + } + if (!segwit_addr_encode(address, coin->bech32_prefix, SEGWIT_VERSION_0, + digest, 32)) { + return 0; + } + } else if (script_type == InputScriptType_SPENDP2SHWITNESS) { + // segwit p2wsh encapsuled in p2sh address + if (!coin->has_segwit) { + return 0; + } + if (!coin->has_address_type_p2sh) { + return 0; + } + raw[0] = 0; // push version + raw[1] = 32; // push 32 bytes + memcpy(raw + 2, digest, 32); // push hash + hasher_Raw(coin->curve->hasher_pubkey, raw, 34, digest); + prelen = address_prefix_bytes_len(coin->address_type_p2sh); + address_write_prefix_bytes(coin->address_type_p2sh, raw); + memcpy(raw + prelen, digest, 32); + if (!base58_encode_check(raw, prelen + 20, coin->curve->hasher_base58, + address, MAX_ADDR_SIZE)) { + return 0; + } + } else if (coin->cashaddr_prefix) { + raw[0] = CASHADDR_P2SH | CASHADDR_160; + ripemd160(digest, 32, raw + 1); + if (!cash_addr_encode(address, coin->cashaddr_prefix, raw, 21)) { + return 0; + } + } else { + // non-segwit p2sh multisig + prelen = address_prefix_bytes_len(coin->address_type_p2sh); + address_write_prefix_bytes(coin->address_type_p2sh, raw); + ripemd160(digest, 32, raw + prelen); + if (!base58_encode_check(raw, prelen + 20, coin->curve->hasher_base58, + address, MAX_ADDR_SIZE)) { + return 0; + } + } + } else if (script_type == InputScriptType_SPENDWITNESS) { + // segwit p2wpkh: pubkey hash is ripemd160 of sha256 + if (!coin->has_segwit || !coin->bech32_prefix) { + return 0; + } + ecdsa_get_pubkeyhash(node->public_key, coin->curve->hasher_pubkey, digest); + if (!segwit_addr_encode(address, coin->bech32_prefix, SEGWIT_VERSION_0, + digest, 20)) { + return 0; + } + } else if (script_type == InputScriptType_SPENDP2SHWITNESS) { + // segwit p2wpkh embedded in p2sh + if (!coin->has_segwit) { + return 0; + } + if (!coin->has_address_type_p2sh) { + return 0; + } + ecdsa_get_address_segwit_p2sh( + node->public_key, coin->address_type_p2sh, coin->curve->hasher_pubkey, + coin->curve->hasher_base58, address, MAX_ADDR_SIZE); + } else if (coin->cashaddr_prefix) { + ecdsa_get_address_raw(node->public_key, CASHADDR_P2KH | CASHADDR_160, + coin->curve->hasher_pubkey, raw); + if (!cash_addr_encode(address, coin->cashaddr_prefix, raw, 21)) { + return 0; + } + } else { + ecdsa_get_address(node->public_key, coin->address_type, + coin->curve->hasher_pubkey, coin->curve->hasher_base58, + address, MAX_ADDR_SIZE); + } + return 1; +} + +int compile_output(const CoinInfo *coin, const HDNode *root, TxOutputType *in, + TxOutputBinType *out, bool needs_confirm) { + memzero(out, sizeof(TxOutputBinType)); + out->amount = in->amount; + out->decred_script_version = in->decred_script_version; + uint8_t addr_raw[MAX_ADDR_RAW_SIZE]; + size_t addr_raw_len; + + if (in->script_type == OutputScriptType_PAYTOOPRETURN) { + // only 0 satoshi allowed for OP_RETURN + if (in->amount != 0) { + return 0; // failed to compile output + } + if (needs_confirm) { + if (in->op_return_data.size >= 8 && + memcmp(in->op_return_data.bytes, "omni", 4) == + 0) { // OMNI transaction + layoutConfirmOmni(in->op_return_data.bytes, in->op_return_data.size); + } else { + layoutConfirmOpReturn(in->op_return_data.bytes, + in->op_return_data.size); + } + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, + false)) { + return -1; // user aborted + } + } + uint32_t r = 0; + out->script_pubkey.bytes[0] = 0x6A; + r++; // OP_RETURN + r += op_push(in->op_return_data.size, out->script_pubkey.bytes + r); + memcpy(out->script_pubkey.bytes + r, in->op_return_data.bytes, + in->op_return_data.size); + r += in->op_return_data.size; + out->script_pubkey.size = r; + return r; + } + + if (in->address_n_count > 0) { + static CONFIDENTIAL HDNode node; + InputScriptType input_script_type; + + switch (in->script_type) { + case OutputScriptType_PAYTOADDRESS: + input_script_type = InputScriptType_SPENDADDRESS; + break; + case OutputScriptType_PAYTOMULTISIG: + input_script_type = InputScriptType_SPENDMULTISIG; + break; + case OutputScriptType_PAYTOWITNESS: + input_script_type = InputScriptType_SPENDWITNESS; + break; + case OutputScriptType_PAYTOP2SHWITNESS: + input_script_type = InputScriptType_SPENDP2SHWITNESS; + break; + default: + return 0; // failed to compile output + } + memcpy(&node, root, sizeof(HDNode)); + if (hdnode_private_ckd_cached(&node, in->address_n, in->address_n_count, + NULL) == 0) { + return 0; // failed to compile output + } + hdnode_fill_public_key(&node); + if (!compute_address(coin, input_script_type, &node, in->has_multisig, + &in->multisig, in->address)) { + return 0; // failed to compile output + } + } else if (!in->has_address) { + return 0; // failed to compile output + } + + addr_raw_len = base58_decode_check(in->address, coin->curve->hasher_base58, + addr_raw, MAX_ADDR_RAW_SIZE); + size_t prefix_len; + if (coin->has_address_type // p2pkh + && addr_raw_len == + 20 + (prefix_len = address_prefix_bytes_len(coin->address_type)) && + address_check_prefix(addr_raw, coin->address_type)) { + out->script_pubkey.bytes[0] = 0x76; // OP_DUP + out->script_pubkey.bytes[1] = 0xA9; // OP_HASH_160 + out->script_pubkey.bytes[2] = 0x14; // pushing 20 bytes + memcpy(out->script_pubkey.bytes + 3, addr_raw + prefix_len, 20); + out->script_pubkey.bytes[23] = 0x88; // OP_EQUALVERIFY + out->script_pubkey.bytes[24] = 0xAC; // OP_CHECKSIG + out->script_pubkey.size = 25; + } else if (coin->has_address_type_p2sh // p2sh + && addr_raw_len == 20 + (prefix_len = address_prefix_bytes_len( + coin->address_type_p2sh)) && + address_check_prefix(addr_raw, coin->address_type_p2sh)) { + out->script_pubkey.bytes[0] = 0xA9; // OP_HASH_160 + out->script_pubkey.bytes[1] = 0x14; // pushing 20 bytes + memcpy(out->script_pubkey.bytes + 2, addr_raw + prefix_len, 20); + out->script_pubkey.bytes[22] = 0x87; // OP_EQUAL + out->script_pubkey.size = 23; + } else if (coin->cashaddr_prefix && + cash_addr_decode(addr_raw, &addr_raw_len, coin->cashaddr_prefix, + in->address)) { + if (addr_raw_len == 21 && addr_raw[0] == (CASHADDR_P2KH | CASHADDR_160)) { + out->script_pubkey.bytes[0] = 0x76; // OP_DUP + out->script_pubkey.bytes[1] = 0xA9; // OP_HASH_160 + out->script_pubkey.bytes[2] = 0x14; // pushing 20 bytes + memcpy(out->script_pubkey.bytes + 3, addr_raw + 1, 20); + out->script_pubkey.bytes[23] = 0x88; // OP_EQUALVERIFY + out->script_pubkey.bytes[24] = 0xAC; // OP_CHECKSIG + out->script_pubkey.size = 25; + + } else if (addr_raw_len == 21 && + addr_raw[0] == (CASHADDR_P2SH | CASHADDR_160)) { + out->script_pubkey.bytes[0] = 0xA9; // OP_HASH_160 + out->script_pubkey.bytes[1] = 0x14; // pushing 20 bytes + memcpy(out->script_pubkey.bytes + 2, addr_raw + 1, 20); + out->script_pubkey.bytes[22] = 0x87; // OP_EQUAL + out->script_pubkey.size = 23; + } else { + return 0; + } + } else if (coin->bech32_prefix) { + int witver; + if (!segwit_addr_decode(&witver, addr_raw, &addr_raw_len, + coin->bech32_prefix, in->address)) { + return 0; + } + // segwit: + // push 1 byte version id (opcode OP_0 = 0, OP_i = 80+i) + // push addr_raw (segwit_addr_decode makes sure addr_raw_len is at most 40) + out->script_pubkey.bytes[0] = witver == 0 ? 0 : 80 + witver; + out->script_pubkey.bytes[1] = addr_raw_len; + memcpy(out->script_pubkey.bytes + 2, addr_raw, addr_raw_len); + out->script_pubkey.size = addr_raw_len + 2; + } else { + return 0; + } + + if (needs_confirm) { + layoutConfirmOutput(coin, in); + if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmOutput, false)) { + return -1; // user aborted + } + } + + return out->script_pubkey.size; +} + +uint32_t compile_script_sig(uint32_t address_type, const uint8_t *pubkeyhash, + uint8_t *out) { + if (coinByAddressType(address_type)) { // valid coin type + out[0] = 0x76; // OP_DUP + out[1] = 0xA9; // OP_HASH_160 + out[2] = 0x14; // pushing 20 bytes + memcpy(out + 3, pubkeyhash, 20); + out[23] = 0x88; // OP_EQUALVERIFY + out[24] = 0xAC; // OP_CHECKSIG + return 25; + } else { + return 0; // unsupported + } +} + +// if out == NULL just compute the length +uint32_t compile_script_multisig(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint8_t *out) { + if (!multisig->has_m) return 0; + const uint32_t m = multisig->m; + const uint32_t n = cryptoMultisigPubkeyCount(multisig); + if (m < 1 || m > 15) return 0; + if (n < 1 || n > 15) return 0; + uint32_t r = 0; + if (out) { + out[r] = 0x50 + m; + r++; + for (uint32_t i = 0; i < n; i++) { + out[r] = 33; + r++; // OP_PUSH 33 + const HDNode *pubnode = cryptoMultisigPubkey(coin, multisig, i); + if (!pubnode) return 0; + memcpy(out + r, pubnode->public_key, 33); + r += 33; + } + out[r] = 0x50 + n; + r++; + out[r] = 0xAE; + r++; // OP_CHECKMULTISIG + } else { + r = 1 + 34 * n + 2; + } + return r; +} + +uint32_t compile_script_multisig_hash(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint8_t *hash) { + if (!multisig->has_m) return 0; + const uint32_t m = multisig->m; + const uint32_t n = cryptoMultisigPubkeyCount(multisig); + if (m < 1 || m > 15) return 0; + if (n < 1 || n > 15) return 0; + + Hasher hasher; + hasher_Init(&hasher, coin->curve->hasher_script); + + uint8_t d[2]; + d[0] = 0x50 + m; + hasher_Update(&hasher, d, 1); + for (uint32_t i = 0; i < n; i++) { + d[0] = 33; + hasher_Update(&hasher, d, 1); // OP_PUSH 33 + const HDNode *pubnode = cryptoMultisigPubkey(coin, multisig, i); + if (!pubnode) return 0; + hasher_Update(&hasher, pubnode->public_key, 33); + } + d[0] = 0x50 + n; + d[1] = 0xAE; + hasher_Update(&hasher, d, 2); + + hasher_Final(&hasher, hash); + + return 1; +} + +uint32_t serialize_script_sig(const uint8_t *signature, uint32_t signature_len, + const uint8_t *pubkey, uint32_t pubkey_len, + uint8_t sighash, uint8_t *out) { + uint32_t r = 0; + r += op_push(signature_len + 1, out + r); + memcpy(out + r, signature, signature_len); + r += signature_len; + out[r] = sighash; + r++; + r += op_push(pubkey_len, out + r); + memcpy(out + r, pubkey, pubkey_len); + r += pubkey_len; + return r; +} + +uint32_t serialize_script_multisig(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint8_t sighash, uint8_t *out) { + uint32_t r = 0; + if (!coin->decred) { + // Decred fixed the off-by-one bug + out[r] = 0x00; + r++; + } + for (uint32_t i = 0; i < multisig->signatures_count; i++) { + if (multisig->signatures[i].size == 0) { + continue; + } + r += op_push(multisig->signatures[i].size + 1, out + r); + memcpy(out + r, multisig->signatures[i].bytes, + multisig->signatures[i].size); + r += multisig->signatures[i].size; + out[r] = sighash; + r++; + } + uint32_t script_len = compile_script_multisig(coin, multisig, 0); + if (script_len == 0) { + return 0; + } + r += op_push(script_len, out + r); + r += compile_script_multisig(coin, multisig, out + r); + return r; +} + +// tx methods + +uint32_t tx_prevout_hash(Hasher *hasher, const TxInputType *input) { + for (int i = 0; i < 32; i++) { + hasher_Update(hasher, &(input->prev_hash.bytes[31 - i]), 1); + } + hasher_Update(hasher, (const uint8_t *)&input->prev_index, 4); + return 36; +} + +uint32_t tx_script_hash(Hasher *hasher, uint32_t size, const uint8_t *data) { + int r = ser_length_hash(hasher, size); + hasher_Update(hasher, data, size); + return r + size; +} + +uint32_t tx_sequence_hash(Hasher *hasher, const TxInputType *input) { + hasher_Update(hasher, (const uint8_t *)&input->sequence, 4); + return 4; +} + +uint32_t tx_output_hash(Hasher *hasher, const TxOutputBinType *output, + bool decred) { + uint32_t r = 0; + hasher_Update(hasher, (const uint8_t *)&output->amount, 8); + r += 8; + if (decred) { + uint16_t script_version = output->decred_script_version & 0xFFFF; + hasher_Update(hasher, (const uint8_t *)&script_version, 2); + r += 2; + } + r += tx_script_hash(hasher, output->script_pubkey.size, + output->script_pubkey.bytes); + return r; +} + +uint32_t tx_serialize_script(uint32_t size, const uint8_t *data, uint8_t *out) { + int r = ser_length(size, out); + memcpy(out + r, data, size); + return r + size; +} + +uint32_t tx_serialize_header(TxStruct *tx, uint8_t *out) { + int r = 4; + if (tx->overwintered) { + uint32_t ver = tx->version | TX_OVERWINTERED; + memcpy(out, &ver, 4); + memcpy(out + 4, &(tx->version_group_id), 4); + r += 4; + } else { + memcpy(out, &(tx->version), 4); + if (tx->is_segwit) { + memcpy(out + r, segwit_header, 2); + r += 2; + } + } + return r + ser_length(tx->inputs_len, out + r); +} + +uint32_t tx_serialize_header_hash(TxStruct *tx) { + int r = 4; + if (tx->overwintered) { + uint32_t ver = tx->version | TX_OVERWINTERED; + hasher_Update(&(tx->hasher), (const uint8_t *)&ver, 4); + hasher_Update(&(tx->hasher), (const uint8_t *)&(tx->version_group_id), 4); + r += 4; + } else { + hasher_Update(&(tx->hasher), (const uint8_t *)&(tx->version), 4); + if (tx->is_segwit) { + hasher_Update(&(tx->hasher), segwit_header, 2); + r += 2; + } + } + return r + ser_length_hash(&(tx->hasher), tx->inputs_len); +} + +uint32_t tx_serialize_input(TxStruct *tx, const TxInputType *input, + uint8_t *out) { + if (tx->have_inputs >= tx->inputs_len) { + // already got all inputs + return 0; + } + uint32_t r = 0; + if (tx->have_inputs == 0) { + r += tx_serialize_header(tx, out + r); + } + for (int i = 0; i < 32; i++) { + *(out + r + i) = input->prev_hash.bytes[31 - i]; + } + r += 32; + memcpy(out + r, &input->prev_index, 4); + r += 4; + if (tx->is_decred) { + uint8_t tree = input->decred_tree & 0xFF; + out[r++] = tree; + } else { + r += tx_serialize_script(input->script_sig.size, input->script_sig.bytes, + out + r); + } + memcpy(out + r, &input->sequence, 4); + r += 4; + + tx->have_inputs++; + tx->size += r; + + return r; +} + +uint32_t tx_serialize_input_hash(TxStruct *tx, const TxInputType *input) { + if (tx->have_inputs >= tx->inputs_len) { + // already got all inputs + return 0; + } + uint32_t r = 0; + if (tx->have_inputs == 0) { + r += tx_serialize_header_hash(tx); + } + r += tx_prevout_hash(&(tx->hasher), input); + if (tx->is_decred) { + uint8_t tree = input->decred_tree & 0xFF; + hasher_Update(&(tx->hasher), (const uint8_t *)&(tree), 1); + r++; + } else { + r += tx_script_hash(&(tx->hasher), input->script_sig.size, + input->script_sig.bytes); + } + r += tx_sequence_hash(&(tx->hasher), input); + + tx->have_inputs++; + tx->size += r; + + return r; +} + +uint32_t tx_serialize_decred_witness(TxStruct *tx, const TxInputType *input, + uint8_t *out) { + static const uint64_t amount = 0; + static const uint32_t block_height = 0x00000000; + static const uint32_t block_index = 0xFFFFFFFF; + + if (tx->have_inputs >= tx->inputs_len) { + // already got all inputs + return 0; + } + uint32_t r = 0; + if (tx->have_inputs == 0) { + r += ser_length(tx->inputs_len, out + r); + } + memcpy(out + r, &amount, 8); + r += 8; + memcpy(out + r, &block_height, 4); + r += 4; + memcpy(out + r, &block_index, 4); + r += 4; + r += tx_serialize_script(input->script_sig.size, input->script_sig.bytes, + out + r); + + tx->have_inputs++; + tx->size += r; + + return r; +} + +uint32_t tx_serialize_decred_witness_hash(TxStruct *tx, + const TxInputType *input) { + if (tx->have_inputs >= tx->inputs_len) { + // already got all inputs + return 0; + } + uint32_t r = 0; + if (tx->have_inputs == 0) { + r += tx_serialize_header_hash(tx); + } + if (input == NULL) { + r += ser_length_hash(&(tx->hasher), 0); + } else { + r += tx_script_hash(&(tx->hasher), input->script_sig.size, + input->script_sig.bytes); + } + + tx->have_inputs++; + tx->size += r; + + return r; +} + +uint32_t tx_serialize_middle(TxStruct *tx, uint8_t *out) { + return ser_length(tx->outputs_len, out); +} + +uint32_t tx_serialize_middle_hash(TxStruct *tx) { + return ser_length_hash(&(tx->hasher), tx->outputs_len); +} + +uint32_t tx_serialize_footer(TxStruct *tx, uint8_t *out) { + memcpy(out, &(tx->lock_time), 4); + if (tx->overwintered) { + if (tx->version == 3) { + memcpy(out + 4, &(tx->expiry), 4); + out[8] = 0x00; // nJoinSplit + return 9; + } else if (tx->version == 4) { + memcpy(out + 4, &(tx->expiry), 4); + memzero(out + 8, 8); // valueBalance + out[16] = 0x00; // nShieldedSpend + out[17] = 0x00; // nShieldedOutput + out[18] = 0x00; // nJoinSplit + return 19; + } + } + if (tx->is_decred) { + memcpy(out + 4, &(tx->expiry), 4); + return 8; + } + return 4; +} + +uint32_t tx_serialize_footer_hash(TxStruct *tx) { + hasher_Update(&(tx->hasher), (const uint8_t *)&(tx->lock_time), 4); + if (tx->overwintered) { + hasher_Update(&(tx->hasher), (const uint8_t *)&(tx->expiry), 4); + hasher_Update(&(tx->hasher), (const uint8_t *)"\x00", 1); // nJoinSplit + return 9; + } + if (tx->is_decred) { + hasher_Update(&(tx->hasher), (const uint8_t *)&(tx->expiry), 4); + return 8; + } + return 4; +} + +uint32_t tx_serialize_output(TxStruct *tx, const TxOutputBinType *output, + uint8_t *out) { + if (tx->have_inputs < tx->inputs_len) { + // not all inputs provided + return 0; + } + if (tx->have_outputs >= tx->outputs_len) { + // already got all outputs + return 0; + } + uint32_t r = 0; + if (tx->have_outputs == 0) { + r += tx_serialize_middle(tx, out + r); + } + memcpy(out + r, &output->amount, 8); + r += 8; + if (tx->is_decred) { + uint16_t script_version = output->decred_script_version & 0xFFFF; + memcpy(out + r, &script_version, 2); + r += 2; + } + r += tx_serialize_script(output->script_pubkey.size, + output->script_pubkey.bytes, out + r); + tx->have_outputs++; + if (tx->have_outputs == tx->outputs_len && !tx->is_segwit) { + r += tx_serialize_footer(tx, out + r); + } + tx->size += r; + return r; +} + +uint32_t tx_serialize_output_hash(TxStruct *tx, const TxOutputBinType *output) { + if (tx->have_inputs < tx->inputs_len) { + // not all inputs provided + return 0; + } + if (tx->have_outputs >= tx->outputs_len) { + // already got all outputs + return 0; + } + uint32_t r = 0; + if (tx->have_outputs == 0) { + r += tx_serialize_middle_hash(tx); + } + r += tx_output_hash(&(tx->hasher), output, tx->is_decred); + tx->have_outputs++; + if (tx->have_outputs == tx->outputs_len && !tx->is_segwit) { + r += tx_serialize_footer_hash(tx); + } + tx->size += r; + return r; +} + +uint32_t tx_serialize_extra_data_hash(TxStruct *tx, const uint8_t *data, + uint32_t datalen) { + if (tx->have_inputs < tx->inputs_len) { + // not all inputs provided + return 0; + } + if (tx->have_outputs < tx->outputs_len) { + // not all inputs provided + return 0; + } + if (tx->extra_data_received + datalen > tx->extra_data_len) { + // we are receiving too much data + return 0; + } + hasher_Update(&(tx->hasher), data, datalen); + tx->extra_data_received += datalen; + tx->size += datalen; + return datalen; +} + +void tx_init(TxStruct *tx, uint32_t inputs_len, uint32_t outputs_len, + uint32_t version, uint32_t lock_time, uint32_t expiry, + uint32_t extra_data_len, HasherType hasher_sign, bool overwintered, + uint32_t version_group_id) { + tx->inputs_len = inputs_len; + tx->outputs_len = outputs_len; + tx->version = version; + tx->lock_time = lock_time; + tx->expiry = expiry; + tx->have_inputs = 0; + tx->have_outputs = 0; + tx->extra_data_len = extra_data_len; + tx->extra_data_received = 0; + tx->size = 0; + tx->is_segwit = false; + tx->is_decred = false; + tx->overwintered = overwintered; + tx->version_group_id = version_group_id; + hasher_Init(&(tx->hasher), hasher_sign); +} + +void tx_hash_final(TxStruct *t, uint8_t *hash, bool reverse) { + hasher_Final(&(t->hasher), hash); + if (!reverse) return; + for (uint8_t i = 0; i < 16; i++) { + uint8_t k = hash[31 - i]; + hash[31 - i] = hash[i]; + hash[i] = k; + } +} + +static uint32_t tx_input_script_size(const TxInputType *txinput) { + uint32_t input_script_size; + if (txinput->has_multisig) { + uint32_t multisig_script_size = + TXSIZE_MULTISIGSCRIPT + + txinput->multisig.pubkeys_count * (1 + TXSIZE_PUBKEY); + input_script_size = 1 // the OP_FALSE bug in multisig + + txinput->multisig.m * (1 + TXSIZE_SIGNATURE) + + op_push_size(multisig_script_size) + + multisig_script_size; + } else { + input_script_size = (1 + TXSIZE_SIGNATURE + 1 + TXSIZE_PUBKEY); + } + + return input_script_size; +} + +uint32_t tx_input_weight(const CoinInfo *coin, const TxInputType *txinput) { + if (coin->decred) { + return 4 * (TXSIZE_INPUT + 1); // Decred tree + } + + uint32_t input_script_size = tx_input_script_size(txinput); + uint32_t weight = 4 * TXSIZE_INPUT; + if (txinput->script_type == InputScriptType_SPENDADDRESS || + txinput->script_type == InputScriptType_SPENDMULTISIG) { + input_script_size += ser_length_size(input_script_size); + weight += 4 * input_script_size; + } else if (txinput->script_type == InputScriptType_SPENDWITNESS || + txinput->script_type == InputScriptType_SPENDP2SHWITNESS) { + if (txinput->script_type == InputScriptType_SPENDP2SHWITNESS) { + weight += 4 * (2 + (txinput->has_multisig ? TXSIZE_WITNESSSCRIPT + : TXSIZE_WITNESSPKHASH)); + } else { + weight += 4; // empty input script + } + weight += input_script_size; // discounted witness + } + return weight; +} + +uint32_t tx_output_weight(const CoinInfo *coin, const TxOutputType *txoutput) { + uint32_t output_script_size = 0; + if (txoutput->script_type == OutputScriptType_PAYTOOPRETURN) { + output_script_size = 1 + op_push_size(txoutput->op_return_data.size) + + txoutput->op_return_data.size; + } else if (txoutput->address_n_count > 0) { + if (txoutput->script_type == OutputScriptType_PAYTOWITNESS) { + output_script_size = + txoutput->has_multisig ? TXSIZE_WITNESSSCRIPT : TXSIZE_WITNESSPKHASH; + } else if (txoutput->script_type == OutputScriptType_PAYTOP2SHWITNESS) { + output_script_size = TXSIZE_P2SCRIPT; + } else { + output_script_size = + txoutput->has_multisig ? TXSIZE_P2SCRIPT : TXSIZE_P2PKHASH; + } + } else { + uint8_t addr_raw[MAX_ADDR_RAW_SIZE]; + int witver; + size_t addr_raw_len; + if (coin->cashaddr_prefix && + cash_addr_decode(addr_raw, &addr_raw_len, coin->cashaddr_prefix, + txoutput->address)) { + if (addr_raw_len == 21 && addr_raw[0] == (CASHADDR_P2KH | CASHADDR_160)) { + output_script_size = TXSIZE_P2PKHASH; + } else if (addr_raw_len == 21 && + addr_raw[0] == (CASHADDR_P2SH | CASHADDR_160)) { + output_script_size = TXSIZE_P2SCRIPT; + } + } else if (coin->bech32_prefix && + segwit_addr_decode(&witver, addr_raw, &addr_raw_len, + coin->bech32_prefix, txoutput->address)) { + output_script_size = 2 + addr_raw_len; + } else { + addr_raw_len = + base58_decode_check(txoutput->address, coin->curve->hasher_base58, + addr_raw, MAX_ADDR_RAW_SIZE); + if (coin->has_address_type && + address_check_prefix(addr_raw, coin->address_type)) { + output_script_size = TXSIZE_P2PKHASH; + } else if (coin->has_address_type_p2sh && + address_check_prefix(addr_raw, coin->address_type_p2sh)) { + output_script_size = TXSIZE_P2SCRIPT; + } + } + } + output_script_size += ser_length_size(output_script_size); + + uint32_t size = TXSIZE_OUTPUT; + if (coin->decred) { + size += 2; // Decred script version + } + + return 4 * (size + output_script_size); +} + +uint32_t tx_decred_witness_weight(const TxInputType *txinput) { + uint32_t input_script_size = tx_input_script_size(txinput); + uint32_t size = TXSIZE_DECRED_WITNESS + ser_length_size(input_script_size) + + input_script_size; + + return 4 * size; +} diff --git a/legacy/firmware/transaction.h b/legacy/firmware/transaction.h new file mode 100644 index 0000000000..3a13f351d3 --- /dev/null +++ b/legacy/firmware/transaction.h @@ -0,0 +1,109 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __TRANSACTION_H__ +#define __TRANSACTION_H__ + +#include +#include +#include "bip32.h" +#include "coins.h" +#include "hasher.h" +#include "messages-bitcoin.pb.h" +#include "sha2.h" + +#define TX_OVERWINTERED 0x80000000 + +typedef struct { + uint32_t inputs_len; + uint32_t outputs_len; + + uint32_t version; + uint32_t version_group_id; + uint32_t lock_time; + uint32_t expiry; + bool is_segwit; + bool is_decred; + + uint32_t have_inputs; + uint32_t have_outputs; + + bool overwintered; + uint32_t extra_data_len; + uint32_t extra_data_received; + + uint32_t size; + + Hasher hasher; +} TxStruct; + +bool compute_address(const CoinInfo *coin, InputScriptType script_type, + const HDNode *node, bool has_multisig, + const MultisigRedeemScriptType *multisig, + char address[MAX_ADDR_SIZE]); +uint32_t compile_script_sig(uint32_t address_type, const uint8_t *pubkeyhash, + uint8_t *out); +uint32_t compile_script_multisig(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint8_t *out); +uint32_t compile_script_multisig_hash(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint8_t *hash); +uint32_t serialize_script_sig(const uint8_t *signature, uint32_t signature_len, + const uint8_t *pubkey, uint32_t pubkey_len, + uint8_t sighash, uint8_t *out); +uint32_t serialize_script_multisig(const CoinInfo *coin, + const MultisigRedeemScriptType *multisig, + uint8_t sighash, uint8_t *out); +int compile_output(const CoinInfo *coin, const HDNode *root, TxOutputType *in, + TxOutputBinType *out, bool needs_confirm); + +uint32_t tx_prevout_hash(Hasher *hasher, const TxInputType *input); +uint32_t tx_script_hash(Hasher *hasher, uint32_t size, const uint8_t *data); +uint32_t tx_sequence_hash(Hasher *hasher, const TxInputType *input); +uint32_t tx_output_hash(Hasher *hasher, const TxOutputBinType *output, + bool decred); +uint32_t tx_serialize_script(uint32_t size, const uint8_t *data, uint8_t *out); + +uint32_t tx_serialize_footer(TxStruct *tx, uint8_t *out); +uint32_t tx_serialize_input(TxStruct *tx, const TxInputType *input, + uint8_t *out); +uint32_t tx_serialize_output(TxStruct *tx, const TxOutputBinType *output, + uint8_t *out); +uint32_t tx_serialize_decred_witness(TxStruct *tx, const TxInputType *input, + uint8_t *out); + +void tx_init(TxStruct *tx, uint32_t inputs_len, uint32_t outputs_len, + uint32_t version, uint32_t lock_time, uint32_t expiry, + uint32_t extra_data_len, HasherType hasher_sign, bool overwintered, + uint32_t version_group_id); +uint32_t tx_serialize_header_hash(TxStruct *tx); +uint32_t tx_serialize_input_hash(TxStruct *tx, const TxInputType *input); +uint32_t tx_serialize_output_hash(TxStruct *tx, const TxOutputBinType *output); +uint32_t tx_serialize_extra_data_hash(TxStruct *tx, const uint8_t *data, + uint32_t datalen); +uint32_t tx_serialize_decred_witness_hash(TxStruct *tx, + const TxInputType *input); +void tx_hash_final(TxStruct *t, uint8_t *hash, bool reverse); + +uint32_t tx_input_weight(const CoinInfo *coin, const TxInputType *txinput); +uint32_t tx_output_weight(const CoinInfo *coin, const TxOutputType *txoutput); +uint32_t tx_decred_witness_weight(const TxInputType *txinput); + +#endif diff --git a/legacy/firmware/trezor.c b/legacy/firmware/trezor.c new file mode 100644 index 0000000000..b112a87e70 --- /dev/null +++ b/legacy/firmware/trezor.c @@ -0,0 +1,159 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "trezor.h" +#include "bitmaps.h" +#include "bl_check.h" +#include "buttons.h" +#include "common.h" +#include "config.h" +#include "gettext.h" +#include "layout.h" +#include "layout2.h" +#include "memzero.h" +#include "oled.h" +#include "rng.h" +#include "setup.h" +#include "timer.h" +#include "usb.h" +#include "util.h" +#if !EMULATOR +#include +#include "otp.h" +#endif + +/* Screen timeout */ +uint32_t system_millis_lock_start; + +void check_lock_screen(void) { + buttonUpdate(); + + // wake from screensaver on any button + if (layoutLast == layoutScreensaver && (button.NoUp || button.YesUp)) { + layoutHome(); + return; + } + + // button held for long enough (2 seconds) + if (layoutLast == layoutHome && button.NoDown >= 285000 * 2) { + layoutDialog(&bmp_icon_question, _("Cancel"), _("Lock Device"), NULL, + _("Do you really want to"), _("lock your TREZOR?"), NULL, NULL, + NULL, NULL); + + // wait until NoButton is released + usbTiny(1); + do { + usbSleep(5); + buttonUpdate(); + } while (!button.NoUp); + + // wait for confirmation/cancellation of the dialog + do { + usbSleep(5); + buttonUpdate(); + } while (!button.YesUp && !button.NoUp); + usbTiny(0); + + if (button.YesUp) { + // lock the screen + session_clear(true); + layoutScreensaver(); + } else { + // resume homescreen + layoutHome(); + } + } + + // if homescreen is shown for too long + if (layoutLast == layoutHome) { + if ((timer_ms() - system_millis_lock_start) >= + config_getAutoLockDelayMs()) { + // lock the screen + session_clear(true); + layoutScreensaver(); + } + } +} + +static void collect_hw_entropy(bool privileged) { +#if EMULATOR + (void)privileged; + memzero(HW_ENTROPY_DATA, HW_ENTROPY_LEN); +#else + if (privileged) { + desig_get_unique_id((uint32_t *)HW_ENTROPY_DATA); + // set entropy in the OTP randomness block + if (!flash_otp_is_locked(FLASH_OTP_BLOCK_RANDOMNESS)) { + uint8_t entropy[FLASH_OTP_BLOCK_SIZE]; + random_buffer(entropy, FLASH_OTP_BLOCK_SIZE); + flash_otp_write(FLASH_OTP_BLOCK_RANDOMNESS, 0, entropy, + FLASH_OTP_BLOCK_SIZE); + flash_otp_lock(FLASH_OTP_BLOCK_RANDOMNESS); + } + // collect entropy from OTP randomness block + flash_otp_read(FLASH_OTP_BLOCK_RANDOMNESS, 0, HW_ENTROPY_DATA + 12, + FLASH_OTP_BLOCK_SIZE); + } else { + // unprivileged mode => use fixed HW_ENTROPY + memset(HW_ENTROPY_DATA, 0x3C, HW_ENTROPY_LEN); + } +#endif +} + +int main(void) { +#ifndef APPVER + setup(); + __stack_chk_guard = random32(); // this supports compiler provided + // unpredictable stack protection checks + oledInit(); +#else + check_bootloader(); + setupApp(); + __stack_chk_guard = random32(); // this supports compiler provided + // unpredictable stack protection checks +#endif + if (!is_mode_unprivileged()) { + collect_hw_entropy(true); + timer_init(); +#ifdef APPVER + // enable MPU (Memory Protection Unit) + mpu_config_firmware(); +#endif + } else { + collect_hw_entropy(false); + } + +#if DEBUG_LINK + oledSetDebugLink(1); + config_wipe(); +#endif + + oledDrawBitmap(40, 0, &bmp_logo64); + oledRefresh(); + + config_init(); + layoutHome(); + usbInit(); + for (;;) { + usbPoll(); + check_lock_screen(); + } + + return 0; +} diff --git a/legacy/firmware/trezor.h b/legacy/firmware/trezor.h new file mode 100644 index 0000000000..9975a997b7 --- /dev/null +++ b/legacy/firmware/trezor.h @@ -0,0 +1,40 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __TREZOR_H__ +#define __TREZOR_H__ + +#include +#include "version.h" + +#define STR(X) #X +#define VERSTR(X) STR(X) + +#ifndef DEBUG_LINK +#define DEBUG_LINK 0 +#endif + +#ifndef DEBUG_LOG +#define DEBUG_LOG 0 +#endif + +/* Screen timeout */ +extern uint32_t system_millis_lock_start; + +#endif diff --git a/legacy/firmware/u2f.c b/legacy/firmware/u2f.c new file mode 100644 index 0000000000..1c799dc495 --- /dev/null +++ b/legacy/firmware/u2f.c @@ -0,0 +1,755 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2015 Mark Bryars + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include + +#include "bip32.h" +#include "buttons.h" +#include "config.h" +#include "curves.h" +#include "debug.h" +#include "gettext.h" +#include "hmac.h" +#include "layout2.h" +#include "memzero.h" +#include "nist256p1.h" +#include "rng.h" +#include "trezor.h" +#include "usb.h" +#include "util.h" + +#include "u2f.h" +#include "u2f/u2f.h" +#include "u2f/u2f_hid.h" +#include "u2f/u2f_keys.h" +#include "u2f_knownapps.h" + +// About 1/2 Second according to values used in protect.c +#define U2F_TIMEOUT (800000 / 2) +#define U2F_OUT_PKT_BUFFER_LEN 130 + +// Initialise without a cid +static uint32_t cid = 0; + +// Circular Output buffer +static uint32_t u2f_out_start = 0; +static uint32_t u2f_out_end = 0; +static uint8_t u2f_out_packets[U2F_OUT_PKT_BUFFER_LEN][HID_RPT_SIZE]; + +#define U2F_PUBKEY_LEN 65 +#define KEY_PATH_LEN 32 +#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH) + +// Derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r' +#define KEY_PATH_ENTRIES (KEY_PATH_LEN / sizeof(uint32_t)) + +// Defined as UsbSignHandler.BOGUS_APP_ID_HASH +// in +// https://github.com/google/u2f-ref-code/blob/master/u2f-chrome-extension/usbsignhandler.js#L118 +#define BOGUS_APPID "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + +// Auth/Register request state machine +typedef enum { + INIT = 0, + AUTH = 10, + AUTH_PASS = 11, + REG = 20, + REG_PASS = 21 +} U2F_STATE; + +static U2F_STATE last_req_state = INIT; + +typedef struct { + uint8_t reserved; + uint8_t appId[U2F_APPID_SIZE]; + uint8_t chal[U2F_CHAL_SIZE]; + uint8_t keyHandle[KEY_HANDLE_LEN]; + uint8_t pubKey[U2F_PUBKEY_LEN]; +} U2F_REGISTER_SIG_STR; + +typedef struct { + uint8_t appId[U2F_APPID_SIZE]; + uint8_t flags; + uint8_t ctr[4]; + uint8_t chal[U2F_CHAL_SIZE]; +} U2F_AUTHENTICATE_SIG_STR; + +static uint32_t dialog_timeout = 0; + +uint32_t next_cid(void) { + // extremely unlikely but hey + do { + cid = random32(); + } while (cid == 0 || cid == CID_BROADCAST); + return cid; +} + +// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-hid-protocol-v1.2-ps-20170411.html#message--and-packet-structure +// states the following: +// With a packet size of 64 bytes (max for full-speed devices), this means that +// the maximum message payload length is 64 - 7 + 128 * (64 - 5) = 7609 bytes. +#define U2F_MAXIMUM_PAYLOAD_LENGTH 7609 +typedef struct { + uint8_t buf[U2F_MAXIMUM_PAYLOAD_LENGTH]; + uint8_t *buf_ptr; + uint32_t len; + uint8_t seq; + uint8_t cmd; +} U2F_ReadBuffer; + +U2F_ReadBuffer *reader; + +void u2fhid_read(char tiny, const U2FHID_FRAME *f) { + // Always handle init packets directly + if (f->init.cmd == U2FHID_INIT) { + u2fhid_init(f); + if (tiny && reader && f->cid == cid) { + // abort current channel + reader->cmd = 0; + reader->len = 0; + reader->seq = 255; + } + return; + } + + if (tiny) { + // read continue packet + if (reader == 0 || cid != f->cid) { + send_u2fhid_error(f->cid, ERR_CHANNEL_BUSY); + return; + } + + if ((f->type & TYPE_INIT) && reader->seq == 255) { + u2fhid_init_cmd(f); + return; + } + + if (reader->seq != f->cont.seq) { + send_u2fhid_error(f->cid, ERR_INVALID_SEQ); + reader->cmd = 0; + reader->len = 0; + reader->seq = 255; + return; + } + + // check out of bounds + if ((reader->buf_ptr - reader->buf) >= (signed)reader->len || + (reader->buf_ptr + sizeof(f->cont.data) - reader->buf) > + (signed)sizeof(reader->buf)) + return; + reader->seq++; + memcpy(reader->buf_ptr, f->cont.data, sizeof(f->cont.data)); + reader->buf_ptr += sizeof(f->cont.data); + return; + } + + u2fhid_read_start(f); +} + +void u2fhid_init_cmd(const U2FHID_FRAME *f) { + reader->seq = 0; + reader->buf_ptr = reader->buf; + reader->len = MSG_LEN(*f); + reader->cmd = f->type; + memcpy(reader->buf_ptr, f->init.data, sizeof(f->init.data)); + reader->buf_ptr += sizeof(f->init.data); + cid = f->cid; +} + +void u2fhid_read_start(const U2FHID_FRAME *f) { + U2F_ReadBuffer readbuffer; + memzero(&readbuffer, sizeof(readbuffer)); + + if (!(f->type & TYPE_INIT)) { + return; + } + + // Broadcast is reserved for init + if (f->cid == CID_BROADCAST || f->cid == 0) { + send_u2fhid_error(f->cid, ERR_INVALID_CID); + return; + } + + if ((unsigned)MSG_LEN(*f) > sizeof(reader->buf)) { + send_u2fhid_error(f->cid, ERR_INVALID_LEN); + return; + } + + reader = &readbuffer; + u2fhid_init_cmd(f); + + usbTiny(1); + for (;;) { + // Do we need to wait for more data + while ((reader->buf_ptr - reader->buf) < (signed)reader->len) { + uint8_t lastseq = reader->seq; + uint8_t lastcmd = reader->cmd; + int counter = U2F_TIMEOUT; + while (reader->seq == lastseq && reader->cmd == lastcmd) { + if (counter-- == 0) { + // timeout + send_u2fhid_error(cid, ERR_MSG_TIMEOUT); + cid = 0; + reader = 0; + usbTiny(0); + layoutHome(); + return; + } + usbPoll(); + } + } + + // We have all the data + switch (reader->cmd) { + case 0: + // message was aborted by init + break; + case U2FHID_PING: + u2fhid_ping(reader->buf, reader->len); + break; + case U2FHID_MSG: + u2fhid_msg((APDU *)reader->buf, reader->len); + break; + case U2FHID_WINK: + u2fhid_wink(reader->buf, reader->len); + break; + default: + send_u2fhid_error(cid, ERR_INVALID_CMD); + break; + } + + // wait for next commmand/ button press + reader->cmd = 0; + reader->seq = 255; + while (dialog_timeout > 0 && reader->cmd == 0) { + dialog_timeout--; + usbPoll(); // may trigger new request + buttonUpdate(); + if (button.YesUp && (last_req_state == AUTH || last_req_state == REG)) { + last_req_state++; + // standard requires to remember button press for 10 seconds. + dialog_timeout = 10 * U2F_TIMEOUT; + } + } + + if (reader->cmd == 0) { + last_req_state = INIT; + cid = 0; + reader = 0; + usbTiny(0); + layoutHome(); + return; + } + } +} + +void u2fhid_ping(const uint8_t *buf, uint32_t len) { + debugLog(0, "", "u2fhid_ping"); + send_u2fhid_msg(U2FHID_PING, buf, len); +} + +void u2fhid_wink(const uint8_t *buf, uint32_t len) { + debugLog(0, "", "u2fhid_wink"); + (void)buf; + + if (len > 0) return send_u2fhid_error(cid, ERR_INVALID_LEN); + + if (dialog_timeout > 0) dialog_timeout = U2F_TIMEOUT; + + U2FHID_FRAME f; + memzero(&f, sizeof(f)); + f.cid = cid; + f.init.cmd = U2FHID_WINK; + f.init.bcntl = 0; + queue_u2f_pkt(&f); +} + +void u2fhid_init(const U2FHID_FRAME *in) { + const U2FHID_INIT_REQ *init_req = (const U2FHID_INIT_REQ *)&in->init.data; + U2FHID_FRAME f; + U2FHID_INIT_RESP resp; + memzero(&resp, sizeof(resp)); + + debugLog(0, "", "u2fhid_init"); + + if (in->cid == 0) { + send_u2fhid_error(in->cid, ERR_INVALID_CID); + return; + } + + memzero(&f, sizeof(f)); + f.cid = in->cid; + f.init.cmd = U2FHID_INIT; + f.init.bcnth = 0; + f.init.bcntl = sizeof(resp); + + memcpy(resp.nonce, init_req->nonce, sizeof(init_req->nonce)); + resp.cid = in->cid == CID_BROADCAST ? next_cid() : in->cid; + resp.versionInterface = U2FHID_IF_VERSION; + resp.versionMajor = VERSION_MAJOR; + resp.versionMinor = VERSION_MINOR; + resp.versionBuild = VERSION_PATCH; + resp.capFlags = CAPFLAG_WINK; + memcpy(&f.init.data, &resp, sizeof(resp)); + + queue_u2f_pkt(&f); +} + +void queue_u2f_pkt(const U2FHID_FRAME *u2f_pkt) { + // debugLog(0, "", "u2f_write_pkt"); + uint32_t next = (u2f_out_end + 1) % U2F_OUT_PKT_BUFFER_LEN; + if (u2f_out_start == next) { + debugLog(0, "", "u2f_write_pkt full"); + return; // Buffer full :( + } + memcpy(u2f_out_packets[u2f_out_end], u2f_pkt, HID_RPT_SIZE); + u2f_out_end = next; +} + +uint8_t *u2f_out_data(void) { + if (u2f_out_start == u2f_out_end) return NULL; // No data + // debugLog(0, "", "u2f_out_data"); + uint32_t t = u2f_out_start; + u2f_out_start = (u2f_out_start + 1) % U2F_OUT_PKT_BUFFER_LEN; + return u2f_out_packets[t]; +} + +void u2fhid_msg(const APDU *a, uint32_t len) { + if ((APDU_LEN(*a) + sizeof(APDU)) > len) { + debugLog(0, "", "BAD APDU LENGTH"); + debugInt(APDU_LEN(*a)); + debugInt(len); + return; + } + + if (a->cla != 0) { + send_u2f_error(U2F_SW_CLA_NOT_SUPPORTED); + return; + } + + switch (a->ins) { + case U2F_REGISTER: + u2f_register(a); + break; + case U2F_AUTHENTICATE: + u2f_authenticate(a); + break; + case U2F_VERSION: + u2f_version(a); + break; + default: + debugLog(0, "", "u2f unknown cmd"); + send_u2f_error(U2F_SW_INS_NOT_SUPPORTED); + } +} + +void send_u2fhid_msg(const uint8_t cmd, const uint8_t *data, + const uint32_t len) { + if (len > U2F_MAXIMUM_PAYLOAD_LENGTH) { + debugLog(0, "", "send_u2fhid_msg failed"); + return; + } + + U2FHID_FRAME f; + uint8_t *p = (uint8_t *)data; + uint32_t l = len; + uint32_t psz; + uint8_t seq = 0; + + // debugLog(0, "", "send_u2fhid_msg"); + + memzero(&f, sizeof(f)); + f.cid = cid; + f.init.cmd = cmd; + f.init.bcnth = len >> 8; + f.init.bcntl = len & 0xff; + + // Init packet + psz = MIN(sizeof(f.init.data), l); + memcpy(f.init.data, p, psz); + queue_u2f_pkt(&f); + l -= psz; + p += psz; + + // Cont packet(s) + for (; l > 0; l -= psz, p += psz) { + // debugLog(0, "", "send_u2fhid_msg con"); + memzero(&f.cont.data, sizeof(f.cont.data)); + f.cont.seq = seq++; + psz = MIN(sizeof(f.cont.data), l); + memcpy(f.cont.data, p, psz); + queue_u2f_pkt(&f); + } + + if (data + len != p) { + debugLog(0, "", "send_u2fhid_msg is bad"); + debugInt(data + len - p); + } +} + +void send_u2fhid_error(uint32_t fcid, uint8_t err) { + U2FHID_FRAME f; + + memzero(&f, sizeof(f)); + f.cid = fcid; + f.init.cmd = U2FHID_ERROR; + f.init.bcntl = 1; + f.init.data[0] = err; + queue_u2f_pkt(&f); +} + +void u2f_version(const APDU *a) { + if (APDU_LEN(*a) != 0) { + debugLog(0, "", "u2f version - badlen"); + send_u2f_error(U2F_SW_WRONG_LENGTH); + return; + } + + // INCLUDES SW_NO_ERROR + static const uint8_t version_response[] = {'U', '2', 'F', '_', + 'V', '2', 0x90, 0x00}; + debugLog(0, "", "u2f version"); + send_u2f_msg(version_response, sizeof(version_response)); +} + +static void getReadableAppId(const uint8_t appid[U2F_APPID_SIZE], + const char **appname) { + static char buf[8 + 2 + 8 + 1]; + + for (unsigned int i = 0; i < sizeof(u2f_well_known) / sizeof(U2FWellKnown); + i++) { + if (memcmp(appid, u2f_well_known[i].appid, U2F_APPID_SIZE) == 0) { + *appname = u2f_well_known[i].appname; + return; + } + } + + data2hex(appid, 4, &buf[0]); + buf[8] = buf[9] = '.'; + data2hex(appid + (U2F_APPID_SIZE - 4), 4, &buf[10]); + *appname = buf; +} + +static const HDNode *getDerivedNode(uint32_t *address_n, + size_t address_n_count) { + static CONFIDENTIAL HDNode node; + if (!config_getU2FRoot(&node)) { + layoutHome(); + debugLog(0, "", "ERR: Device not init"); + return 0; + } + if (!address_n || address_n_count == 0) { + return &node; + } + for (size_t i = 0; i < address_n_count; i++) { + if (hdnode_private_ckd(&node, address_n[i]) == 0) { + layoutHome(); + debugLog(0, "", "ERR: Derive private failed"); + return 0; + } + } + return &node; +} + +static const HDNode *generateKeyHandle(const uint8_t app_id[], + uint8_t key_handle[]) { + uint8_t keybase[U2F_APPID_SIZE + KEY_PATH_LEN]; + + // Derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r' + uint32_t key_path[KEY_PATH_ENTRIES]; + for (uint32_t i = 0; i < KEY_PATH_ENTRIES; i++) { + // high bit for hardened keys + key_path[i] = 0x80000000 | random32(); + } + + // First half of keyhandle is key_path + memcpy(key_handle, key_path, KEY_PATH_LEN); + + // prepare keypair from /random data + const HDNode *node = getDerivedNode(key_path, KEY_PATH_ENTRIES); + if (!node) return NULL; + + // For second half of keyhandle + // Signature of app_id and random data + memcpy(&keybase[0], app_id, U2F_APPID_SIZE); + memcpy(&keybase[U2F_APPID_SIZE], key_handle, KEY_PATH_LEN); + hmac_sha256(node->private_key, sizeof(node->private_key), keybase, + sizeof(keybase), &key_handle[KEY_PATH_LEN]); + + // Done! + return node; +} + +static const HDNode *validateKeyHandle(const uint8_t app_id[], + const uint8_t key_handle[]) { + uint32_t key_path[KEY_PATH_ENTRIES]; + memcpy(key_path, key_handle, KEY_PATH_LEN); + for (unsigned int i = 0; i < KEY_PATH_ENTRIES; i++) { + // check high bit for hardened keys + if (!(key_path[i] & 0x80000000)) { + return NULL; + } + } + + const HDNode *node = getDerivedNode(key_path, KEY_PATH_ENTRIES); + if (!node) return NULL; + + uint8_t keybase[U2F_APPID_SIZE + KEY_PATH_LEN]; + memcpy(&keybase[0], app_id, U2F_APPID_SIZE); + memcpy(&keybase[U2F_APPID_SIZE], key_handle, KEY_PATH_LEN); + + uint8_t hmac[SHA256_DIGEST_LENGTH]; + hmac_sha256(node->private_key, sizeof(node->private_key), keybase, + sizeof(keybase), hmac); + + if (memcmp(&key_handle[KEY_PATH_LEN], hmac, SHA256_DIGEST_LENGTH) != 0) + return NULL; + + // Done! + return node; +} + +void u2f_register(const APDU *a) { + static U2F_REGISTER_REQ last_req; + const U2F_REGISTER_REQ *req = (U2F_REGISTER_REQ *)a->data; + + if (!config_isInitialized()) { + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + return; + } + + // Validate basic request parameters + debugLog(0, "", "u2f register"); + if (APDU_LEN(*a) != sizeof(U2F_REGISTER_REQ)) { + debugLog(0, "", "u2f register - badlen"); + send_u2f_error(U2F_SW_WRONG_LENGTH); + return; + } + + // If this request is different from last request, reset state machine + if (memcmp(&last_req, req, sizeof(last_req)) != 0) { + memcpy(&last_req, req, sizeof(last_req)); + last_req_state = INIT; + } + + // First Time request, return not present and display request dialog + if (last_req_state == INIT) { + // error: testof-user-presence is required + buttonUpdate(); // Clear button state + if (0 == memcmp(req->appId, BOGUS_APPID, U2F_APPID_SIZE)) { + layoutDialog(&bmp_icon_warning, NULL, _("OK"), NULL, + _("Another U2F device"), _("was used to register"), + _("in this application."), NULL, NULL, NULL); + } else { + const char *appname; + getReadableAppId(req->appId, &appname); + layoutU2FDialog(_("Register"), appname); + } + last_req_state = REG; + } + + // Still awaiting Keypress + if (last_req_state == REG) { + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + dialog_timeout = U2F_TIMEOUT; + return; + } + + // Buttons said yes + if (last_req_state == REG_PASS) { + uint8_t data[sizeof(U2F_REGISTER_RESP) + 2]; + U2F_REGISTER_RESP *resp = (U2F_REGISTER_RESP *)&data; + memzero(data, sizeof(data)); + + resp->registerId = U2F_REGISTER_ID; + resp->keyHandleLen = KEY_HANDLE_LEN; + // Generate keypair for this appId + const HDNode *node = + generateKeyHandle(req->appId, (uint8_t *)&resp->keyHandleCertSig); + + if (!node) { + debugLog(0, "", "getDerivedNode Fail"); + send_u2f_error(U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + ecdsa_get_public_key65(node->curve->params, node->private_key, + (uint8_t *)&resp->pubKey); + + memcpy(resp->keyHandleCertSig + resp->keyHandleLen, U2F_ATT_CERT, + sizeof(U2F_ATT_CERT)); + + uint8_t sig[64]; + U2F_REGISTER_SIG_STR sig_base; + sig_base.reserved = 0; + memcpy(sig_base.appId, req->appId, U2F_APPID_SIZE); + memcpy(sig_base.chal, req->chal, U2F_CHAL_SIZE); + memcpy(sig_base.keyHandle, &resp->keyHandleCertSig, KEY_HANDLE_LEN); + memcpy(sig_base.pubKey, &resp->pubKey, U2F_PUBKEY_LEN); + if (ecdsa_sign(&nist256p1, HASHER_SHA2, U2F_ATT_PRIV_KEY, + (uint8_t *)&sig_base, sizeof(sig_base), sig, NULL, + NULL) != 0) { + send_u2f_error(U2F_SW_WRONG_DATA); + return; + } + + // Where to write the signature in the response + uint8_t *resp_sig = + resp->keyHandleCertSig + resp->keyHandleLen + sizeof(U2F_ATT_CERT); + // Convert to der for the response + const uint8_t sig_len = ecdsa_sig_to_der(sig, resp_sig); + + // Append success bytes + memcpy(resp->keyHandleCertSig + resp->keyHandleLen + sizeof(U2F_ATT_CERT) + + sig_len, + "\x90\x00", 2); + + int l = 1 /* registerId */ + U2F_PUBKEY_LEN + 1 /* keyhandleLen */ + + resp->keyHandleLen + sizeof(U2F_ATT_CERT) + sig_len + 2; + + last_req_state = INIT; + dialog_timeout = 0; + send_u2f_msg(data, l); + return; + } + + // Didnt expect to get here + dialog_timeout = 0; +} + +void u2f_authenticate(const APDU *a) { + const U2F_AUTHENTICATE_REQ *req = (U2F_AUTHENTICATE_REQ *)a->data; + static U2F_AUTHENTICATE_REQ last_req; + + if (!config_isInitialized()) { + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + return; + } + + if (APDU_LEN(*a) < 64) { /// FIXME: decent value + debugLog(0, "", "u2f authenticate - badlen"); + send_u2f_error(U2F_SW_WRONG_LENGTH); + return; + } + + if (req->keyHandleLen != KEY_HANDLE_LEN) { + debugLog(0, "", "u2f auth - bad keyhandle len"); + send_u2f_error(U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + const HDNode *node = validateKeyHandle(req->appId, req->keyHandle); + + if (!node) { + debugLog(0, "", "u2f auth - bad keyhandle len"); + send_u2f_error(U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + if (a->p1 == U2F_AUTH_CHECK_ONLY) { + debugLog(0, "", "u2f authenticate check"); + // This is a success for a good keyhandle + // A failed check would have happened earlier + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + return; + } + + if (a->p1 != U2F_AUTH_ENFORCE) { + debugLog(0, "", "u2f authenticate unknown"); + // error:bad key handle + send_u2f_error(U2F_SW_WRONG_DATA); + return; + } + + debugLog(0, "", "u2f authenticate enforce"); + + if (memcmp(&last_req, req, sizeof(last_req)) != 0) { + memcpy(&last_req, req, sizeof(last_req)); + last_req_state = INIT; + } + + if (last_req_state == INIT) { + // error: testof-user-presence is required + buttonUpdate(); // Clear button state + const char *appname; + getReadableAppId(req->appId, &appname); + layoutU2FDialog(_("Authenticate"), appname); + last_req_state = AUTH; + } + + // Awaiting Keypress + if (last_req_state == AUTH) { + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + dialog_timeout = U2F_TIMEOUT; + return; + } + + // Buttons said yes + if (last_req_state == AUTH_PASS) { + uint8_t buf[sizeof(U2F_AUTHENTICATE_RESP) + 2]; + U2F_AUTHENTICATE_RESP *resp = (U2F_AUTHENTICATE_RESP *)&buf; + + const uint32_t ctr = config_nextU2FCounter(); + resp->flags = U2F_AUTH_FLAG_TUP; + resp->ctr[0] = ctr >> 24 & 0xff; + resp->ctr[1] = ctr >> 16 & 0xff; + resp->ctr[2] = ctr >> 8 & 0xff; + resp->ctr[3] = ctr & 0xff; + + // Build and sign response + U2F_AUTHENTICATE_SIG_STR sig_base; + uint8_t sig[64]; + memcpy(sig_base.appId, req->appId, U2F_APPID_SIZE); + sig_base.flags = resp->flags; + memcpy(sig_base.ctr, resp->ctr, 4); + memcpy(sig_base.chal, req->chal, U2F_CHAL_SIZE); + if (ecdsa_sign(&nist256p1, HASHER_SHA2, node->private_key, + (uint8_t *)&sig_base, sizeof(sig_base), sig, NULL, + NULL) != 0) { + send_u2f_error(U2F_SW_WRONG_DATA); + return; + } + + // Copy DER encoded signature into response + const uint8_t sig_len = ecdsa_sig_to_der(sig, resp->sig); + + // Append OK + memcpy(buf + sizeof(U2F_AUTHENTICATE_RESP) - U2F_MAX_EC_SIG_SIZE + sig_len, + "\x90\x00", 2); + last_req_state = INIT; + dialog_timeout = 0; + send_u2f_msg( + buf, sizeof(U2F_AUTHENTICATE_RESP) - U2F_MAX_EC_SIG_SIZE + sig_len + 2); + } +} + +void send_u2f_error(const uint16_t err) { + uint8_t data[2]; + data[0] = err >> 8 & 0xFF; + data[1] = err & 0xFF; + send_u2f_msg(data, 2); +} + +void send_u2f_msg(const uint8_t *data, const uint32_t len) { + send_u2fhid_msg(U2FHID_MSG, data, len); +} diff --git a/legacy/firmware/u2f.h b/legacy/firmware/u2f.h new file mode 100644 index 0000000000..e1e3a41975 --- /dev/null +++ b/legacy/firmware/u2f.h @@ -0,0 +1,62 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2015 Mark Bryars + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __U2F_H__ +#define __U2F_H__ + +#include +#include +#include "trezor.h" +#include "u2f/u2f_hid.h" + +#define U2F_KEY_PATH 0x80553246 + +typedef struct { + uint8_t cla, ins, p1, p2; + uint8_t lc1, lc2, lc3; + uint8_t data[]; +} APDU; + +#define APDU_LEN(A) (uint32_t)(((A).lc1 << 16) + ((A).lc2 << 8) + ((A).lc3)) + +void u2fhid_read(char tiny, const U2FHID_FRAME *buf); +void u2fhid_init_cmd(const U2FHID_FRAME *f); +void u2fhid_read_start(const U2FHID_FRAME *f); +bool u2fhid_write(uint8_t *buf); +void u2fhid_init(const U2FHID_FRAME *in); +void u2fhid_ping(const uint8_t *buf, uint32_t len); +void u2fhid_wink(const uint8_t *buf, uint32_t len); +void u2fhid_sync(const uint8_t *buf, uint32_t len); +void u2fhid_lock(const uint8_t *buf, uint32_t len); +void u2fhid_msg(const APDU *a, uint32_t len); +void queue_u2f_pkt(const U2FHID_FRAME *u2f_pkt); + +uint8_t *u2f_out_data(void); +void u2f_register(const APDU *a); +void u2f_version(const APDU *a); +void u2f_authenticate(const APDU *a); + +void send_u2f_msg(const uint8_t *data, uint32_t len); +void send_u2f_error(uint16_t err); + +void send_u2fhid_msg(const uint8_t cmd, const uint8_t *data, + const uint32_t len); +void send_u2fhid_error(uint32_t fcid, uint8_t err); + +#endif diff --git a/legacy/firmware/u2f/genkeys.sh b/legacy/firmware/u2f/genkeys.sh new file mode 100755 index 0000000000..33371d5be9 --- /dev/null +++ b/legacy/firmware/u2f/genkeys.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -e + +cat > u2f_keys.h < + +const uint8_t U2F_ATT_PRIV_KEY[] = { +EOF + +if [ \! -e trezordevkey.pem ]; then + openssl ecparam -genkey -out trezordevkey.pem -name prime256v1 +fi +openssl ec -in trezordevkey.pem -text | + perl -e '$key = "\t"; while (<>) { + if (/priv:/) { $priv = 1 } + elsif (/pub:/) { $priv = 0 } + elsif ($priv) { + while ($_ =~ s/.*?([0-9a-f]{2})//) { + $key .= "0x$1,"; + if ($num++ % 8 == 7) { $key .= "\n\t"; } + else {$key .= " ";} + } + } + } + $key =~ s/,\s*$/\n/s; + print $key;' >> u2f_keys.h +cat >> u2f_keys.h <> u2f_keys.h + +cat >> u2f_keys.h < +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// General constants + +#define U2F_EC_KEY_SIZE 32 // EC key size in bytes +#define U2F_EC_POINT_SIZE ((U2F_EC_KEY_SIZE * 2) + 1) // Size of EC point +#define U2F_MAX_KH_SIZE 128 // Max size of key handle +#define U2F_MAX_ATT_CERT_SIZE 2048 // Max size of attestation certificate +#define U2F_MAX_EC_SIG_SIZE 72 // Max size of DER coded EC signature +#define U2F_CTR_SIZE 4 // Size of counter field +#define U2F_APPID_SIZE 32 // Size of application id +#define U2F_CHAL_SIZE 32 // Size of challenge + +#define ENC_SIZE(x) ((x + 7) & 0xfff8) + +// EC (uncompressed) point + +#define U2F_POINT_UNCOMPRESSED 0x04 // Uncompressed point format + +typedef struct __attribute__((packed)) { + uint8_t pointFormat; // Point type + uint8_t x[U2F_EC_KEY_SIZE]; // X-value + uint8_t y[U2F_EC_KEY_SIZE]; // Y-value +} U2F_EC_POINT; + +// U2F native commands + +#define U2F_REGISTER 0x01 // Registration command +#define U2F_AUTHENTICATE 0x02 // Authenticate/sign command +#define U2F_VERSION 0x03 // Read version string command + +#define U2F_VENDOR_FIRST 0x40 // First vendor defined command +#define U2F_VENDOR_LAST 0xbf // Last vendor defined command + +// U2F_CMD_REGISTER command defines + +#define U2F_REGISTER_ID 0x05 // Version 2 registration identifier +#define U2F_REGISTER_HASH_ID 0x00 // Version 2 hash identintifier + +typedef struct __attribute__((packed)) { + uint8_t chal[U2F_CHAL_SIZE]; // Challenge + uint8_t appId[U2F_APPID_SIZE]; // Application id +} U2F_REGISTER_REQ; + +typedef struct __attribute__((packed)) { + uint8_t registerId; // Registration identifier (U2F_REGISTER_ID_V2) + U2F_EC_POINT pubKey; // Generated public key + uint8_t keyHandleLen; // Length of key handle + uint8_t keyHandleCertSig[ + U2F_MAX_KH_SIZE + // Key handle + U2F_MAX_ATT_CERT_SIZE + // Attestation certificate + U2F_MAX_EC_SIG_SIZE]; // Registration signature +} U2F_REGISTER_RESP; + +// U2F_CMD_AUTHENTICATE command defines + +// Authentication control byte + +#define U2F_AUTH_ENFORCE 0x03 // Enforce user presence and sign +#define U2F_AUTH_CHECK_ONLY 0x07 // Check only +#define U2F_AUTH_FLAG_TUP 0x01 // Test of user presence set + +typedef struct __attribute__((packed)) { + uint8_t chal[U2F_CHAL_SIZE]; // Challenge + uint8_t appId[U2F_APPID_SIZE]; // Application id + uint8_t keyHandleLen; // Length of key handle + uint8_t keyHandle[U2F_MAX_KH_SIZE]; // Key handle +} U2F_AUTHENTICATE_REQ; + +typedef struct __attribute__((packed)) { + uint8_t flags; // U2F_AUTH_FLAG_ values + uint8_t ctr[U2F_CTR_SIZE]; // Counter field (big-endian) + uint8_t sig[U2F_MAX_EC_SIG_SIZE]; // Signature +} U2F_AUTHENTICATE_RESP; + +// Command status responses + +#define U2F_SW_NO_ERROR 0x9000 // SW_NO_ERROR +#define U2F_SW_WRONG_LENGTH 0x6700 // SW_WRONG_LENGTH +#define U2F_SW_WRONG_DATA 0x6A80 // SW_WRONG_DATA +#define U2F_SW_CONDITIONS_NOT_SATISFIED 0x6985 // SW_CONDITIONS_NOT_SATISFIED +#define U2F_SW_COMMAND_NOT_ALLOWED 0x6986 // SW_COMMAND_NOT_ALLOWED +#define U2F_SW_INS_NOT_SUPPORTED 0x6D00 // SW_INS_NOT_SUPPORTED +#define U2F_SW_CLA_NOT_SUPPORTED 0x6E00 // SW_CLA_NOT_SUPPORTED + +#ifdef __cplusplus +} +#endif + +#endif // __U2F_H_INCLUDED__ diff --git a/legacy/firmware/u2f/u2f_hid.h b/legacy/firmware/u2f/u2f_hid.h new file mode 100644 index 0000000000..f29059da21 --- /dev/null +++ b/legacy/firmware/u2f/u2f_hid.h @@ -0,0 +1,132 @@ +/** + * Copyright FIDO Alliance, 2017 + * + * Licensed under CC-BY: + * https://creativecommons.org/licenses/by/4.0/legalcode + * + * Editor: Jakob Ehrensvard, Yubico, jakob@yubico.com + */ + +#ifndef __U2FHID_H_INCLUDED__ +#define __U2FHID_H_INCLUDED__ + +#ifdef _MSC_VER // Windows +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long int uint64_t; +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Size of HID reports + +#define HID_RPT_SIZE 64 // Default size of raw HID report + +// Frame layout - command- and continuation frames + +#define CID_BROADCAST 0xffffffff // Broadcast channel id + +#define TYPE_MASK 0x80 // Frame type mask +#define TYPE_INIT 0x80 // Initial frame identifier +#define TYPE_CONT 0x00 // Continuation frame identifier + +typedef struct __attribute__((packed)) { + uint32_t cid; // Channel identifier + union __attribute__((packed)) { + uint8_t type; // Frame type - b7 defines type + struct __attribute__((packed)) { + uint8_t cmd; // Command - b7 set + uint8_t bcnth; // Message byte count - high part + uint8_t bcntl; // Message byte count - low part + uint8_t data[HID_RPT_SIZE - 7]; // Data payload + } init; + struct __attribute__((packed)) { + uint8_t seq; // Sequence number - b7 cleared + uint8_t data[HID_RPT_SIZE - 5]; // Data payload + } cont; + }; +} U2FHID_FRAME; + +#define FRAME_TYPE(f) ((f).type & TYPE_MASK) +#define FRAME_CMD(f) ((f).init.cmd & ~TYPE_MASK) +#define MSG_LEN(f) ((f).init.bcnth*256 + (f).init.bcntl) +#define FRAME_SEQ(f) ((f).cont.seq & ~TYPE_MASK) + +// HID usage- and usage-page definitions + +#define FIDO_USAGE_PAGE 0xf1d0 // FIDO alliance HID usage page +#define FIDO_USAGE_U2FHID 0x01 // U2FHID usage for top-level collection +#define FIDO_USAGE_DATA_IN 0x20 // Raw IN data report +#define FIDO_USAGE_DATA_OUT 0x21 // Raw OUT data report + +// General constants + +#define U2FHID_IF_VERSION 2 // Current interface implementation version +#define U2FHID_TRANS_TIMEOUT 3000 // Default message timeout in ms + +// U2FHID native commands + +#define U2FHID_PING (TYPE_INIT | 0x01) // Echo data through local processor only +#define U2FHID_MSG (TYPE_INIT | 0x03) // Send U2F message frame +#define U2FHID_LOCK (TYPE_INIT | 0x04) // Send lock channel command +#define U2FHID_INIT (TYPE_INIT | 0x06) // Channel initialization +#define U2FHID_WINK (TYPE_INIT | 0x08) // Send device identification wink +#define U2FHID_SYNC (TYPE_INIT | 0x3c) // Protocol resync command +#define U2FHID_ERROR (TYPE_INIT | 0x3f) // Error response + +#define U2FHID_VENDOR_FIRST (TYPE_INIT | 0x40) // First vendor defined command +#define U2FHID_VENDOR_LAST (TYPE_INIT | 0x7f) // Last vendor defined command + +// U2FHID_INIT command defines + +#define INIT_NONCE_SIZE 8 // Size of channel initialization challenge +#define CAPFLAG_WINK 0x01 // Device supports WINK command +#define CAPFLAG_LOCK 0x02 // Device supports LOCK command + +typedef struct __attribute__((packed)) { + uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce +} U2FHID_INIT_REQ; + +typedef struct __attribute__((packed)) { + uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce + uint32_t cid; // Channel identifier + uint8_t versionInterface; // Interface version + uint8_t versionMajor; // Major version number + uint8_t versionMinor; // Minor version number + uint8_t versionBuild; // Build version number + uint8_t capFlags; // Capabilities flags +} U2FHID_INIT_RESP; + +// U2FHID_SYNC command defines + +typedef struct __attribute__((packed)) { + uint8_t nonce; // Client application nonce +} U2FHID_SYNC_REQ; + +typedef struct __attribute__((packed)) { + uint8_t nonce; // Client application nonce +} U2FHID_SYNC_RESP; + +// Low-level error codes. Return as negatives. + +#define ERR_NONE 0x00 // No error +#define ERR_INVALID_CMD 0x01 // Invalid command +#define ERR_INVALID_PAR 0x02 // Invalid parameter +#define ERR_INVALID_LEN 0x03 // Invalid message length +#define ERR_INVALID_SEQ 0x04 // Invalid message sequencing +#define ERR_MSG_TIMEOUT 0x05 // Message has timed out +#define ERR_CHANNEL_BUSY 0x06 // Channel busy +#define ERR_LOCK_REQUIRED 0x0a // Command requires channel lock +#define ERR_INVALID_CID 0x0b // Message on CID 0 +#define ERR_OTHER 0x7f // Other unspecified error + +#ifdef __cplusplus +} +#endif + +#endif // __U2FHID_H_INCLUDED__ diff --git a/legacy/firmware/u2f/u2f_keys.h b/legacy/firmware/u2f/u2f_keys.h new file mode 100644 index 0000000000..578b435c08 --- /dev/null +++ b/legacy/firmware/u2f/u2f_keys.h @@ -0,0 +1,38 @@ +#ifndef __U2F_KEYS_H_INCLUDED__ +#define __U2F_KEYS_H_INCLUDED__ + +#include + +const uint8_t U2F_ATT_PRIV_KEY[] = { + 0x71, 0x26, 0xac, 0x2b, 0xf6, 0x44, 0xdc, 0x61, 0x86, 0xad, 0x83, + 0xef, 0x1f, 0xcd, 0xf1, 0x2a, 0x57, 0xb5, 0xcf, 0xa2, 0x00, 0x0b, + 0x8a, 0xd0, 0x27, 0xe9, 0x56, 0xe8, 0x54, 0xc5, 0x0a, 0x8b}; + +const uint8_t U2F_ATT_CERT[] = { + 0x30, 0x82, 0x01, 0x18, 0x30, 0x81, 0xc0, 0x02, 0x09, 0x00, 0xb1, 0xd9, + 0x8f, 0x42, 0x64, 0x72, 0xd3, 0x2c, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x15, 0x31, 0x13, 0x30, 0x11, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0a, 0x54, 0x72, 0x65, 0x7a, 0x6f, + 0x72, 0x20, 0x55, 0x32, 0x46, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x30, + 0x34, 0x32, 0x39, 0x31, 0x33, 0x33, 0x31, 0x35, 0x33, 0x5a, 0x17, 0x0d, + 0x32, 0x36, 0x30, 0x34, 0x32, 0x37, 0x31, 0x33, 0x33, 0x31, 0x35, 0x33, + 0x5a, 0x30, 0x15, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x0c, 0x0a, 0x54, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x20, 0x55, 0x32, 0x46, + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, 0x04, 0xd9, 0x18, 0xbd, 0xfa, 0x8a, 0x54, 0xac, 0x92, 0xe9, + 0x0d, 0xa9, 0x1f, 0xca, 0x7a, 0xa2, 0x64, 0x54, 0xc0, 0xd1, 0x73, 0x36, + 0x31, 0x4d, 0xde, 0x83, 0xa5, 0x4b, 0x86, 0xb5, 0xdf, 0x4e, 0xf0, 0x52, + 0x65, 0x9a, 0x1d, 0x6f, 0xfc, 0xb7, 0x46, 0x7f, 0x1a, 0xcd, 0xdb, 0x8a, + 0x33, 0x08, 0x0b, 0x5e, 0xed, 0x91, 0x89, 0x13, 0xf4, 0x43, 0xa5, 0x26, + 0x1b, 0xc7, 0x7b, 0x68, 0x60, 0x6f, 0xc1, 0x30, 0x0a, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x47, 0x00, 0x30, 0x44, + 0x02, 0x20, 0x24, 0x1e, 0x81, 0xff, 0xd2, 0xe5, 0xe6, 0x15, 0x36, 0x94, + 0xc3, 0x55, 0x2e, 0x8f, 0xeb, 0xd7, 0x1e, 0x89, 0x35, 0x92, 0x1c, 0xb4, + 0x83, 0x41, 0x43, 0x71, 0x1c, 0x76, 0xea, 0xee, 0xf3, 0x95, 0x02, 0x20, + 0x5f, 0x80, 0xeb, 0x10, 0xf2, 0x5c, 0xcc, 0x39, 0x8b, 0x3c, 0xa8, 0xa9, + 0xad, 0xa4, 0x02, 0x7f, 0x93, 0x13, 0x20, 0x77, 0xb7, 0xab, 0xce, 0x77, + 0x46, 0x5a, 0x27, 0xf5, 0x3d, 0x33, 0xa1, 0x1d, +}; + +#endif // __U2F_KEYS_H_INCLUDED__ diff --git a/legacy/firmware/u2f_knownapps.h b/legacy/firmware/u2f_knownapps.h new file mode 100644 index 0000000000..f01a03510d --- /dev/null +++ b/legacy/firmware/u2f_knownapps.h @@ -0,0 +1,150 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2016 Jochen Hoenicke + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __U2F_KNOWNAPPS_H_INCLUDED__ +#define __U2F_KNOWNAPPS_H_INCLUDED__ + +#include +#include "u2f/u2f.h" + +typedef struct { + const uint8_t appid[U2F_APPID_SIZE]; + const char *appname; +} U2FWellKnown; + +// contents generated via script in +// trezor-common/defs/webauthn/gen.py +// do not edit manually + +// clang-format off +static const U2FWellKnown u2f_well_known[] = { + { + // U2F: https://bitbucket.org + { 0x12, 0x74, 0x3b, 0x92, 0x12, 0x97, 0xb7, 0x7f, 0x11, 0x35, 0xe4, 0x1f, 0xde, 0xdd, 0x4a, 0x84, 0x6a, 0xfe, 0x82, 0xe1, 0xf3, 0x69, 0x32, 0xa9, 0x91, 0x2f, 0x3b, 0x0d, 0x8d, 0xfb, 0x7d, 0x0e }, + "Bitbucket" + }, + { + // U2F: https://www.bitfinex.com + { 0x30, 0x2f, 0xd5, 0xb4, 0x49, 0x2a, 0x07, 0xb9, 0xfe, 0xbb, 0x30, 0xe7, 0x32, 0x69, 0xec, 0xa5, 0x01, 0x20, 0x5c, 0xcf, 0xe0, 0xc2, 0x0b, 0xf7, 0xb4, 0x72, 0xfa, 0x2d, 0x31, 0xe2, 0x1e, 0x63 }, + "Bitfinex" + }, + { + // U2F: https://vault.bitwarden.com/app-id.json + { 0xa3, 0x4d, 0x30, 0x9f, 0xfa, 0x28, 0xc1, 0x24, 0x14, 0xb8, 0xba, 0x6c, 0x07, 0xee, 0x1e, 0xfa, 0xe1, 0xa8, 0x5e, 0x8a, 0x04, 0x61, 0x48, 0x59, 0xa6, 0x7c, 0x04, 0x93, 0xb6, 0x95, 0x61, 0x90 }, + "Bitwarden" + }, + { + // U2F: https://www.dashlane.com + { 0x68, 0x20, 0x19, 0x15, 0xd7, 0x4c, 0xb4, 0x2a, 0xf5, 0xb3, 0xcc, 0x5c, 0x95, 0xb9, 0x55, 0x3e, 0x3e, 0x3a, 0x83, 0xb4, 0xd2, 0xa9, 0x3b, 0x45, 0xfb, 0xad, 0xaa, 0x84, 0x69, 0xff, 0x8e, 0x6e }, + "Dashlane" + }, + { + // U2F: https://www.dropbox.com/u2f-app-id.json + { 0xc5, 0x0f, 0x8a, 0x7b, 0x70, 0x8e, 0x92, 0xf8, 0x2e, 0x7a, 0x50, 0xe2, 0xbd, 0xc5, 0x5d, 0x8f, 0xd9, 0x1a, 0x22, 0xfe, 0x6b, 0x29, 0xc0, 0xcd, 0xf7, 0x80, 0x55, 0x30, 0x84, 0x2a, 0xf5, 0x81 }, + "Dropbox" + }, + { + // WebAuthn: www.dropbox.com + { 0x82, 0xf4, 0xa8, 0xc9, 0x5f, 0xec, 0x94, 0xb2, 0x6b, 0xaf, 0x9e, 0x37, 0x25, 0x0e, 0x95, 0x63, 0xd9, 0xa3, 0x66, 0xc7, 0xbe, 0x26, 0x1c, 0xa4, 0xdd, 0x01, 0x01, 0xf4, 0xd5, 0xef, 0xcb, 0x83 }, + "Dropbox" + }, + { + // U2F: https://api-9dcf9b83.duosecurity.com + { 0xf3, 0xe2, 0x04, 0x2f, 0x94, 0x60, 0x7d, 0xa0, 0xa9, 0xc1, 0xf3, 0xb9, 0x5e, 0x0d, 0x2f, 0x2b, 0xb2, 0xe0, 0x69, 0xc5, 0xbb, 0x4f, 0xa7, 0x64, 0xaf, 0xfa, 0x64, 0x7d, 0x84, 0x7b, 0x7e, 0xd6 }, + "Duo" + }, + { + // U2F: https://www.fastmail.com + { 0x69, 0x66, 0xab, 0xe3, 0x67, 0x4e, 0xa2, 0xf5, 0x30, 0x79, 0xeb, 0x71, 0x01, 0x97, 0x84, 0x8c, 0x9b, 0xe6, 0xf3, 0x63, 0x99, 0x2f, 0xd0, 0x29, 0xe9, 0x89, 0x84, 0x47, 0xcb, 0x9f, 0x00, 0x84 }, + "FastMail" + }, + { + // U2F: https://id.fedoraproject.org/u2f-origins.json + { 0x9d, 0x61, 0x44, 0x2f, 0x5c, 0xe1, 0x33, 0xbd, 0x46, 0x54, 0x4f, 0xc4, 0x2f, 0x0a, 0x6d, 0x54, 0xc0, 0xde, 0xb8, 0x88, 0x40, 0xca, 0xc2, 0xb6, 0xae, 0xfa, 0x65, 0x14, 0xf8, 0x93, 0x49, 0xe9 }, + "Fedora" + }, + { + // U2F: https://account.gandi.net/api/u2f/trusted_facets.json + { 0xa4, 0xe2, 0x2d, 0xca, 0xfe, 0xa7, 0xe9, 0x0e, 0x12, 0x89, 0x50, 0x11, 0x39, 0x89, 0xfc, 0x45, 0x97, 0x8d, 0xc9, 0xfb, 0x87, 0x76, 0x75, 0x60, 0x51, 0x6c, 0x1c, 0x69, 0xdf, 0xdf, 0xd1, 0x96 }, + "Gandi" + }, + { + // U2F: https://github.com/u2f/trusted_facets + { 0x70, 0x61, 0x7d, 0xfe, 0xd0, 0x65, 0x86, 0x3a, 0xf4, 0x7c, 0x15, 0x55, 0x6c, 0x91, 0x79, 0x88, 0x80, 0x82, 0x8c, 0xc4, 0x07, 0xfd, 0xf7, 0x0a, 0xe8, 0x50, 0x11, 0x56, 0x94, 0x65, 0xa0, 0x75 }, + "GitHub" + }, + { + // U2F: https://gitlab.com + { 0xe7, 0xbe, 0x96, 0xa5, 0x1b, 0xd0, 0x19, 0x2a, 0x72, 0x84, 0x0d, 0x2e, 0x59, 0x09, 0xf7, 0x2b, 0xa8, 0x2a, 0x2f, 0xe9, 0x3f, 0xaa, 0x62, 0x4f, 0x03, 0x39, 0x6b, 0x30, 0xe4, 0x94, 0xc8, 0x04 }, + "GitLab" + }, + { + // U2F: https://www.gstatic.com/securitykey/origins.json + { 0xa5, 0x46, 0x72, 0xb2, 0x22, 0xc4, 0xcf, 0x95, 0xe1, 0x51, 0xed, 0x8d, 0x4d, 0x3c, 0x76, 0x7a, 0x6c, 0xc3, 0x49, 0x43, 0x59, 0x43, 0x79, 0x4e, 0x88, 0x4f, 0x3d, 0x02, 0x3a, 0x82, 0x29, 0xfd }, + "Google" + }, + { + // U2F: https://keepersecurity.com + { 0x53, 0xa1, 0x5b, 0xa4, 0x2a, 0x7c, 0x03, 0x25, 0xb8, 0xdb, 0xee, 0x28, 0x96, 0x34, 0xa4, 0x8f, 0x58, 0xae, 0xa3, 0x24, 0x66, 0x45, 0xd5, 0xff, 0x41, 0x8f, 0x9b, 0xb8, 0x81, 0x98, 0x85, 0xa9 }, + "Keeper" + }, + { + // U2F: https://lastpass.com + { 0xd7, 0x55, 0xc5, 0x27, 0xa8, 0x6b, 0xf7, 0x84, 0x45, 0xc2, 0x82, 0xe7, 0x13, 0xdc, 0xb8, 0x6d, 0x46, 0xff, 0x8b, 0x3c, 0xaf, 0xcf, 0xb7, 0x3b, 0x2e, 0x8c, 0xbe, 0x6c, 0x08, 0x84, 0xcb, 0x24 }, + "LastPass" + }, + { + // U2F: https://slushpool.com/static/security/u2f.json + { 0x08, 0xb2, 0xa3, 0xd4, 0x19, 0x39, 0xaa, 0x31, 0x66, 0x84, 0x93, 0xcb, 0x36, 0xcd, 0xcc, 0x4f, 0x16, 0xc4, 0xd9, 0xb4, 0xc8, 0x23, 0x8b, 0x73, 0xc2, 0xf6, 0x72, 0xc0, 0x33, 0x00, 0x71, 0x97 }, + "Slush Pool" + }, + { + // U2F: https://dashboard.stripe.com + { 0x2a, 0xc6, 0xad, 0x09, 0xa6, 0xd0, 0x77, 0x2c, 0x44, 0xda, 0x73, 0xa6, 0x07, 0x2f, 0x9d, 0x24, 0x0f, 0xc6, 0x85, 0x4a, 0x70, 0xd7, 0x9c, 0x10, 0x24, 0xff, 0x7c, 0x75, 0x59, 0x59, 0x32, 0x92 }, + "Stripe" + }, + { + // U2F: https://u2f.bin.coffee + { 0x1b, 0x3c, 0x16, 0xdd, 0x2f, 0x7c, 0x46, 0xe2, 0xb4, 0xc2, 0x89, 0xdc, 0x16, 0x74, 0x6b, 0xcc, 0x60, 0xdf, 0xcf, 0x0f, 0xb8, 0x18, 0xe1, 0x32, 0x15, 0x52, 0x6e, 0x14, 0x08, 0xe7, 0xf4, 0x68 }, + "u2f.bin.coffee" + }, + { + // WebAuthn: webauthn.bin.coffee + { 0xa6, 0x42, 0xd2, 0x1b, 0x7c, 0x6d, 0x55, 0xe1, 0xce, 0x23, 0xc5, 0x39, 0x98, 0x28, 0xd2, 0xc7, 0x49, 0xbf, 0x6a, 0x6e, 0xf2, 0xfe, 0x03, 0xcc, 0x9e, 0x10, 0xcd, 0xf4, 0xed, 0x53, 0x08, 0x8b }, + "webauthn.bin.coffee" + }, + { + // WebAuthn: webauthn.io + { 0x74, 0xa6, 0xea, 0x92, 0x13, 0xc9, 0x9c, 0x2f, 0x74, 0xb2, 0x24, 0x92, 0xb3, 0x20, 0xcf, 0x40, 0x26, 0x2a, 0x94, 0xc1, 0xa9, 0x50, 0xa0, 0x39, 0x7f, 0x29, 0x25, 0x0b, 0x60, 0x84, 0x1e, 0xf0 }, + "WebAuthn.io" + }, + { + // WebAuthn: webauthn.me + { 0xf9, 0x5b, 0xc7, 0x38, 0x28, 0xee, 0x21, 0x0f, 0x9f, 0xd3, 0xbb, 0xe7, 0x2d, 0x97, 0x90, 0x80, 0x13, 0xb0, 0xa3, 0x75, 0x9e, 0x9a, 0xea, 0x3d, 0x0a, 0xe3, 0x18, 0x76, 0x6c, 0xd2, 0xe1, 0xad }, + "WebAuthn.me" + }, + { + // WebAuthn: demo.yubico.com + { 0xc4, 0x6c, 0xef, 0x82, 0xad, 0x1b, 0x54, 0x64, 0x77, 0x59, 0x1d, 0x00, 0x8b, 0x08, 0x75, 0x9e, 0xc3, 0xe6, 0xd2, 0xec, 0xb4, 0xf3, 0x94, 0x74, 0xbf, 0xea, 0x69, 0x69, 0x92, 0x5d, 0x03, 0xb7 }, + "demo.yubico.com" + }, +}; +// clang-format on + +#endif // U2F_KNOWNAPPS_INCLUDED diff --git a/legacy/firmware/udp.c b/legacy/firmware/udp.c new file mode 100644 index 0000000000..e9161261de --- /dev/null +++ b/legacy/firmware/udp.c @@ -0,0 +1,77 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2017 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "usb.h" + +#include "debug.h" +#include "messages.h" +#include "timer.h" + +static volatile char tiny = 0; + +void usbInit(void) { emulatorSocketInit(); } + +#if DEBUG_LINK +#define _ISDBG (((iface == 1) ? 'd' : 'n')) +#else +#define _ISDBG ('n') +#endif + +void usbPoll(void) { + emulatorPoll(); + + static uint8_t buffer[64]; + + int iface = 0; + if (emulatorSocketRead(&iface, buffer, sizeof(buffer)) > 0) { + if (!tiny) { + msg_read_common(_ISDBG, buffer, sizeof(buffer)); + } else { + msg_read_tiny(buffer, sizeof(buffer)); + } + } + + const uint8_t *data = msg_out_data(); + if (data != NULL) { + emulatorSocketWrite(0, data, 64); + } + +#if DEBUG_LINK + data = msg_debug_out_data(); + if (data != NULL) { + emulatorSocketWrite(1, data, 64); + } +#endif +} + +char usbTiny(char set) { + char old = tiny; + tiny = set; + return old; +} + +void usbSleep(uint32_t millis) { + uint32_t start = timer_ms(); + + while ((timer_ms() - start) < millis) { + usbPoll(); + } +} diff --git a/legacy/firmware/usb.c b/legacy/firmware/usb.c new file mode 100644 index 0000000000..8d80dbed92 --- /dev/null +++ b/legacy/firmware/usb.c @@ -0,0 +1,423 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include + +#include "config.h" +#include "debug.h" +#include "messages.h" +#include "timer.h" +#include "trezor.h" +#include "u2f.h" +#include "usb.h" +#include "util.h" + +#include "usb21_standard.h" +#include "webusb.h" +#include "winusb.h" + +#define USB_INTERFACE_INDEX_MAIN 0 +#if DEBUG_LINK +#define USB_INTERFACE_INDEX_DEBUG 1 +#define USB_INTERFACE_INDEX_U2F 2 +#define USB_INTERFACE_COUNT 3 +#else +#define USB_INTERFACE_INDEX_U2F 1 +#define USB_INTERFACE_COUNT 2 +#endif + +#define ENDPOINT_ADDRESS_MAIN_IN (0x81) +#define ENDPOINT_ADDRESS_MAIN_OUT (0x01) +#if DEBUG_LINK +#define ENDPOINT_ADDRESS_DEBUG_IN (0x82) +#define ENDPOINT_ADDRESS_DEBUG_OUT (0x02) +#endif +#define ENDPOINT_ADDRESS_U2F_IN (0x83) +#define ENDPOINT_ADDRESS_U2F_OUT (0x03) + +#define USB_STRINGS \ + X(MANUFACTURER, "SatoshiLabs") \ + X(PRODUCT, "TREZOR") \ + X(SERIAL_NUMBER, config_uuid_str) \ + X(INTERFACE_MAIN, "TREZOR Interface") \ + X(INTERFACE_DEBUG, "TREZOR Debug Link Interface") \ + X(INTERFACE_U2F, "TREZOR U2F Interface") + +#define X(name, value) USB_STRING_##name, +enum { + USB_STRING_LANGID_CODES, // LANGID code array + USB_STRINGS +}; +#undef X + +#define X(name, value) value, +static const char *usb_strings[] = {USB_STRINGS}; +#undef X + +static const struct usb_device_descriptor dev_descr = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0210, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x1209, + .idProduct = 0x53c1, + .bcdDevice = 0x0100, + .iManufacturer = USB_STRING_MANUFACTURER, + .iProduct = USB_STRING_PRODUCT, + .iSerialNumber = USB_STRING_SERIAL_NUMBER, + .bNumConfigurations = 1, +}; + +static const uint8_t hid_report_descriptor_u2f[] = { + 0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance) + 0x09, 0x01, // USAGE (U2F HID Authenticator Device) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x20, // USAGE (Input Report Data) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x40, // REPORT_COUNT (64) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x09, 0x21, // USAGE (Output Report Data) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x40, // REPORT_COUNT (64) + 0x91, 0x02, // OUTPUT (Data,Var,Abs) + 0xc0 // END_COLLECTION +}; + +static const struct { + struct usb_hid_descriptor hid_descriptor_u2f; + struct { + uint8_t bReportDescriptorType; + uint16_t wDescriptorLength; + } __attribute__((packed)) hid_report_u2f; +} __attribute__((packed)) +hid_function_u2f = {.hid_descriptor_u2f = + { + .bLength = sizeof(hid_function_u2f), + .bDescriptorType = USB_DT_HID, + .bcdHID = 0x0111, + .bCountryCode = 0, + .bNumDescriptors = 1, + }, + .hid_report_u2f = { + .bReportDescriptorType = USB_DT_REPORT, + .wDescriptorLength = sizeof(hid_report_descriptor_u2f), + }}; + +static const struct usb_endpoint_descriptor hid_endpoints_u2f[2] = { + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_U2F_IN, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }, + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_U2F_OUT, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }}; + +static const struct usb_interface_descriptor hid_iface_u2f[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = USB_INTERFACE_INDEX_U2F, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = USB_STRING_INTERFACE_U2F, + .endpoint = hid_endpoints_u2f, + .extra = &hid_function_u2f, + .extralen = sizeof(hid_function_u2f), +}}; + +#if DEBUG_LINK +static const struct usb_endpoint_descriptor webusb_endpoints_debug[2] = { + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_DEBUG_IN, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }, + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_DEBUG_OUT, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }}; + +static const struct usb_interface_descriptor webusb_iface_debug[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = USB_INTERFACE_INDEX_DEBUG, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = USB_STRING_INTERFACE_DEBUG, + .endpoint = webusb_endpoints_debug, + .extra = NULL, + .extralen = 0, +}}; + +#endif + +static const struct usb_endpoint_descriptor webusb_endpoints_main[2] = { + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_MAIN_IN, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }, + { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_MAIN_OUT, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, + }}; + +static const struct usb_interface_descriptor webusb_iface_main[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = USB_INTERFACE_INDEX_MAIN, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = USB_STRING_INTERFACE_MAIN, + .endpoint = webusb_endpoints_main, + .extra = NULL, + .extralen = 0, +}}; + +// Windows are strict about interfaces appearing +// in correct order +static const struct usb_interface ifaces[] = { + { + .num_altsetting = 1, + .altsetting = webusb_iface_main, +#if DEBUG_LINK + }, + { + .num_altsetting = 1, + .altsetting = webusb_iface_debug, +#endif + }, + { + .num_altsetting = 1, + .altsetting = hid_iface_u2f, + }}; + +static const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = USB_INTERFACE_COUNT, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x80, + .bMaxPower = 0x32, + .interface = ifaces, +}; + +static enum usbd_request_return_codes hid_control_request( + usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + usbd_control_complete_callback *complete) { + (void)complete; + (void)dev; + + wait_random(); + + if ((req->bmRequestType != 0x81) || + (req->bRequest != USB_REQ_GET_DESCRIPTOR) || (req->wValue != 0x2200)) + return 0; + + debugLog(0, "", "hid_control_request u2f"); + *buf = (uint8_t *)hid_report_descriptor_u2f; + *len = MIN_8bits(*len, sizeof(hid_report_descriptor_u2f)); + return 1; +} + +static volatile char tiny = 0; + +static void main_rx_callback(usbd_device *dev, uint8_t ep) { + (void)ep; + static CONFIDENTIAL uint8_t buf[64] __attribute__((aligned(4))); + if (usbd_ep_read_packet(dev, ENDPOINT_ADDRESS_MAIN_OUT, buf, 64) != 64) + return; + debugLog(0, "", "main_rx_callback"); + if (!tiny) { + msg_read(buf, 64); + } else { + msg_read_tiny(buf, 64); + } +} + +static void u2f_rx_callback(usbd_device *dev, uint8_t ep) { + (void)ep; + static CONFIDENTIAL uint8_t buf[64] __attribute__((aligned(4))); + + debugLog(0, "", "u2f_rx_callback"); + if (usbd_ep_read_packet(dev, ENDPOINT_ADDRESS_U2F_OUT, buf, 64) != 64) return; + u2fhid_read(tiny, (const U2FHID_FRAME *)(void *)buf); +} + +#if DEBUG_LINK +static void debug_rx_callback(usbd_device *dev, uint8_t ep) { + (void)ep; + static uint8_t buf[64] __attribute__((aligned(4))); + if (usbd_ep_read_packet(dev, ENDPOINT_ADDRESS_DEBUG_OUT, buf, 64) != 64) + return; + debugLog(0, "", "debug_rx_callback"); + if (!tiny) { + msg_debug_read(buf, 64); + } else { + msg_read_tiny(buf, 64); + } +} +#endif + +static void set_config(usbd_device *dev, uint16_t wValue) { + (void)wValue; + + usbd_ep_setup(dev, ENDPOINT_ADDRESS_MAIN_IN, USB_ENDPOINT_ATTR_INTERRUPT, 64, + 0); + usbd_ep_setup(dev, ENDPOINT_ADDRESS_MAIN_OUT, USB_ENDPOINT_ATTR_INTERRUPT, 64, + main_rx_callback); + usbd_ep_setup(dev, ENDPOINT_ADDRESS_U2F_IN, USB_ENDPOINT_ATTR_INTERRUPT, 64, + 0); + usbd_ep_setup(dev, ENDPOINT_ADDRESS_U2F_OUT, USB_ENDPOINT_ATTR_INTERRUPT, 64, + u2f_rx_callback); +#if DEBUG_LINK + usbd_ep_setup(dev, ENDPOINT_ADDRESS_DEBUG_IN, USB_ENDPOINT_ATTR_INTERRUPT, 64, + 0); + usbd_ep_setup(dev, ENDPOINT_ADDRESS_DEBUG_OUT, USB_ENDPOINT_ATTR_INTERRUPT, + 64, debug_rx_callback); +#endif + + usbd_register_control_callback( + dev, USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, hid_control_request); +} + +static usbd_device *usbd_dev = NULL; +static uint8_t usbd_control_buffer[256] __attribute__((aligned(2))); + +static const struct usb_device_capability_descriptor *capabilities[] = { + (const struct usb_device_capability_descriptor + *)&webusb_platform_capability_descriptor_no_landing_page, +}; + +static const struct usb_bos_descriptor bos_descriptor = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + .bNumDeviceCaps = sizeof(capabilities) / sizeof(capabilities[0]), + .capabilities = capabilities}; + +void usbInit(void) { + usbd_dev = usbd_init(&otgfs_usb_driver, &dev_descr, &config, usb_strings, + sizeof(usb_strings) / sizeof(*usb_strings), + usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(usbd_dev, set_config); + usb21_setup(usbd_dev, &bos_descriptor); + static const char *origin_url = "trezor.io/start"; + webusb_setup(usbd_dev, origin_url); + // Debug link interface does not have WinUSB set; + // if you really need debug link on windows, edit the descriptor in winusb.c + winusb_setup(usbd_dev, USB_INTERFACE_INDEX_MAIN); +} + +void usbPoll(void) { + if (usbd_dev == NULL) { + return; + } + + static const uint8_t *data; + // poll read buffer + usbd_poll(usbd_dev); + // write pending data + data = msg_out_data(); + if (data) { + while (usbd_ep_write_packet(usbd_dev, ENDPOINT_ADDRESS_MAIN_IN, data, 64) != + 64) { + } + } + data = u2f_out_data(); + if (data) { + while (usbd_ep_write_packet(usbd_dev, ENDPOINT_ADDRESS_U2F_IN, data, 64) != + 64) { + } + } +#if DEBUG_LINK + // write pending debug data + data = msg_debug_out_data(); + if (data) { + while (usbd_ep_write_packet(usbd_dev, ENDPOINT_ADDRESS_DEBUG_IN, data, + 64) != 64) { + } + } +#endif +} + +void usbReconnect(void) { + if (usbd_dev != NULL) { + usbd_disconnect(usbd_dev, 1); + delay(1000); + usbd_disconnect(usbd_dev, 0); + } +} + +char usbTiny(char set) { + char old = tiny; + tiny = set; + return old; +} + +void usbSleep(uint32_t millis) { + uint32_t start = timer_ms(); + + while ((timer_ms() - start) < millis) { + if (usbd_dev != NULL) { + usbd_poll(usbd_dev); + } + } +} diff --git a/legacy/firmware/usb.h b/legacy/firmware/usb.h new file mode 100644 index 0000000000..46426777f6 --- /dev/null +++ b/legacy/firmware/usb.h @@ -0,0 +1,29 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __USB_H__ +#define __USB_H__ + +void usbInit(void); +void usbPoll(void); +void usbReconnect(void); +char usbTiny(char set); +void usbSleep(uint32_t millis); + +#endif diff --git a/legacy/firmware/version.h b/legacy/firmware/version.h new file mode 100644 index 0000000000..1202e1b44f --- /dev/null +++ b/legacy/firmware/version.h @@ -0,0 +1,7 @@ +#define VERSION_MAJOR 1 +#define VERSION_MINOR 8 +#define VERSION_PATCH 0 + +#define FIX_VERSION_MAJOR 1 +#define FIX_VERSION_MINOR 8 +#define FIX_VERSION_PATCH 0 diff --git a/legacy/flash.c b/legacy/flash.c new file mode 100644 index 0000000000..fc5bbafa29 --- /dev/null +++ b/legacy/flash.c @@ -0,0 +1,130 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "common.h" +#include "flash.h" +#include "memory.h" +#include "supervise.h" + +static const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1] = { + [0] = 0x08000000, // - 0x08003FFF | 16 KiB + [1] = 0x08004000, // - 0x08007FFF | 16 KiB + [2] = 0x08008000, // - 0x0800BFFF | 16 KiB + [3] = 0x0800C000, // - 0x0800FFFF | 16 KiB + [4] = 0x08010000, // - 0x0801FFFF | 64 KiB + [5] = 0x08020000, // - 0x0803FFFF | 128 KiB + [6] = 0x08040000, // - 0x0805FFFF | 128 KiB + [7] = 0x08060000, // - 0x0807FFFF | 128 KiB + [8] = 0x08080000, // - 0x0809FFFF | 128 KiB + [9] = 0x080A0000, // - 0x080BFFFF | 128 KiB + [10] = 0x080C0000, // - 0x080DFFFF | 128 KiB + [11] = 0x080E0000, // - 0x080FFFFF | 128 KiB + [12] = 0x08100000, // last element - not a valid sector +}; + +static secbool flash_check_success(uint32_t status) { + return (status & (FLASH_SR_PGAERR | FLASH_SR_PGPERR | FLASH_SR_PGSERR | + FLASH_SR_WRPERR)) + ? secfalse + : sectrue; +} + +void flash_init(void) {} + +secbool flash_unlock_write(void) { + svc_flash_unlock(); + return sectrue; +} + +secbool flash_lock_write(void) { return flash_check_success(svc_flash_lock()); } + +const void *flash_get_address(uint8_t sector, uint32_t offset, uint32_t size) { + if (sector >= FLASH_SECTOR_COUNT) { + return NULL; + } + const uint32_t addr = FLASH_SECTOR_TABLE[sector] + offset; + const uint32_t next = FLASH_SECTOR_TABLE[sector + 1]; + if (addr + size > next) { + return NULL; + } + return (const void *)FLASH_PTR(addr); +} + +secbool flash_erase(uint8_t sector) { + ensure(flash_unlock_write(), NULL); + svc_flash_erase_sector(sector); + ensure(flash_lock_write(), NULL); + + // Check whether the sector was really deleted (contains only 0xFF). + const uint32_t addr_start = FLASH_SECTOR_TABLE[sector], + addr_end = FLASH_SECTOR_TABLE[sector + 1]; + for (uint32_t addr = addr_start; addr < addr_end; addr += 4) { + if (*((const uint32_t *)FLASH_PTR(addr)) != 0xFFFFFFFF) { + return secfalse; + } + } + return sectrue; +} + +secbool flash_write_byte(uint8_t sector, uint32_t offset, uint8_t data) { + uint8_t *address = (uint8_t *)flash_get_address(sector, offset, 1); + if (address == NULL) { + return secfalse; + } + + if ((*address & data) != data) { + return secfalse; + } + + svc_flash_program(FLASH_CR_PROGRAM_X8); + *(volatile uint8_t *)address = data; + + if (*address != data) { + return secfalse; + } + + return sectrue; +} + +secbool flash_write_word(uint8_t sector, uint32_t offset, uint32_t data) { + uint32_t *address = (uint32_t *)flash_get_address(sector, offset, 4); + if (address == NULL) { + return secfalse; + } + + if (offset % 4 != 0) { + return secfalse; + } + + if ((*address & data) != data) { + return secfalse; + } + + svc_flash_program(FLASH_CR_PROGRAM_X32); + *(volatile uint32_t *)address = data; + + if (*address != data) { + return secfalse; + } + + return sectrue; +} diff --git a/legacy/flash.h b/legacy/flash.h new file mode 100644 index 0000000000..c0c7289d16 --- /dev/null +++ b/legacy/flash.h @@ -0,0 +1,50 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FLASH_H +#define FLASH_H + +#include +#include +#include "secbool.h" + +#define FLASH_SECTOR_COUNT 24 + +// note: FLASH_SR_RDERR is STM32F42xxx and STM32F43xxx specific (STM32F427) +// (reference RM0090 section 3.7.5) +#ifndef STM32F427xx +#define FLASH_SR_RDERR 0 +#endif + +#define FLASH_STATUS_ALL_FLAGS \ + (FLASH_SR_RDERR | FLASH_SR_PGSERR | FLASH_SR_PGPERR | FLASH_SR_PGAERR | \ + FLASH_SR_WRPERR | FLASH_SR_SOP | FLASH_SR_EOP) + +void flash_init(void); + +secbool __wur flash_unlock_write(void); +secbool __wur flash_lock_write(void); + +const void *flash_get_address(uint8_t sector, uint32_t offset, uint32_t size); + +secbool __wur flash_erase(uint8_t sector); +secbool __wur flash_write_byte(uint8_t sector, uint32_t offset, uint8_t data); +secbool __wur flash_write_word(uint8_t sector, uint32_t offset, uint32_t data); + +#endif // FLASH_H diff --git a/legacy/gen/Makefile b/legacy/gen/Makefile new file mode 100644 index 0000000000..c518d9182c --- /dev/null +++ b/legacy/gen/Makefile @@ -0,0 +1,9 @@ +CC=gcc + +all: strwidth + +strwidth: strwidth.c fonts.c + $(CC) strwidth.c fonts.c -o strwidth -lreadline + +clean: + rm -f strwidth diff --git a/legacy/gen/bitmaps.c b/legacy/gen/bitmaps.c new file mode 100644 index 0000000000..5d60730d1a --- /dev/null +++ b/legacy/gen/bitmaps.c @@ -0,0 +1,51 @@ +#include "bitmaps.h" + +const uint8_t bmp_digit0_data[] = { 0xff, 0xff, 0xf8, 0x1f, 0xf0, 0x0f, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xf0, 0x0f, 0xf8, 0x1f, 0xff, 0xff, }; +const uint8_t bmp_digit1_data[] = { 0xff, 0xff, 0xfc, 0x3f, 0xf8, 0x3f, 0xf0, 0x3f, 0xf0, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff, }; +const uint8_t bmp_digit2_data[] = { 0xff, 0xff, 0xe0, 0x1f, 0xe0, 0x0f, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xf8, 0x0f, 0xf0, 0x1f, 0xe1, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xe1, 0xff, 0xe0, 0x07, 0xe0, 0x07, 0xff, 0xff, }; +const uint8_t bmp_digit3_data[] = { 0xff, 0xff, 0xe0, 0x1f, 0xe0, 0x0f, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xf8, 0x0f, 0xf8, 0x0f, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xe0, 0x0f, 0xe0, 0x1f, 0xff, 0xff, }; +const uint8_t bmp_digit4_data[] = { 0xff, 0xff, 0xff, 0x0f, 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf1, 0x0f, 0xe3, 0x0f, 0xc7, 0x0f, 0xcf, 0x0f, 0xc0, 0x0f, 0xc0, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0xff, 0xff, }; +const uint8_t bmp_digit5_data[] = { 0xff, 0xff, 0xe0, 0x1f, 0xe0, 0x1f, 0xe7, 0xff, 0xe7, 0xff, 0xe0, 0x1f, 0xe0, 0x0f, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xff, 0x87, 0xe0, 0x0f, 0xe0, 0x1f, 0xff, 0xff, }; +const uint8_t bmp_digit6_data[] = { 0xff, 0xff, 0xf8, 0x1f, 0xf0, 0x1f, 0xe1, 0xff, 0xe1, 0xff, 0xe0, 0x1f, 0xe0, 0x0f, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xf0, 0x0f, 0xf8, 0x1f, 0xff, 0xff, }; +const uint8_t bmp_digit7_data[] = { 0xff, 0xff, 0xe0, 0x07, 0xe0, 0x07, 0xff, 0x87, 0xff, 0x87, 0xff, 0x0f, 0xfe, 0x1f, 0xfc, 0x1f, 0xfc, 0x3f, 0xf8, 0x7f, 0xf8, 0x7f, 0xf8, 0x7f, 0xf8, 0x7f, 0xf8, 0x7f, 0xf8, 0x7f, 0xff, 0xff, }; +const uint8_t bmp_digit8_data[] = { 0xff, 0xff, 0xf8, 0x1f, 0xf0, 0x0f, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xf0, 0x0f, 0xf0, 0x0f, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xf0, 0x0f, 0xf8, 0x1f, 0xff, 0xff, }; +const uint8_t bmp_digit9_data[] = { 0xff, 0xff, 0xf8, 0x1f, 0xf0, 0x0f, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xe1, 0x87, 0xf0, 0x07, 0xf8, 0x07, 0xff, 0x87, 0xff, 0x87, 0xf8, 0x0f, 0xf8, 0x1f, 0xff, 0xff, }; +const uint8_t bmp_gears0_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0c, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x1e, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x3e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x3e, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x1f, 0xe0, 0x00, 0x00, 0x1f, 0xf0, 0x1f, 0xe0, 0x00, 0x00, 0x1f, 0xf0, 0x1f, 0xe0, 0x00, 0x00, 0x1f, 0xf0, 0x1f, 0xc0, 0x00, 0x00, 0x01, 0xf0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0xc0, 0x18, 0x00, 0x00, 0x7f, 0xfd, 0xe0, 0x3c, 0x00, 0x00, 0x7f, 0xfd, 0xe0, 0x7c, 0x00, 0x00, 0xff, 0xfd, 0xff, 0xf8, 0x00, 0x00, 0xf8, 0x7e, 0xff, 0xf8, 0x00, 0x00, 0xf0, 0x1e, 0xff, 0xf0, 0x00, 0x00, 0x60, 0x0d, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x3f, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x3f, 0xc0, 0x00, 0x00, 0x3f, 0xe0, 0x3f, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x3e, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_gears1_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x07, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xcf, 0x80, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x3e, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x1f, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x01, 0xf0, 0x1f, 0xe0, 0x00, 0x00, 0x01, 0xf0, 0x1f, 0xe0, 0x00, 0x00, 0x01, 0xf8, 0x3f, 0xe1, 0xc0, 0x00, 0x00, 0xff, 0xfc, 0xc1, 0xc0, 0x00, 0x00, 0xff, 0xfb, 0x03, 0xc0, 0x00, 0x01, 0xff, 0xf7, 0xc3, 0xc0, 0x00, 0x03, 0xff, 0xf7, 0xff, 0xc0, 0x00, 0x03, 0xc7, 0xf7, 0xff, 0xe0, 0x00, 0x01, 0x81, 0xf3, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xf1, 0xff, 0xff, 0x80, 0x00, 0x00, 0xe1, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xf0, 0x7f, 0xc0, 0x00, 0x00, 0x03, 0xe0, 0x3f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x3e, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x3e, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x3e, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_gears2_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc1, 0x80, 0x00, 0x00, 0x07, 0x3f, 0xf7, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x3e, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x3f, 0x0e, 0x00, 0x00, 0x07, 0xff, 0xff, 0x8f, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xcf, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xcf, 0x00, 0x00, 0x06, 0x1f, 0xe1, 0x9f, 0x83, 0x00, 0x00, 0x0f, 0xce, 0x7f, 0xef, 0x80, 0x00, 0x07, 0x9f, 0xff, 0xff, 0x80, 0x00, 0x07, 0x9f, 0xff, 0xff, 0x00, 0x00, 0x03, 0x8f, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x7e, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0c, 0x3f, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_gears3_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x01, 0x81, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xe1, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0xe0, 0x00, 0x00, 0x01, 0xf0, 0x1f, 0xc0, 0x00, 0x00, 0x01, 0xf0, 0x1f, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x1f, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0x0f, 0xf8, 0x3e, 0x70, 0x00, 0x00, 0x00, 0xff, 0xff, 0x78, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x78, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x7c, 0x0e, 0x00, 0x00, 0x1f, 0xff, 0x7f, 0x9f, 0x00, 0x00, 0x1f, 0x87, 0x7f, 0xff, 0x00, 0x00, 0x1e, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x1e, 0x01, 0xff, 0xfc, 0x00, 0x00, 0x0c, 0x1f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x3c, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x3e, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x3f, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x3f, 0xc0, 0x00, 0x00, 0x03, 0xe0, 0x3f, 0xc0, 0x00, 0x00, 0x03, 0xf0, 0x7f, 0xc0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x03, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x07, 0x8f, 0xe0, 0x00, 0x00, 0x00, 0x03, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_icon_error_data[] = { 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7b, 0xde, 0xf1, 0x8f, 0xf8, 0x1f, 0xfc, 0x3f, 0xfc, 0x3f, 0xf8, 0x1f, 0xf1, 0x8f, 0x7b, 0xde, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, }; +const uint8_t bmp_icon_info_data[] = { 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3e, 0x7c, 0x7e, 0x7e, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0x7e, 0x7e, 0x3e, 0x7c, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, }; +const uint8_t bmp_icon_ok_data[] = { 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0xff, 0xef, 0xff, 0xdf, 0xff, 0xbf, 0xf9, 0x3f, 0xf8, 0x7f, 0xfc, 0xff, 0x7e, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, }; +const uint8_t bmp_icon_question_data[] = { 0x07, 0xe0, 0x0f, 0xf0, 0x1e, 0x78, 0x3c, 0x3c, 0x79, 0x9e, 0xf3, 0xcf, 0xff, 0xcf, 0xff, 0x9f, 0xff, 0x3f, 0xfe, 0x7f, 0xfe, 0x7f, 0x7f, 0xfe, 0x3e, 0x7c, 0x1e, 0x78, 0x0f, 0xf0, 0x07, 0xe0, }; +const uint8_t bmp_icon_warning_data[] = { 0x01, 0x80, 0x01, 0x80, 0x03, 0xc0, 0x03, 0xc0, 0x07, 0xe0, 0x07, 0xe0, 0x0e, 0x70, 0x0e, 0x70, 0x1e, 0x78, 0x1e, 0x78, 0x3e, 0x7c, 0x3f, 0xfc, 0x7e, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, }; +const uint8_t bmp_logo48_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x07, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xc3, 0xfc, 0x00, 0x00, 0x3f, 0x00, 0xfc, 0x00, 0x00, 0x7f, 0x00, 0xfe, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x03, 0xf0, 0x0f, 0xf0, 0x00, 0x0f, 0xf0, 0x0f, 0xfc, 0x00, 0x3f, 0xf0, 0x0f, 0xff, 0x00, 0xff, 0xf0, 0x0f, 0xff, 0xc3, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_logo48_empty_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x03, 0x81, 0xc0, 0x00, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x20, 0x3c, 0x04, 0x00, 0x00, 0x20, 0xc3, 0x04, 0x00, 0x00, 0x21, 0x00, 0x84, 0x00, 0x00, 0x41, 0x00, 0x82, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x0f, 0xc3, 0xff, 0xc3, 0xf0, 0x08, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x10, 0x08, 0x7f, 0xff, 0xfe, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x30, 0x00, 0x0c, 0x10, 0x08, 0x0c, 0x00, 0x30, 0x10, 0x08, 0x03, 0x00, 0xc0, 0x10, 0x0c, 0x00, 0xc3, 0x00, 0x30, 0x03, 0x00, 0x3c, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x03, 0x00, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x30, 0x00, 0x00, 0x03, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_logo64_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xf8, 0x1f, 0xf8, 0x00, 0x00, 0x3f, 0xf0, 0x0f, 0xfc, 0x00, 0x00, 0x3f, 0xe0, 0x07, 0xfc, 0x00, 0x00, 0x3f, 0xc0, 0x03, 0xfc, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x07, 0xf8, 0x1f, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x1f, 0xfe, 0x00, 0x00, 0x7f, 0xf8, 0x1f, 0xff, 0x80, 0x01, 0xff, 0xf8, 0x1f, 0xff, 0xe0, 0x07, 0xff, 0xf8, 0x1f, 0xff, 0xf8, 0x1f, 0xff, 0xf8, 0x07, 0xff, 0xfe, 0x7f, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_logo64_empty_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x70, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x07, 0xe0, 0x10, 0x00, 0x00, 0x10, 0x08, 0x10, 0x08, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, 0x20, 0x20, 0x04, 0x04, 0x00, 0x00, 0x20, 0x40, 0x02, 0x04, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x00, 0x40, 0x80, 0x01, 0x02, 0x00, 0x1f, 0xc0, 0xff, 0xff, 0x03, 0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x3f, 0xff, 0xff, 0xfc, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x04, 0x08, 0x10, 0x18, 0x00, 0x00, 0x18, 0x08, 0x10, 0x06, 0x00, 0x00, 0x60, 0x08, 0x10, 0x01, 0x80, 0x01, 0x80, 0x08, 0x10, 0x00, 0x60, 0x06, 0x00, 0x08, 0x18, 0x00, 0x18, 0x18, 0x00, 0x18, 0x06, 0x00, 0x06, 0x60, 0x00, 0x60, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x06, 0x00, 0x00, 0x60, 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +const uint8_t bmp_webauthn_data[] = { 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x03, 0xfe, 0x1c, 0x00, 0x03, 0xfc, 0x0c, 0x00, 0x07, 0xf8, 0x06, 0x00, 0x07, 0xf8, 0x06, 0x00, 0x0f, 0xf8, 0x06, 0x00, 0x0f, 0xf8, 0x07, 0x00, 0x0f, 0xfc, 0x0f, 0x00, 0x0f, 0xfe, 0x1f, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xdf, 0x80, 0x03, 0xff, 0x80, 0x00, 0x07, 0xff, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, }; + +const BITMAP bmp_digit0 = {16, 16, bmp_digit0_data}; +const BITMAP bmp_digit1 = {16, 16, bmp_digit1_data}; +const BITMAP bmp_digit2 = {16, 16, bmp_digit2_data}; +const BITMAP bmp_digit3 = {16, 16, bmp_digit3_data}; +const BITMAP bmp_digit4 = {16, 16, bmp_digit4_data}; +const BITMAP bmp_digit5 = {16, 16, bmp_digit5_data}; +const BITMAP bmp_digit6 = {16, 16, bmp_digit6_data}; +const BITMAP bmp_digit7 = {16, 16, bmp_digit7_data}; +const BITMAP bmp_digit8 = {16, 16, bmp_digit8_data}; +const BITMAP bmp_digit9 = {16, 16, bmp_digit9_data}; +const BITMAP bmp_gears0 = {48, 48, bmp_gears0_data}; +const BITMAP bmp_gears1 = {48, 48, bmp_gears1_data}; +const BITMAP bmp_gears2 = {48, 48, bmp_gears2_data}; +const BITMAP bmp_gears3 = {48, 48, bmp_gears3_data}; +const BITMAP bmp_icon_error = {16, 16, bmp_icon_error_data}; +const BITMAP bmp_icon_info = {16, 16, bmp_icon_info_data}; +const BITMAP bmp_icon_ok = {16, 16, bmp_icon_ok_data}; +const BITMAP bmp_icon_question = {16, 16, bmp_icon_question_data}; +const BITMAP bmp_icon_warning = {16, 16, bmp_icon_warning_data}; +const BITMAP bmp_logo48 = {40, 48, bmp_logo48_data}; +const BITMAP bmp_logo48_empty = {40, 48, bmp_logo48_empty_data}; +const BITMAP bmp_logo64 = {48, 64, bmp_logo64_data}; +const BITMAP bmp_logo64_empty = {48, 64, bmp_logo64_empty_data}; +const BITMAP bmp_webauthn = {32, 32, bmp_webauthn_data}; diff --git a/legacy/gen/bitmaps.h b/legacy/gen/bitmaps.h new file mode 100644 index 0000000000..c06351db07 --- /dev/null +++ b/legacy/gen/bitmaps.h @@ -0,0 +1,36 @@ +#ifndef __BITMAPS_H__ +#define __BITMAPS_H__ + +#include + +typedef struct { + uint8_t width, height; + const uint8_t *data; +} BITMAP; + +extern const BITMAP bmp_digit0; +extern const BITMAP bmp_digit1; +extern const BITMAP bmp_digit2; +extern const BITMAP bmp_digit3; +extern const BITMAP bmp_digit4; +extern const BITMAP bmp_digit5; +extern const BITMAP bmp_digit6; +extern const BITMAP bmp_digit7; +extern const BITMAP bmp_digit8; +extern const BITMAP bmp_digit9; +extern const BITMAP bmp_gears0; +extern const BITMAP bmp_gears1; +extern const BITMAP bmp_gears2; +extern const BITMAP bmp_gears3; +extern const BITMAP bmp_icon_error; +extern const BITMAP bmp_icon_info; +extern const BITMAP bmp_icon_ok; +extern const BITMAP bmp_icon_question; +extern const BITMAP bmp_icon_warning; +extern const BITMAP bmp_logo48; +extern const BITMAP bmp_logo48_empty; +extern const BITMAP bmp_logo64; +extern const BITMAP bmp_logo64_empty; +extern const BITMAP bmp_webauthn; + +#endif diff --git a/legacy/gen/bitmaps/digit0.png b/legacy/gen/bitmaps/digit0.png new file mode 100644 index 0000000000..9dfc0a41cb Binary files /dev/null and b/legacy/gen/bitmaps/digit0.png differ diff --git a/legacy/gen/bitmaps/digit1.png b/legacy/gen/bitmaps/digit1.png new file mode 100644 index 0000000000..8645ae6f1e Binary files /dev/null and b/legacy/gen/bitmaps/digit1.png differ diff --git a/legacy/gen/bitmaps/digit2.png b/legacy/gen/bitmaps/digit2.png new file mode 100644 index 0000000000..81f206e79a Binary files /dev/null and b/legacy/gen/bitmaps/digit2.png differ diff --git a/legacy/gen/bitmaps/digit3.png b/legacy/gen/bitmaps/digit3.png new file mode 100644 index 0000000000..eef5494406 Binary files /dev/null and b/legacy/gen/bitmaps/digit3.png differ diff --git a/legacy/gen/bitmaps/digit4.png b/legacy/gen/bitmaps/digit4.png new file mode 100644 index 0000000000..6096ec9964 Binary files /dev/null and b/legacy/gen/bitmaps/digit4.png differ diff --git a/legacy/gen/bitmaps/digit5.png b/legacy/gen/bitmaps/digit5.png new file mode 100644 index 0000000000..aafcd3c545 Binary files /dev/null and b/legacy/gen/bitmaps/digit5.png differ diff --git a/legacy/gen/bitmaps/digit6.png b/legacy/gen/bitmaps/digit6.png new file mode 100644 index 0000000000..0b0c59eac0 Binary files /dev/null and b/legacy/gen/bitmaps/digit6.png differ diff --git a/legacy/gen/bitmaps/digit7.png b/legacy/gen/bitmaps/digit7.png new file mode 100644 index 0000000000..e4eea9e0a4 Binary files /dev/null and b/legacy/gen/bitmaps/digit7.png differ diff --git a/legacy/gen/bitmaps/digit8.png b/legacy/gen/bitmaps/digit8.png new file mode 100644 index 0000000000..de021652a1 Binary files /dev/null and b/legacy/gen/bitmaps/digit8.png differ diff --git a/legacy/gen/bitmaps/digit9.png b/legacy/gen/bitmaps/digit9.png new file mode 100644 index 0000000000..2a7458aae6 Binary files /dev/null and b/legacy/gen/bitmaps/digit9.png differ diff --git a/legacy/gen/bitmaps/gears0.png b/legacy/gen/bitmaps/gears0.png new file mode 100644 index 0000000000..10f5935975 Binary files /dev/null and b/legacy/gen/bitmaps/gears0.png differ diff --git a/legacy/gen/bitmaps/gears1.png b/legacy/gen/bitmaps/gears1.png new file mode 100644 index 0000000000..9fa6a8102a Binary files /dev/null and b/legacy/gen/bitmaps/gears1.png differ diff --git a/legacy/gen/bitmaps/gears2.png b/legacy/gen/bitmaps/gears2.png new file mode 100644 index 0000000000..56c1998620 Binary files /dev/null and b/legacy/gen/bitmaps/gears2.png differ diff --git a/legacy/gen/bitmaps/gears3.png b/legacy/gen/bitmaps/gears3.png new file mode 100644 index 0000000000..9aa384062f Binary files /dev/null and b/legacy/gen/bitmaps/gears3.png differ diff --git a/legacy/gen/bitmaps/generate.py b/legacy/gen/bitmaps/generate.py new file mode 100755 index 0000000000..d0005427ce --- /dev/null +++ b/legacy/gen/bitmaps/generate.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +import glob +import os +from PIL import Image + +hdrs = [] +data = [] +imgs = [] + +def encode_pixels(img): + r = '' + img = [ (x[0] + x[1] + x[2] > 384 and '1' or '0') for x in img] + for i in range(len(img) // 8): + c = ''.join(img[i * 8 : i * 8 + 8]) + r += '0x%02x, ' % int(c, 2) + return r + +cnt = 0 +for fn in sorted(glob.glob('*.png')): + print('Processing:', fn) + im = Image.open(fn) + name = os.path.splitext(fn)[0] + w, h = im.size + if w % 8 != 0: + raise Exception('Width must be divisable by 8! (%s is %dx%d)' % (fn, w, h)) + img = list(im.getdata()) + hdrs.append('extern const BITMAP bmp_%s;\n' % name) + imgs.append('const BITMAP bmp_%s = {%d, %d, bmp_%s_data};\n' % (name, w, h, name)) + data.append('const uint8_t bmp_%s_data[] = { %s};\n' % (name, encode_pixels(img))) + cnt += 1 + +with open('../bitmaps.c', 'wt') as f: + f.write('#include "bitmaps.h"\n\n') + for i in range(cnt): + f.write(data[i]) + f.write('\n') + for i in range(cnt): + f.write(imgs[i]) + f.close() + +with open('../bitmaps.h', 'wt') as f: + f.write('''#ifndef __BITMAPS_H__ +#define __BITMAPS_H__ + +#include + +typedef struct { + uint8_t width, height; + const uint8_t *data; +} BITMAP; + +''') + + for i in range(cnt): + f.write(hdrs[i]) + + f.write('\n#endif\n') + f.close() diff --git a/legacy/gen/bitmaps/icon_error.png b/legacy/gen/bitmaps/icon_error.png new file mode 100644 index 0000000000..f23a1e7945 Binary files /dev/null and b/legacy/gen/bitmaps/icon_error.png differ diff --git a/legacy/gen/bitmaps/icon_info.png b/legacy/gen/bitmaps/icon_info.png new file mode 100644 index 0000000000..2044c7622f Binary files /dev/null and b/legacy/gen/bitmaps/icon_info.png differ diff --git a/legacy/gen/bitmaps/icon_ok.png b/legacy/gen/bitmaps/icon_ok.png new file mode 100644 index 0000000000..36fcee2a2e Binary files /dev/null and b/legacy/gen/bitmaps/icon_ok.png differ diff --git a/legacy/gen/bitmaps/icon_question.png b/legacy/gen/bitmaps/icon_question.png new file mode 100644 index 0000000000..b3ef441620 Binary files /dev/null and b/legacy/gen/bitmaps/icon_question.png differ diff --git a/legacy/gen/bitmaps/icon_warning.png b/legacy/gen/bitmaps/icon_warning.png new file mode 100644 index 0000000000..1e7ea18d5c Binary files /dev/null and b/legacy/gen/bitmaps/icon_warning.png differ diff --git a/legacy/gen/bitmaps/logo48.png b/legacy/gen/bitmaps/logo48.png new file mode 100644 index 0000000000..e90423ca08 Binary files /dev/null and b/legacy/gen/bitmaps/logo48.png differ diff --git a/legacy/gen/bitmaps/logo48_empty.png b/legacy/gen/bitmaps/logo48_empty.png new file mode 100644 index 0000000000..2b88a2905e Binary files /dev/null and b/legacy/gen/bitmaps/logo48_empty.png differ diff --git a/legacy/gen/bitmaps/logo64.png b/legacy/gen/bitmaps/logo64.png new file mode 100644 index 0000000000..1ea8747a90 Binary files /dev/null and b/legacy/gen/bitmaps/logo64.png differ diff --git a/legacy/gen/bitmaps/logo64_empty.png b/legacy/gen/bitmaps/logo64_empty.png new file mode 100644 index 0000000000..ce7a006fde Binary files /dev/null and b/legacy/gen/bitmaps/logo64_empty.png differ diff --git a/legacy/gen/bitmaps/webauthn.png b/legacy/gen/bitmaps/webauthn.png new file mode 100644 index 0000000000..1f1b595abf Binary files /dev/null and b/legacy/gen/bitmaps/webauthn.png differ diff --git a/legacy/gen/font.inc b/legacy/gen/font.inc new file mode 100644 index 0000000000..e14089597b --- /dev/null +++ b/legacy/gen/font.inc @@ -0,0 +1,128 @@ + /* 0x00 _ */ (uint8_t *)"\x01\x00", + /* 0x01 _ */ (uint8_t *)"\x01\x00", + /* 0x02 _ */ (uint8_t *)"\x01\x00", + /* 0x03 _ */ (uint8_t *)"\x01\x00", + /* 0x04 _ */ (uint8_t *)"\x01\x00", + /* 0x05 _ */ (uint8_t *)"\x01\x00", + /* 0x06 _ */ (uint8_t *)"\x07\x18\x1c\x0e\x18\x30\x40\x80", + /* 0x07 _ */ (uint8_t *)"\x01\x00", + /* 0x08 _ */ (uint8_t *)"\x01\x00", + /* 0x09 _ */ (uint8_t *)"\x01\x00", + /* 0x0a _ */ (uint8_t *)"\x01\x00", + /* 0x0b _ */ (uint8_t *)"\x01\x00", + /* 0x0c _ */ (uint8_t *)"\x01\x00", + /* 0x0d _ */ (uint8_t *)"\x01\x00", + /* 0x0e _ */ (uint8_t *)"\x01\x00", + /* 0x0f _ */ (uint8_t *)"\x01\x00", + /* 0x10 _ */ (uint8_t *)"\x01\x00", + /* 0x11 _ */ (uint8_t *)"\x01\x00", + /* 0x12 _ */ (uint8_t *)"\x01\x00", + /* 0x13 _ */ (uint8_t *)"\x01\x00", + /* 0x14 _ */ (uint8_t *)"\x01\x00", + /* 0x15 _ */ (uint8_t *)"\x07\x44\xee\x7c\x38\x7c\xee\x44", + /* 0x16 _ */ (uint8_t *)"\x01\x00", + /* 0x17 _ */ (uint8_t *)"\x01\x00", + /* 0x18 _ */ (uint8_t *)"\x01\x00", + /* 0x19 _ */ (uint8_t *)"\x01\x00", + /* 0x1a _ */ (uint8_t *)"\x01\x00", + /* 0x1b _ */ (uint8_t *)"\x01\x00", + /* 0x1c _ */ (uint8_t *)"\x01\x00", + /* 0x1d _ */ (uint8_t *)"\x01\x00", + /* 0x1e _ */ (uint8_t *)"\x01\x00", + /* 0x1f _ */ (uint8_t *)"\x01\x00", + /* 0x20 */ (uint8_t *)"\x01\x00", + /* 0x21 ! */ (uint8_t *)"\x02\xfa\xfa", + /* 0x22 " */ (uint8_t *)"\x03\xc0\x00\xc0", + /* 0x23 # */ (uint8_t *)"\x05\x6c\xfe\x6c\xfe\x6c", + /* 0x24 $ */ (uint8_t *)"\x05\x32\xff\x5a\xff\x4c", + /* 0x25 % */ (uint8_t *)"\x06\xc0\xc6\x1c\x70\xc6\x06", + /* 0x26 & */ (uint8_t *)"\x06\x5c\xfe\xb2\xfe\x4c\x1e", + /* 0x27 ' */ (uint8_t *)"\x01\xc0", + /* 0x28 ( */ (uint8_t *)"\x03\x38\x7c\x82", + /* 0x29 ) */ (uint8_t *)"\x03\x82\x7c\x38", + /* 0x2a * */ (uint8_t *)"\x05\x6c\x38\xfe\x38\x6c", + /* 0x2b + */ (uint8_t *)"\x05\x10\x10\x7c\x10\x10", + /* 0x2c , */ (uint8_t *)"\x02\x03\x06", + /* 0x2d - */ (uint8_t *)"\x04\x10\x10\x10\x10", + /* 0x2e . */ (uint8_t *)"\x02\x06\x06", + /* 0x2f / */ (uint8_t *)"\x03\x0e\x38\xe0", + /* 0x30 0 */ (uint8_t *)"\x05\x7c\xfe\x82\xfe\x7c", + /* 0x31 1 */ (uint8_t *)"\x03\x40\xfe\xfe", + /* 0x32 2 */ (uint8_t *)"\x05\x8e\x9e\x92\xf2\x62", + /* 0x33 3 */ (uint8_t *)"\x05\x82\x92\x92\xfe\x6c", + /* 0x34 4 */ (uint8_t *)"\x05\x18\x28\x48\xfe\xfe", + /* 0x35 5 */ (uint8_t *)"\x05\xe2\xa2\xa2\xbe\x1c", + /* 0x36 6 */ (uint8_t *)"\x05\x7c\xfe\xa2\xbe\x1c", + /* 0x37 7 */ (uint8_t *)"\x05\x80\x8e\xbe\xf0\xc0", + /* 0x38 8 */ (uint8_t *)"\x05\x6c\xfe\x92\xfe\x6c", + /* 0x39 9 */ (uint8_t *)"\x05\x70\xfa\x8a\xfe\x7c", + /* 0x3a : */ (uint8_t *)"\x02\x36\x36", + /* 0x3b ; */ (uint8_t *)"\x02\x33\x36", + /* 0x3c < */ (uint8_t *)"\x04\x10\x38\x6c\xc6", + /* 0x3d = */ (uint8_t *)"\x04\x28\x28\x28\x28", + /* 0x3e > */ (uint8_t *)"\x04\xc6\x6c\x38\x10", + /* 0x3f ? */ (uint8_t *)"\x05\x80\x9a\xba\xe0\x40", + /* 0x40 @ */ (uint8_t *)"\x06\x7c\xfe\xaa\xba\xfa\x78", + /* 0x41 A */ (uint8_t *)"\x05\x7e\xfe\x88\xfe\x7e", + /* 0x42 B */ (uint8_t *)"\x05\xfe\xfe\xa2\xfe\x5c", + /* 0x43 C */ (uint8_t *)"\x05\x7c\xfe\x82\x82\x82", + /* 0x44 D */ (uint8_t *)"\x05\xfe\xfe\x82\xfe\x7c", + /* 0x45 E */ (uint8_t *)"\x05\xfe\xfe\xa2\xa2\x82", + /* 0x46 F */ (uint8_t *)"\x05\xfe\xfe\xa0\xa0\x80", + /* 0x47 G */ (uint8_t *)"\x05\x7c\xfe\x82\x9e\x1e", + /* 0x48 H */ (uint8_t *)"\x05\xfe\xfe\x20\xfe\xfe", + /* 0x49 I */ (uint8_t *)"\x02\xfe\xfe", + /* 0x4a J */ (uint8_t *)"\x04\x02\x02\xfe\xfc", + /* 0x4b K */ (uint8_t *)"\x06\xfe\xfe\x38\x6c\xc6\x82", + /* 0x4c L */ (uint8_t *)"\x04\xfe\xfe\x02\x02", + /* 0x4d M */ (uint8_t *)"\x07\xfe\x7e\x30\x18\x30\x7e\xfe", + /* 0x4e N */ (uint8_t *)"\x06\xfe\x7e\x30\x18\xfc\xfe", + /* 0x4f O */ (uint8_t *)"\x06\x7c\xfe\x82\x82\xfe\x7c", + /* 0x50 P */ (uint8_t *)"\x05\xfe\xfe\x88\xf8\x70", + /* 0x51 Q */ (uint8_t *)"\x06\x7c\xfe\x82\x86\xff\x7d", + /* 0x52 R */ (uint8_t *)"\x05\xfe\xfe\x88\xfe\x72", + /* 0x53 S */ (uint8_t *)"\x04\x62\xf2\x9e\x8c", + /* 0x54 T */ (uint8_t *)"\x06\x80\x80\xfe\xfe\x80\x80", + /* 0x55 U */ (uint8_t *)"\x05\xfc\xfe\x02\xfe\xfc", + /* 0x56 V */ (uint8_t *)"\x06\xe0\xf8\x1e\x1e\xf8\xe0", + /* 0x57 W */ (uint8_t *)"\x07\xf0\xfe\x1e\x3c\x1e\xfe\xf0", + /* 0x58 X */ (uint8_t *)"\x06\xc6\xee\x38\x38\xee\xc6", + /* 0x59 Y */ (uint8_t *)"\x06\xc0\xe0\x3e\x3e\xe0\xc0", + /* 0x5a Z */ (uint8_t *)"\x05\x8e\x9e\xba\xf2\xe2", + /* 0x5b [ */ (uint8_t *)"\x03\xfe\xfe\x82", + /* 0x5c \ */ (uint8_t *)"\x03\xe0\x38\x0e", + /* 0x5d ] */ (uint8_t *)"\x03\x82\xfe\xfe", + /* 0x5e ^ */ (uint8_t *)"\x03\x60\xc0\x60", + /* 0x5f _ */ (uint8_t *)"\x06\x02\x02\x02\x02\x02\x02", + /* 0x60 ` */ (uint8_t *)"\x02\x80\x40", + /* 0x61 a */ (uint8_t *)"\x05\x04\x2e\x2a\x3e\x1e", + /* 0x62 b */ (uint8_t *)"\x05\xfe\xfe\x22\x3e\x1c", + /* 0x63 c */ (uint8_t *)"\x05\x1c\x3e\x22\x36\x14", + /* 0x64 d */ (uint8_t *)"\x05\x1c\x3e\x22\xfe\xfe", + /* 0x65 e */ (uint8_t *)"\x05\x1c\x3e\x2a\x3a\x1a", + /* 0x66 f */ (uint8_t *)"\x03\x7e\xfe\xa0", + /* 0x67 g */ (uint8_t *)"\x05\x18\x3d\x25\x3f\x3e", + /* 0x68 h */ (uint8_t *)"\x05\xfe\xfe\x20\x3e\x1e", + /* 0x69 i */ (uint8_t *)"\x02\xbe\xbe", + /* 0x6a j */ (uint8_t *)"\x03\x01\xbf\xbe", + /* 0x6b k */ (uint8_t *)"\x05\xfe\xfe\x1c\x36\x22", + /* 0x6c l */ (uint8_t *)"\x02\xfe\xfe", + /* 0x6d m */ (uint8_t *)"\x08\x3e\x3e\x20\x3e\x3e\x20\x3e\x1e", + /* 0x6e n */ (uint8_t *)"\x05\x3e\x3e\x20\x3e\x1e", + /* 0x6f o */ (uint8_t *)"\x05\x1c\x3e\x22\x3e\x1c", + /* 0x70 p */ (uint8_t *)"\x05\x3f\x3f\x24\x3c\x18", + /* 0x71 q */ (uint8_t *)"\x05\x18\x3c\x24\x3f\x3f", + /* 0x72 r */ (uint8_t *)"\x04\x3e\x3e\x10\x30", + /* 0x73 s */ (uint8_t *)"\x04\x1a\x3a\x2e\x2c", + /* 0x74 t */ (uint8_t *)"\x03\xfc\xfe\x22", + /* 0x75 u */ (uint8_t *)"\x05\x3c\x3e\x02\x3e\x3e", + /* 0x76 v */ (uint8_t *)"\x05\x30\x3c\x0e\x3c\x30", + /* 0x77 w */ (uint8_t *)"\x07\x38\x3e\x06\x1c\x06\x3e\x38", + /* 0x78 x */ (uint8_t *)"\x05\x36\x3e\x08\x3e\x36", + /* 0x79 y */ (uint8_t *)"\x05\x38\x3d\x05\x3f\x3e", + /* 0x7a z */ (uint8_t *)"\x05\x26\x2e\x3a\x32\x22", + /* 0x7b { */ (uint8_t *)"\x04\x10\x7c\xee\x82", + /* 0x7c | */ (uint8_t *)"\x02\xff\xff", + /* 0x7d } */ (uint8_t *)"\x04\x82\xee\x7c\x10", + /* 0x7e ~ */ (uint8_t *)"\x04\x08\x10\x08\x10", + /* 0x7f _ */ (uint8_t *)"\x01\x00", diff --git a/legacy/gen/fontfixed.inc b/legacy/gen/fontfixed.inc new file mode 100644 index 0000000000..823d4b0a17 --- /dev/null +++ b/legacy/gen/fontfixed.inc @@ -0,0 +1,128 @@ + /* 0x00 _ */ (uint8_t *)"\x01\x00", + /* 0x01 _ */ (uint8_t *)"\x01\x00", + /* 0x02 _ */ (uint8_t *)"\x01\x00", + /* 0x03 _ */ (uint8_t *)"\x01\x00", + /* 0x04 _ */ (uint8_t *)"\x01\x00", + /* 0x05 _ */ (uint8_t *)"\x01\x00", + /* 0x06 _ */ (uint8_t *)"\x07\x18\x1c\x0e\x18\x30\x40\x80", + /* 0x07 _ */ (uint8_t *)"\x01\x00", + /* 0x08 _ */ (uint8_t *)"\x01\x00", + /* 0x09 _ */ (uint8_t *)"\x01\x00", + /* 0x0a _ */ (uint8_t *)"\x01\x00", + /* 0x0b _ */ (uint8_t *)"\x01\x00", + /* 0x0c _ */ (uint8_t *)"\x01\x00", + /* 0x0d _ */ (uint8_t *)"\x01\x00", + /* 0x0e _ */ (uint8_t *)"\x01\x00", + /* 0x0f _ */ (uint8_t *)"\x01\x00", + /* 0x10 _ */ (uint8_t *)"\x01\x00", + /* 0x11 _ */ (uint8_t *)"\x01\x00", + /* 0x12 _ */ (uint8_t *)"\x01\x00", + /* 0x13 _ */ (uint8_t *)"\x01\x00", + /* 0x14 _ */ (uint8_t *)"\x01\x00", + /* 0x15 _ */ (uint8_t *)"\x07\x44\xee\x7c\x38\x7c\xee\x44", + /* 0x16 _ */ (uint8_t *)"\x01\x00", + /* 0x17 _ */ (uint8_t *)"\x01\x00", + /* 0x18 _ */ (uint8_t *)"\x01\x00", + /* 0x19 _ */ (uint8_t *)"\x01\x00", + /* 0x1a _ */ (uint8_t *)"\x01\x00", + /* 0x1b _ */ (uint8_t *)"\x01\x00", + /* 0x1c _ */ (uint8_t *)"\x01\x00", + /* 0x1d _ */ (uint8_t *)"\x01\x00", + /* 0x1e _ */ (uint8_t *)"\x01\x00", + /* 0x1f _ */ (uint8_t *)"\x01\x00", + /* 0x20 */ (uint8_t *)"\x01\x00", + /* 0x21 ! */ (uint8_t *)"\x03\x60\xfa\x60", + /* 0x22 " */ (uint8_t *)"\x05\x00\xe0\x00\xe0\x00", + /* 0x23 # */ (uint8_t *)"\x05\x6c\xfe\x6c\xfe\x6c", + /* 0x24 $ */ (uint8_t *)"\x05\x32\xff\x5a\xff\x4c", + /* 0x25 % */ (uint8_t *)"\x05\xc2\xcc\x10\x66\x86", + /* 0x26 & */ (uint8_t *)"\x05\x5c\xa2\xb2\x4c\x1a", + /* 0x27 ' */ (uint8_t *)"\x05\x00\x00\xe0\x00\x00", + /* 0x28 ( */ (uint8_t *)"\x03\x38\x44\x82", + /* 0x29 ) */ (uint8_t *)"\x03\x82\x44\x38", + /* 0x2a * */ (uint8_t *)"\x05\x44\x28\xfe\x28\x44", + /* 0x2b + */ (uint8_t *)"\x05\x10\x10\x7c\x10\x10", + /* 0x2c , */ (uint8_t *)"\x03\x01\x06\x00", + /* 0x2d - */ (uint8_t *)"\x04\x10\x10\x10\x10", + /* 0x2e . */ (uint8_t *)"\x03\x00\x02\x00", + /* 0x2f / */ (uint8_t *)"\x03\x06\x38\xc0", + /* 0x30 0 */ (uint8_t *)"\x05\x7c\x82\x92\x82\x7c", + /* 0x31 1 */ (uint8_t *)"\x05\x22\x42\xfe\x02\x02", + /* 0x32 2 */ (uint8_t *)"\x05\x42\x86\x8a\x92\x62", + /* 0x33 3 */ (uint8_t *)"\x05\x44\x82\x92\x92\x6c", + /* 0x34 4 */ (uint8_t *)"\x05\x18\x28\x48\x88\xfe", + /* 0x35 5 */ (uint8_t *)"\x05\xf4\x92\x92\x92\x8c", + /* 0x36 6 */ (uint8_t *)"\x05\x7c\x92\x92\x92\x4c", + /* 0x37 7 */ (uint8_t *)"\x05\x80\x80\x8e\xb0\xc0", + /* 0x38 8 */ (uint8_t *)"\x05\x6c\x92\x92\x92\x6c", + /* 0x39 9 */ (uint8_t *)"\x05\x64\x92\x92\x92\x7c", + /* 0x3a : */ (uint8_t *)"\x03\x00\x24\x00", + /* 0x3b ; */ (uint8_t *)"\x03\x01\x26\x00", + /* 0x3c < */ (uint8_t *)"\x04\x10\x28\x44\x82", + /* 0x3d = */ (uint8_t *)"\x04\x28\x28\x28\x28", + /* 0x3e > */ (uint8_t *)"\x04\x82\x44\x28\x10", + /* 0x3f ? */ (uint8_t *)"\x05\x40\x80\x9a\xa0\x40", + /* 0x40 @ */ (uint8_t *)"\x05\x7c\x82\x9a\xaa\x72", + /* 0x41 A */ (uint8_t *)"\x05\x7e\x90\x90\x90\x7e", + /* 0x42 B */ (uint8_t *)"\x05\xfe\x92\x92\x92\x6c", + /* 0x43 C */ (uint8_t *)"\x05\x7c\x82\x82\x82\x44", + /* 0x44 D */ (uint8_t *)"\x05\xfe\x82\x82\x82\x7c", + /* 0x45 E */ (uint8_t *)"\x05\xfe\x92\x92\x92\x82", + /* 0x46 F */ (uint8_t *)"\x05\xfe\x90\x90\x90\x80", + /* 0x47 G */ (uint8_t *)"\x05\x7c\x82\x82\x92\x5c", + /* 0x48 H */ (uint8_t *)"\x05\xfe\x10\x10\x10\xfe", + /* 0x49 I */ (uint8_t *)"\x05\x82\x82\xfe\x82\x82", + /* 0x4a J */ (uint8_t *)"\x05\x04\x02\x02\x82\xfc", + /* 0x4b K */ (uint8_t *)"\x05\xfe\x10\x28\x44\x82", + /* 0x4c L */ (uint8_t *)"\x05\xfe\x02\x02\x02\x02", + /* 0x4d M */ (uint8_t *)"\x05\xfe\x40\x30\x40\xfe", + /* 0x4e N */ (uint8_t *)"\x05\xfe\x40\x38\x04\xfe", + /* 0x4f O */ (uint8_t *)"\x05\x7c\x82\x82\x82\x7c", + /* 0x50 P */ (uint8_t *)"\x05\xfe\x90\x90\x90\x60", + /* 0x51 Q */ (uint8_t *)"\x05\x7c\x82\x8a\x84\x7a", + /* 0x52 R */ (uint8_t *)"\x05\xfe\x90\x98\x94\x62", + /* 0x53 S */ (uint8_t *)"\x05\x64\x92\x92\x92\x4c", + /* 0x54 T */ (uint8_t *)"\x05\x80\x80\xfe\x80\x80", + /* 0x55 U */ (uint8_t *)"\x05\xfc\x02\x02\x02\xfc", + /* 0x56 V */ (uint8_t *)"\x05\xe0\x18\x06\x18\xe0", + /* 0x57 W */ (uint8_t *)"\x05\xfc\x02\x1c\x02\xfc", + /* 0x58 X */ (uint8_t *)"\x05\xc6\x28\x10\x28\xc6", + /* 0x59 Y */ (uint8_t *)"\x05\xc0\x20\x1e\x20\xc0", + /* 0x5a Z */ (uint8_t *)"\x05\x86\x8a\x92\xa2\xc2", + /* 0x5b [ */ (uint8_t *)"\x03\xfe\x82\x82", + /* 0x5c \ */ (uint8_t *)"\x03\xe0\x38\x0e", + /* 0x5d ] */ (uint8_t *)"\x03\x82\x82\xfe", + /* 0x5e ^ */ (uint8_t *)"\x03\x60\xc0\x60", + /* 0x5f _ */ (uint8_t *)"\x05\x02\x02\x02\x02\x02", + /* 0x60 ` */ (uint8_t *)"\x05\x00\x80\x40\x20\x00", + /* 0x61 a */ (uint8_t *)"\x05\x04\x2a\x2a\x2a\x1e", + /* 0x62 b */ (uint8_t *)"\x05\xfe\x22\x22\x22\x1c", + /* 0x63 c */ (uint8_t *)"\x05\x1c\x22\x22\x22\x14", + /* 0x64 d */ (uint8_t *)"\x05\x1c\x22\x22\x22\xfe", + /* 0x65 e */ (uint8_t *)"\x05\x1c\x2a\x2a\x2a\x1a", + /* 0x66 f */ (uint8_t *)"\x05\x10\x7e\x90\x80\x40", + /* 0x67 g */ (uint8_t *)"\x05\x18\x25\x25\x25\x3e", + /* 0x68 h */ (uint8_t *)"\x05\xfe\x10\x20\x20\x1e", + /* 0x69 i */ (uint8_t *)"\x05\x00\x22\xbe\x02\x00", + /* 0x6a j */ (uint8_t *)"\x05\x02\x01\x21\xbe\x00", + /* 0x6b k */ (uint8_t *)"\x05\xfe\x08\x14\x22\x00", + /* 0x6c l */ (uint8_t *)"\x05\x80\x80\xfc\x02\x02", + /* 0x6d m */ (uint8_t *)"\x05\x3e\x20\x1e\x20\x1e", + /* 0x6e n */ (uint8_t *)"\x05\x3e\x10\x20\x20\x1e", + /* 0x6f o */ (uint8_t *)"\x05\x1c\x22\x22\x22\x1c", + /* 0x70 p */ (uint8_t *)"\x05\x3f\x24\x24\x24\x18", + /* 0x71 q */ (uint8_t *)"\x05\x18\x24\x24\x24\x3f", + /* 0x72 r */ (uint8_t *)"\x05\x3e\x10\x20\x20\x10", + /* 0x73 s */ (uint8_t *)"\x05\x12\x2a\x2a\x2a\x24", + /* 0x74 t */ (uint8_t *)"\x05\x20\x20\xfc\x22\x22", + /* 0x75 u */ (uint8_t *)"\x05\x3c\x02\x02\x02\x3e", + /* 0x76 v */ (uint8_t *)"\x05\x30\x0c\x02\x0c\x30", + /* 0x77 w */ (uint8_t *)"\x05\x3c\x02\x0c\x02\x3c", + /* 0x78 x */ (uint8_t *)"\x05\x22\x36\x08\x36\x22", + /* 0x79 y */ (uint8_t *)"\x05\x38\x05\x05\x05\x3e", + /* 0x7a z */ (uint8_t *)"\x05\x22\x26\x2a\x32\x22", + /* 0x7b { */ (uint8_t *)"\x04\x10\x7c\xee\x82", + /* 0x7c | */ (uint8_t *)"\x03\x00\xfe\x00", + /* 0x7d } */ (uint8_t *)"\x04\x82\xee\x7c\x10", + /* 0x7e ~ */ (uint8_t *)"\x05\x18\x20\x10\x08\x30", + /* 0x7f _ */ (uint8_t *)"\x01\x00", diff --git a/legacy/gen/fonts.c b/legacy/gen/fonts.c new file mode 100644 index 0000000000..68deb549bc --- /dev/null +++ b/legacy/gen/fonts.c @@ -0,0 +1,18 @@ +#include "fonts.h" + +const uint8_t * const font_data[2][128] = { + { +#include"font.inc" + }, + { +#include"fontfixed.inc" + }, +}; + +int fontCharWidth(int font, char c) { + return font_data[font][c & 0x7f][0]; +} + +const uint8_t *fontCharData(int font, char c) { + return font_data[font][c & 0x7f] + 1; +} diff --git a/legacy/gen/fonts.h b/legacy/gen/fonts.h new file mode 100644 index 0000000000..dbbbef1303 --- /dev/null +++ b/legacy/gen/fonts.h @@ -0,0 +1,16 @@ +#ifndef __FONTS_H__ +#define __FONTS_H__ + +#include + +#define FONT_HEIGHT 8 +#define FONT_STANDARD 0 +#define FONT_FIXED 1 +#define FONT_DOUBLE 0x80 + +extern const uint8_t * const font_data[2][128]; + +int fontCharWidth(int font, char c); +const uint8_t *fontCharData(int font, char c); + +#endif diff --git a/legacy/gen/fonts/font.png b/legacy/gen/fonts/font.png new file mode 100644 index 0000000000..a3d26a98eb Binary files /dev/null and b/legacy/gen/fonts/font.png differ diff --git a/legacy/gen/fonts/fontfixed.png b/legacy/gen/fonts/fontfixed.png new file mode 100644 index 0000000000..50c77f7a4b Binary files /dev/null and b/legacy/gen/fonts/fontfixed.png differ diff --git a/legacy/gen/fonts/generate.py b/legacy/gen/fonts/generate.py new file mode 100755 index 0000000000..3299138c71 --- /dev/null +++ b/legacy/gen/fonts/generate.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +from PIL import Image + +class Img(object): + + def __init__(self, fn): + im = Image.open(fn) + self.w, self.h = im.size + self.data = list(im.getdata()) + + def pixel(self, r, c): + p = self.data[ r + c * self.w ] + if p == (255, 255, 255): + return '0' + if p == (0, 0, 0): + return '1' + if p == (255, 0, 255): + return None + raise Exception('Unknown color', p) + + +def convert(imgfile, outfile): + img = Img(imgfile) + cur = '' + with open(outfile, 'w') as f: + for i in range(128): + x = (i % 16) * 10 + y = (i // 16) * 10 + cur = '' + while img.pixel(x, y) != None: + val = ''.join(img.pixel(x, y + j) for j in range(8)) + x += 1 + cur += '\\x%02x' % int(val, 2) + cur = '\\x%02x' % (len(cur) // 4) + cur + ch = chr(i) if i >= 32 and i <= 126 else '_' + f.write('\t/* 0x%02x %c */ (uint8_t *)"%s",\n' % (i, ch , cur)) + +convert('fonts/fontfixed.png', 'fontfixed.inc') +convert('fonts/font.png', 'font.inc') diff --git a/legacy/gen/handlers/handlers.py b/legacy/gen/handlers/handlers.py new file mode 100755 index 0000000000..5e79610675 --- /dev/null +++ b/legacy/gen/handlers/handlers.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +from __future__ import print_function + +handlers = [ + 'hard_fault_handler', + 'mem_manage_handler', + 'bus_fault_handler', + 'usage_fault_handler', + 'nvic_wwdg_isr', + 'pvd_isr', + 'tamp_stamp_isr', + 'rtc_wkup_isr', + 'flash_isr', + 'rcc_isr', + 'exti0_isr', + 'exti1_isr', + 'exti2_isr', + 'exti3_isr', + 'exti4_isr', + 'dma1_stream0_isr', + 'dma1_stream1_isr', + 'dma1_stream2_isr', + 'dma1_stream3_isr', + 'dma1_stream4_isr', + 'dma1_stream5_isr', + 'dma1_stream6_isr', + 'adc_isr', + 'can1_tx_isr', + 'can1_rx0_isr', + 'can1_rx1_isr', + 'can1_sce_isr', + 'exti9_5_isr', + 'tim1_brk_tim9_isr', + 'tim1_up_tim10_isr', + 'tim1_trg_com_tim11_isr', + 'tim1_cc_isr', + 'tim2_isr', + 'tim3_isr', + 'tim4_isr', + 'i2c1_ev_isr', + 'i2c1_er_isr', + 'i2c2_ev_isr', + 'i2c2_er_isr', + 'spi1_isr', + 'spi2_isr', + 'usart1_isr', + 'usart2_isr', + 'usart3_isr', + 'exti15_10_isr', + 'rtc_alarm_isr', + 'usb_fs_wkup_isr', + 'tim8_brk_tim12_isr', + 'tim8_up_tim13_isr', + 'tim8_trg_com_tim14_isr', + 'tim8_cc_isr', + 'dma1_stream7_isr', + 'fsmc_isr', + 'sdio_isr', + 'tim5_isr', + 'spi3_isr', + 'uart4_isr', + 'uart5_isr', + 'tim6_dac_isr', + 'tim7_isr', + 'dma2_stream0_isr', + 'dma2_stream1_isr', + 'dma2_stream2_isr', + 'dma2_stream3_isr', + 'dma2_stream4_isr', + 'eth_isr', + 'eth_wkup_isr', + 'can2_tx_isr', + 'can2_rx0_isr', + 'can2_rx1_isr', + 'can2_sce_isr', + 'otg_fs_isr', + 'dma2_stream5_isr', + 'dma2_stream6_isr', + 'dma2_stream7_isr', + 'usart6_isr', + 'i2c3_ev_isr', + 'i2c3_er_isr', + 'otg_hs_ep1_out_isr', + 'otg_hs_ep1_in_isr', + 'otg_hs_wkup_isr', + 'otg_hs_isr', + 'dcmi_isr', + 'cryp_isr', + 'hash_rng_isr', +] + +with open('handlers.c', 'wt') as f: + f.write('#include "layout.h"\n') + f.write('#include "oled.h"\n\n') + for i in handlers: + f.write('void __attribute__((noreturn)) %s(void)\n' % i) + f.write('{\n') + f.write('\tlayoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Encountered", NULL, "%s", NULL, "Please restart", "the device.");\n' % i.upper()) + f.write('\tfor (;;) {} // loop forever\n') + f.write('}\n\n') diff --git a/legacy/gen/strwidth.c b/legacy/gen/strwidth.c new file mode 100644 index 0000000000..8ba3f1c367 --- /dev/null +++ b/legacy/gen/strwidth.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#include "fonts.h" + +static inline char convert(char c) { + if (c < 0x80) { + return c; + } else if (c >= 0xC0) { + return '_'; + } else { + return '\0'; + } +} + +int main(int argc, char **argv) { + char *line; + int font = FONT_STANDARD; + while ((line = readline(NULL)) != NULL) { + size_t length = strlen(line); + if (length) { + add_history(line); + } + + size_t width = 0; + for (size_t i = 0; i < length; i++) { + width += fontCharWidth(font, convert(line[i])) + 1; + } + + printf("%zu\n", width); + free(line); + } +} diff --git a/legacy/gen/wordlist/build-recovery-table.pl b/legacy/gen/wordlist/build-recovery-table.pl new file mode 100644 index 0000000000..60d12eb8df --- /dev/null +++ b/legacy/gen/wordlist/build-recovery-table.pl @@ -0,0 +1,110 @@ +#!/usr/bin/perl +print <<'EOF'; +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2016 Jochen Hoenicke + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +EOF + +my @arr1; +my @arr2; +my $x = 0; +my $l = "00"; +my @words; +while (<>) { + $_ =~ /([1-9]{2})[1-9] ([1-6]):(.*)/; + my $n = $1; + my $c = $2; + my @nw = split(",", $3); + die if $c != @nw; + die if $c > 6; + push @words, @nw; + if ($n ne $l) { + $len = @arr2; + die if $len - $arr1[-1] > 9; + push @arr1, $len; + } + push @arr2, $x; + $x += $c; + $l = $n; +} +$len = @arr2; +push @arr1, $len; +push @arr2, $x; + +sub computerange($$$) { + my ($i1, $i2, $entries) = @_; + $prev = $i1 == 0 ? "_" : $words[$i1 - 1]; + $first = $words[$i1]; + $last = $words[$i2-1]; + $next = $i2 == @words ? "_" : $words[$i2]; + my $j; + for ($j = 0; $j < 5; $j++) { + last if substr($first, 0, $j+1) ne substr($last, 0, $j+1); + last if substr($prev, 0, $j) ne substr($first, 0, $j) + && substr($next, 0, $j) ne substr($last, 0, $j); + } + $prefix = substr($first, 0, $j); + $range = ""; + $rng = 0; + if (substr($prev, 0, $j) eq substr($first, 0, $j) + || substr($last, 0, $j) eq substr($next, 0, $j)) { + $range = "[".substr($first, $j, 1) . "-". substr($last, $j, 1)."]"; + $rng++; + if ($j <= 1) { + $range = substr($first,0, $j+1)."-".substr($last,0,$j+1); + $prefix= ""; + } + } + if (substr($prev, 0, $j+1) eq substr($first, 0, $j+1) + || substr($last, 0, $j+1) eq substr($next, 0, $j+1)) { + $j = 0; $rng = 2; + } + #printf STDERR " # %1d: %9s - %9s = \U$prefix$range\E\n", $entries, $first, $last; + return $j + $rng; +} + +print << 'EOF'; +/* DO NOT EDIT: This file is automatically generated by + * cd ../gen/wordlist + * perl build-recoverytable.pl recovery_english.txt + */ + +EOF + +$len = @arr1; +print "static const uint16_t word_table1[$len] =\n"; +print "{"; +for ($i = 0; $i< @arr1; $i++) { + print "\n " if ($i % 9 == 0); + $prefixlen = computerange($arr2[$arr1[$i]], $arr2[$arr1[$i+1]], $arr1[$i+1]-$arr1[$i]); + $prefixlen = 0 if ($i == @arr1 - 1); + printf(" %5d,", $arr1[$i] + 4096 * $prefixlen); +} +print "\n};\n\n"; + +$len = @arr2; +print "static const uint16_t word_table2[$len] =\n"; +print "{"; +for ($i = 0; $i< @arr2; $i++) { + print "\n " if ($i % 9 == 0); + $prefixlen = computerange($arr2[$i], $arr2[$i+1], $arr2[$i+1]-$arr2[$i]); + $prefixlen = 0 if ($i == @arr2 - 1); + printf(" %5d,", $arr2[$i] + 4096 * $prefixlen); +} +print "\n};\n"; diff --git a/legacy/gen/wordlist/recovery_english.txt b/legacy/gen/wordlist/recovery_english.txt new file mode 100644 index 0000000000..ab85f326bf --- /dev/null +++ b/legacy/gen/wordlist/recovery_english.txt @@ -0,0 +1,630 @@ +111 5:abandon,ability,able,about,above +112 4:absent,absorb,abstract,absurd +113 1:abuse +114 4:access,accident,account,accuse +115 2:achieve,acid +116 2:acoustic,acquire +117 1:across +118 5:act,action,actor,actress,actual +121 1:adapt +122 3:add,addict,address +123 5:adjust,admit,adult,advance,advice +124 1:aerobic +125 3:affair,afford,afraid +126 4:again,age,agent,agree +127 1:ahead +128 4:aim,air,airport,aisle +131 3:alarm,album,alcohol +132 5:alert,alien,all,alley,allow +133 3:almost,alone,alpha +134 4:already,also,alter,always +135 5:amateur,amazing,among,amount,amused +136 3:analyst,anchor,ancient +137 4:anger,angle,angry,animal +138 4:ankle,announce,annual,another +139 5:answer,antenna,antique,anxiety,any +141 6:apart,apology,appear,apple,approve,april +142 2:arch,arctic +143 2:area,arena +144 1:argue +145 4:arm,armed,armor,army +146 1:around +147 4:arrange,arrest,arrive,arrow +148 4:art,artefact,artist,artwork +151 2:ask,aspect +152 4:assault,asset,assist,assume +153 1:asthma +154 6:athlete,atom,attack,attend,attitude,attract +155 3:auction,audit,august +156 4:aunt,author,auto,autumn +157 3:average,avocado,avoid +158 6:awake,aware,away,awesome,awful,awkward +159 1:axis +161 4:baby,bachelor,bacon,badge +162 5:bag,balance,balcony,ball,bamboo +163 6:banana,banner,bar,barely,bargain,barrel +164 4:base,basic,basket,battle +165 5:beach,bean,beauty,because,become +166 3:beef,before,begin +167 5:behave,behind,believe,below,belt +168 3:bench,benefit,best +169 4:betray,better,between,beyond +171 2:bicycle,bid +172 3:bike,bind,biology +173 3:bird,birth,bitter +174 5:black,blade,blame,blanket,blast +175 3:bleak,bless,blind +176 3:blood,blossom,blouse +177 3:blue,blur,blush +181 4:board,boat,body,boil +182 5:bomb,bone,bonus,book,boost +183 4:border,boring,borrow,boss +184 4:bottom,bounce,box,boy +185 5:bracket,brain,brand,brass,brave +186 2:bread,breeze +187 6:brick,bridge,brief,bright,bring,brisk +188 6:broccoli,broken,bronze,broom,brother,brown +189 1:brush +191 5:bubble,buddy,budget,buffalo,build +192 3:bulb,bulk,bullet +193 2:bundle,bunker +194 3:burden,burger,burst +195 3:bus,business,busy +196 3:butter,buyer,buzz +211 3:cabbage,cabin,cable +212 1:cactus +213 1:cage +214 1:cake +215 2:call,calm +216 2:camera,camp +221 4:can,canal,cancel,candy +222 4:cannon,canoe,canvas,canyon +223 3:capable,capital,captain +224 4:car,carbon,card,cargo +225 3:carpet,carry,cart +226 5:case,cash,casino,castle,casual +227 5:cat,catalog,catch,category,cattle +228 3:caught,cause,caution +229 1:cave +231 1:ceiling +232 1:celery +233 1:cement +234 2:census,century +235 2:cereal,certain +241 3:chair,chalk,champion +243 6:change,chaos,chapter,charge,chase,chat +245 6:cheap,check,cheese,chef,cherry,chest +246 4:chicken,chief,child,chimney +247 2:choice,choose +248 1:chronic +249 3:chuckle,chunk,churn +251 1:cigar +252 1:cinnamon +253 1:circle +254 2:citizen,city +255 1:civil +261 5:claim,clap,clarify,claw,clay +262 3:clean,clerk,clever +263 6:click,client,cliff,climb,clinic,clip +264 6:clock,clog,close,cloth,cloud,clown +265 4:club,clump,cluster,clutch +271 5:coach,coast,coconut,code,coffee +272 5:coil,coin,collect,color,column +273 6:combine,come,comfort,comic,common,company +274 4:concert,conduct,confirm,congress +275 4:connect,consider,control,convince +276 4:cook,cool,copper,copy +277 6:coral,core,corn,correct,cost,cotton +278 5:couch,country,couple,course,cousin +279 2:cover,coyote +281 4:crack,cradle,craft,cram +283 5:crane,crash,crater,crawl,crazy +285 4:cream,credit,creek,crew +286 4:cricket,crime,crisp,critic +287 4:crop,cross,crouch,crowd +288 6:crucial,cruel,cruise,crumble,crunch,crush +289 2:cry,crystal +291 1:cube +292 1:culture +293 2:cup,cupboard +294 4:curious,current,curtain,curve +295 2:cushion,custom +296 1:cute +299 1:cycle +311 1:dad +312 2:damage,damp +313 2:dance,danger +314 1:daring +315 1:dash +316 1:daughter +317 1:dawn +318 1:day +321 1:deal +322 2:debate,debris +323 6:decade,december,decide,decline,decorate,decrease +324 1:deer +325 3:defense,define,defy +326 1:degree +327 2:delay,deliver +328 2:demand,demise +331 3:denial,dentist,deny +332 5:depart,depend,deposit,depth,deputy +333 1:derive +334 6:describe,desert,design,desk,despair,destroy +335 2:detail,detect +336 3:develop,device,devote +341 4:diagram,dial,diamond,diary +342 3:dice,diesel,diet +343 4:differ,digital,dignity,dilemma +344 2:dinner,dinosaur +345 2:direct,dirt +346 5:disagree,discover,disease,dish,dismiss +347 3:disorder,display,distance +348 3:divert,divide,divorce +349 1:dizzy +351 2:doctor,document +352 1:dog +353 2:doll,dolphin +354 1:domain +355 3:donate,donkey,donor +356 1:door +357 1:dose +358 1:double +359 1:dove +361 5:draft,dragon,drama,drastic,draw +362 2:dream,dress +363 5:drift,drill,drink,drip,drive +364 3:drop,drum,dry +365 2:duck,dumb +366 5:dune,during,dust,dutch,duty +368 1:dwarf +369 1:dynamic +371 2:eager,eagle +372 6:early,earn,earth,easily,east,easy +373 6:echo,ecology,economy,edge,edit,educate +374 4:effort,egg,eight,either +375 2:elbow,elder +376 5:electric,elegant,element,elephant,elevator +377 2:elite,else +378 4:embark,embody,embrace,emerge +379 4:emotion,employ,empower,empty +381 2:enable,enact +382 3:end,endless,endorse +383 5:enemy,energy,enforce,engage,engine +384 3:enhance,enjoy,enlist +385 4:enough,enrich,enroll,ensure +386 4:enter,entire,entry,envelope +387 3:episode,equal,equip +388 6:era,erase,erode,erosion,error,erupt +389 4:escape,essay,essence,estate +391 2:eternal,ethics +392 4:evidence,evil,evoke,evolve +393 2:exact,example +394 5:excess,exchange,excite,exclude,excuse +395 4:execute,exercise,exhaust,exhibit +396 4:exile,exist,exit,exotic +397 6:expand,expect,expire,explain,expose,express +398 2:extend,extra +399 2:eye,eyebrow +411 3:fabric,face,faculty +412 1:fade +413 2:faint,faith +414 2:fall,false +415 3:fame,family,famous +416 3:fan,fancy,fantasy +417 2:farm,fashion +418 4:fat,fatal,father,fatigue +419 2:fault,favorite +421 3:feature,february,federal +422 3:fee,feed,feel +423 6:female,fence,festival,fetch,fever,few +431 4:fiber,fiction,field,figure +432 3:file,film,filter +433 5:final,find,fine,finger,finish +434 3:fire,firm,first +435 5:fiscal,fish,fit,fitness,fix +441 5:flag,flame,flash,flat,flavor +442 3:flee,flight,flip +443 4:float,flock,floor,flower +444 3:fluid,flush,fly +445 4:foam,focus,fog,foil +446 4:fold,follow,food,foot +447 4:force,forest,forget,fork +448 3:fortune,forum,forward +449 4:fossil,foster,found,fox +451 2:fragile,frame +452 2:frequent,fresh +453 2:friend,fringe +454 5:frog,front,frost,frown,frozen +455 1:fruit +458 6:fuel,fun,funny,furnace,fury,future +461 2:gadget,gain +462 2:galaxy,gallery +463 2:game,gap +464 5:garage,garbage,garden,garlic,garment +465 2:gas,gasp +466 2:gate,gather +467 2:gauge,gaze +471 6:general,genius,genre,gentle,genuine,gesture +472 1:ghost +473 1:giant +474 1:gift +475 1:giggle +476 1:ginger +477 2:giraffe,girl +478 1:give +481 4:glad,glance,glare,glass +482 2:glide,glimpse +483 5:globe,gloom,glory,glove,glow +484 1:glue +486 2:goat,goddess +487 3:gold,good,goose +488 3:gorilla,gospel,gossip +489 2:govern,gown +491 3:grab,grace,grain +492 4:grant,grape,grass,gravity +493 2:great,green +494 3:grid,grief,grit +495 3:grocery,group,grow +496 1:grunt +497 6:guard,guess,guide,guilt,guitar,gun +498 1:gym +511 2:habit,hair +512 3:half,hammer,hamster +513 2:hand,happy +514 4:harbor,hard,harsh,harvest +515 4:hat,have,hawk,hazard +516 4:head,health,heart,heavy +517 2:hedgehog,height +518 3:hello,helmet,help +519 2:hen,hero +521 2:hidden,high +522 2:hill,hint +523 3:hip,hire,history +524 2:hobby,hockey +525 4:hold,hole,holiday,hollow +526 4:home,honey,hood,hope +527 3:horn,horror,horse +528 5:hospital,host,hotel,hour,hover +531 1:hub +532 1:huge +533 3:human,humble,humor +534 3:hundred,hungry,hunt +535 3:hurdle,hurry,hurt +536 1:husband +539 1:hybrid +541 2:ice,icon +542 3:idea,identify,idle +543 1:ignore +544 3:ill,illegal,illness +545 1:image +546 1:imitate +547 2:immense,immune +548 4:impact,impose,improve,impulse +551 4:inch,include,income,increase +552 4:index,indicate,indoor,industry +553 3:infant,inflict,inform +554 3:inhale,inherit,initial +555 3:inject,injury,inmate +556 4:inner,innocent,input,inquiry +557 5:insane,insect,inside,inspire,install +558 6:intact,interest,into,invest,invite,involve +559 6:iron,island,isolate,issue,item,ivory +561 4:jacket,jaguar,jar,jazz +562 4:jealous,jeans,jelly,jewel +563 5:job,join,joke,journey,joy +564 1:judge +565 1:juice +566 1:jump +567 3:jungle,junior,junk +568 1:just +571 1:kangaroo +572 4:keen,keep,ketchup,key +573 1:kick +574 2:kid,kidney +575 2:kind,kingdom +576 1:kiss +577 4:kit,kitchen,kite,kitten +578 1:kiwi +579 4:knee,knife,knock,know +581 5:lab,label,labor,ladder,lady +582 3:lake,lamp,language +583 4:laptop,large,later,latin +584 3:laugh,laundry,lava +585 5:law,lawn,lawsuit,layer,lazy +586 4:leader,leaf,learn,leave +587 5:lecture,left,leg,legal,legend +588 5:leisure,lemon,lend,length,lens +589 4:leopard,lesson,letter,level +591 4:liar,liberty,library,license +592 4:life,lift,light,like +593 4:limb,limit,link,lion +594 5:liquid,list,little,live,lizard +595 5:load,loan,lobster,local,lock +596 4:logic,lonely,long,loop +597 5:lottery,loud,lounge,love,loyal +598 6:lucky,luggage,lumber,lunar,lunch,luxury +599 1:lyrics +611 4:machine,mad,magic,magnet +612 3:maid,mail,main +613 3:major,make,mammal +614 6:man,manage,mandate,mango,mansion,manual +615 1:maple +616 6:marble,march,margin,marine,market,marriage +617 3:mask,mass,master +618 5:match,material,math,matrix,matter +619 2:maximum,maze +621 4:meadow,mean,measure,meat +622 1:mechanic +623 2:medal,media +624 2:melody,melt +625 2:member,memory +626 2:mention,menu +627 4:mercy,merge,merit,merry +628 2:mesh,message +629 2:metal,method +631 2:middle,midnight +632 2:milk,million +633 1:mimic +634 4:mind,minimum,minor,minute +635 2:miracle,mirror +636 3:misery,miss,mistake +637 3:mix,mixed,mixture +641 5:mobile,model,modify,mom,moment +642 4:monitor,monkey,monster,month +643 4:moon,moral,more,morning +644 4:mosquito,mother,motion,motor +645 4:mountain,mouse,move,movie +646 4:much,muffin,mule,multiply +647 5:muscle,museum,mushroom,music,must +648 1:mutual +649 3:myself,mystery,myth +651 2:naive,name +652 2:napkin,narrow +653 3:nasty,nation,nature +654 2:near,neck +655 3:need,negative,neglect +656 2:neither,nephew +657 2:nerve,nest +658 3:net,network,neutral +659 3:never,news,next +661 2:nice,night +662 4:noble,noise,nominee,noodle +663 2:normal,north +664 1:nose +665 4:notable,note,nothing,notice +666 2:novel,now +669 4:nuclear,number,nurse,nut +671 1:oak +672 3:obey,object,oblige +673 4:obscure,observe,obtain,obvious +675 3:occur,ocean,october +676 1:odor +677 4:off,offer,office,often +678 1:oil +679 1:okay +681 3:old,olive,olympic +682 1:omit +683 5:once,one,onion,online,only +684 5:open,opera,opinion,oppose,option +691 6:orange,orbit,orchard,order,ordinary,organ +692 3:orient,original,orphan +693 1:ostrich +694 1:other +695 4:outdoor,outer,output,outside +696 3:oval,oven,over +697 2:own,owner +698 3:oxygen,oyster,ozone +711 6:pact,paddle,page,pair,palace,palm +712 5:panda,panel,panic,panther,paper +713 6:parade,parent,park,parrot,party,pass +714 5:patch,path,patient,patrol,pattern +715 3:pause,pave,payment +716 4:peace,peanut,pear,peasant +717 5:pelican,pen,penalty,pencil,people +718 5:pepper,perfect,permit,person,pet +719 4:phone,photo,phrase,physical +721 4:piano,picnic,picture,piece +722 5:pig,pigeon,pill,pilot,pink +723 5:pioneer,pipe,pistol,pitch,pizza +724 5:place,planet,plastic,plate,play +725 5:please,pledge,pluck,plug,plunge +726 3:poem,poet,point +727 5:polar,pole,police,pond,pony +728 6:pool,popular,portion,position,possible,post +729 5:potato,pottery,poverty,powder,power +731 2:practice,praise +732 6:predict,prefer,prepare,present,pretty,prevent +733 3:price,pride,primary +735 5:print,priority,prison,private,prize +736 4:problem,process,produce,profit +737 5:program,project,promote,proof,property +739 4:prosper,protect,proud,provide +741 2:public,pudding +742 3:pull,pulp,pulse +743 2:pumpkin,punch +744 2:pupil,puppy +745 4:purchase,purity,purpose,purse +746 3:push,put,puzzle +749 1:pyramid +751 3:quality,quantum,quarter +752 1:question +753 3:quick,quit,quiz +754 1:quote +761 1:rabbit +762 3:raccoon,race,rack +763 2:radar,radio +764 3:rail,rain,raise +765 2:rally,ramp +766 3:ranch,random,range +767 2:rapid,rare +768 2:rate,rather +769 3:raven,raw,razor +771 3:ready,real,reason +772 2:rebel,rebuild +773 5:recall,receive,recipe,record,recycle +774 1:reduce +775 3:reflect,reform,refuse +776 3:region,regret,regular +777 1:reject +778 4:relax,release,relief,rely +779 4:remain,remember,remind,remove +781 3:render,renew,rent +782 1:reopen +783 4:repair,repeat,replace,report +784 1:require +785 6:rescue,resemble,resist,resource,response,result +786 3:retire,retreat,return +787 1:reunion +788 2:reveal,review +789 1:reward +791 1:rhythm +792 6:rib,ribbon,rice,rich,ride,ridge +793 5:rifle,right,rigid,ring,riot +794 5:ripple,risk,ritual,rival,river +795 5:road,roast,robot,robust,rocket +796 5:romance,roof,rookie,room,rose +797 5:rotate,rough,round,route,royal +798 3:rubber,rude,rug +799 4:rule,run,runway,rural +811 5:sad,saddle,sadness,safe,sail +812 5:salad,salmon,salon,salt,salute +813 3:same,sample,sand +814 4:satisfy,satoshi,sauce,sausage +815 2:save,say +816 4:scale,scan,scare,scatter +817 3:scene,scheme,school +818 4:science,scissors,scorpion,scout +819 4:scrap,screen,script,scrub +821 4:sea,search,season,seat +822 4:second,secret,section,security +823 6:seed,seek,segment,select,sell,seminar +824 3:senior,sense,sentence +825 6:series,service,session,settle,setup,seven +831 4:shadow,shaft,shallow,share +832 3:shed,shell,sheriff +833 5:shield,shift,shine,ship,shiver +834 2:shock,shoe +836 5:shoot,shop,short,shoulder,shove +837 2:shrimp,shrug +838 1:shuffle +839 1:shy +841 4:sibling,sick,side,siege +842 6:sight,sign,silent,silk,silly,silver +843 4:similar,simple,since,sing +844 5:siren,sister,situate,six,size +845 2:skate,sketch +846 5:ski,skill,skin,skirt,skull +847 4:slab,slam,sleep,slender +848 4:slice,slide,slight,slim +849 4:slogan,slot,slow,slush +851 5:small,smart,smile,smoke,smooth +852 5:snack,snake,snap,sniff,snow +853 1:soap +854 3:soccer,social,sock +855 2:soda,soft +856 5:solar,soldier,solid,solution,solve +857 3:someone,song,soon +858 2:sorry,sort +859 5:soul,sound,soup,source,south +861 4:space,spare,spatial,spawn +862 5:speak,special,speed,spell,spend +863 1:sphere +864 5:spice,spider,spike,spin,spirit +865 1:split +866 5:spoil,sponsor,spoon,sport,spot +867 3:spray,spread,spring +868 1:spy +869 3:square,squeeze,squirrel +871 6:stable,stadium,staff,stage,stairs,stamp +872 4:stand,start,state,stay +873 5:steak,steel,stem,step,stereo +874 3:stick,still,sting +875 6:stock,stomach,stone,stool,story,stove +876 5:strategy,street,strike,strong,struggle +877 3:student,stuff,stumble +878 1:style +881 3:subject,submit,subway +882 2:success,such +883 4:sudden,suffer,sugar,suggest +884 2:suit,summer +885 3:sun,sunny,sunset +886 3:super,supply,supreme +887 6:sure,surface,surge,surprise,surround,survey +888 2:suspect,sustain +891 4:swallow,swamp,swap,swarm +892 2:swear,sweet +893 4:swift,swim,swing,switch +894 1:sword +898 4:symbol,symptom,syrup,system +911 4:table,tackle,tag,tail, +912 5:talent,talk,tank,tape,target +913 4:task,taste,tattoo,taxi +914 3:teach,team,tell +915 4:ten,tenant,tennis,tent +916 3:term,test,text +921 2:thank,that +922 5:theme,then,theory,there,they +923 3:thing,this,thought +924 3:three,thrive,throw +925 2:thumb,thunder +926 2:ticket,tide +927 4:tiger,tilt,timber,time +928 2:tiny,tip +929 3:tired,tissue,title +931 4:toast,tobacco,today,toddler +932 3:toe,together,toilet +933 3:token,tomato,tomorrow +934 3:tone,tongue,tonight +935 2:tool,tooth +936 3:top,topic,topple +937 3:torch,tornado,tortoise +938 3:toss,total,tourist +939 4:toward,tower,town,toy +941 5:track,trade,traffic,tragic,train +942 5:transfer,trap,trash,travel,tray +943 3:treat,tree,trend +944 6:trial,tribe,trick,trigger,trim,trip +945 2:trophy,trouble +946 6:truck,true,truly,trumpet,trust,truth +947 1:try +951 5:tube,tuition,tumble,tuna,tunnel +952 3:turkey,turn,turtle +953 6:twelve,twenty,twice,twin,twist,two +954 2:type,typical +961 2:ugly,umbrella +962 4:unable,unaware,uncle,uncover +963 5:under,undo,unfair,unfold,unhappy +964 4:uniform,unique,unit,universe +965 5:unknown,unlock,until,unusual,unveil +966 6:update,upgrade,uphold,upon,upper,upset +967 2:urban,urge +968 6:usage,use,used,useful,useless,usual +969 1:utility +971 6:vacant,vacuum,vague,valid,valley,valve +972 6:van,vanish,vapor,various,vast,vault +973 5:vehicle,velvet,vendor,venture,venue +974 6:verb,verify,version,very,vessel,veteran +975 5:viable,vibrant,vicious,victory,video +976 6:view,village,vintage,violin,virtual,virus +977 5:visa,visit,visual,vital,vivid +978 3:vocal,voice,void +979 4:volcano,volume,vote,voyage +981 3:wage,wagon,wait +982 4:walk,wall,walnut,want +983 3:warfare,warm,warrior +984 6:wash,wasp,waste,water,wave,way +985 5:wealth,weapon,wear,weasel,weather +986 3:web,wedding,weekend +987 4:weird,welcome,west,wet +988 6:whale,what,wheat,wheel,when,where +989 2:whip,whisper +991 5:wide,width,wife,wild,will +992 5:win,window,wine,wing,wink +993 2:winner,winter +994 5:wire,wisdom,wise,wish,witness +995 5:wolf,woman,wonder,wood,wool +996 5:word,work,world,worry,worth +997 6:wrap,wreck,wrestle,wrist,write,wrong +998 6:yard,year,yellow,you,young,youth +999 4:zebra,zero,zone,zoo diff --git a/legacy/gitian/gitian.yml b/legacy/gitian/gitian.yml new file mode 100644 index 0000000000..2f24b9777f --- /dev/null +++ b/legacy/gitian/gitian.yml @@ -0,0 +1,19 @@ +--- +name: "trezor-mcu" +enable_cache: true +suites: +- "trusty" +architectures: +- "amd64" +packages: +- "build-essential" +- "gcc-arm-none-eabi" +reference_datetime: "2015-06-01 00:00:00" +remotes: +- "url": "https://github.com/trezor/trezor-mcu.git" + "dir": "trezor-mcu" +files: [] +script: | + make -C vendor/libopencm3 + make + make -C firmware diff --git a/legacy/layout.c b/legacy/layout.c new file mode 100644 index 0000000000..b2a37e177b --- /dev/null +++ b/legacy/layout.c @@ -0,0 +1,123 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "layout.h" +#include "oled.h" + +void layoutButtonNo(const char *btnNo) { + oledDrawString(1, OLED_HEIGHT - 8, "\x15", FONT_STANDARD); + oledDrawString(fontCharWidth(FONT_STANDARD, '\x15') + 3, OLED_HEIGHT - 8, + btnNo, FONT_STANDARD); + oledInvert(0, OLED_HEIGHT - 9, + fontCharWidth(FONT_STANDARD, '\x15') + + oledStringWidth(btnNo, FONT_STANDARD) + 2, + OLED_HEIGHT - 1); +} + +void layoutButtonYes(const char *btnYes) { + oledDrawString(OLED_WIDTH - fontCharWidth(FONT_STANDARD, '\x06') - 1, + OLED_HEIGHT - 8, "\x06", FONT_STANDARD); + oledDrawStringRight(OLED_WIDTH - fontCharWidth(FONT_STANDARD, '\x06') - 3, + OLED_HEIGHT - 8, btnYes, FONT_STANDARD); + oledInvert(OLED_WIDTH - oledStringWidth(btnYes, FONT_STANDARD) - + fontCharWidth(FONT_STANDARD, '\x06') - 4, + OLED_HEIGHT - 9, OLED_WIDTH - 1, OLED_HEIGHT - 1); +} + +void layoutDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, + const char *desc, const char *line1, const char *line2, + const char *line3, const char *line4, const char *line5, + const char *line6) { + int left = 0; + oledClear(); + if (icon) { + oledDrawBitmap(0, 0, icon); + left = icon->width + 4; + } + if (line1) oledDrawString(left, 0 * 9, line1, FONT_STANDARD); + if (line2) oledDrawString(left, 1 * 9, line2, FONT_STANDARD); + if (line3) oledDrawString(left, 2 * 9, line3, FONT_STANDARD); + if (line4) oledDrawString(left, 3 * 9, line4, FONT_STANDARD); + if (desc) { + oledDrawStringCenter(OLED_WIDTH / 2, OLED_HEIGHT - 2 * 9 - 1, desc, + FONT_STANDARD); + if (btnYes || btnNo) { + oledHLine(OLED_HEIGHT - 21); + } + } else { + if (line5) oledDrawString(left, 4 * 9, line5, FONT_STANDARD); + if (line6) oledDrawString(left, 5 * 9, line6, FONT_STANDARD); + if (btnYes || btnNo) { + oledHLine(OLED_HEIGHT - 13); + } + } + if (btnNo) { + layoutButtonNo(btnNo); + } + if (btnYes) { + layoutButtonYes(btnYes); + } + oledRefresh(); +} + +void layoutProgressUpdate(bool refresh) { + static uint8_t step = 0; + switch (step) { + case 0: + oledDrawBitmap(40, 0, &bmp_gears0); + break; + case 1: + oledDrawBitmap(40, 0, &bmp_gears1); + break; + case 2: + oledDrawBitmap(40, 0, &bmp_gears2); + break; + case 3: + oledDrawBitmap(40, 0, &bmp_gears3); + break; + } + step = (step + 1) % 4; + if (refresh) { + oledRefresh(); + } +} + +void layoutProgress(const char *desc, int permil) { + oledClear(); + layoutProgressUpdate(false); + // progressbar + oledFrame(0, OLED_HEIGHT - 8, OLED_WIDTH - 1, OLED_HEIGHT - 1); + oledBox(1, OLED_HEIGHT - 7, OLED_WIDTH - 2, OLED_HEIGHT - 2, 0); + permil = permil * (OLED_WIDTH - 4) / 1000; + if (permil < 0) { + permil = 0; + } + if (permil > OLED_WIDTH - 4) { + permil = OLED_WIDTH - 4; + } + oledBox(2, OLED_HEIGHT - 6, 1 + permil, OLED_HEIGHT - 3, 1); + // text + oledBox(0, OLED_HEIGHT - 16, OLED_WIDTH - 1, OLED_HEIGHT - 16 + 7, 0); + if (desc) { + oledDrawStringCenter(OLED_WIDTH / 2, OLED_HEIGHT - 16, desc, FONT_STANDARD); + } + oledRefresh(); +} diff --git a/legacy/layout.h b/legacy/layout.h new file mode 100644 index 0000000000..2f662c657f --- /dev/null +++ b/legacy/layout.h @@ -0,0 +1,36 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __LAYOUT_H__ +#define __LAYOUT_H__ + +#include +#include +#include "bitmaps.h" + +void layoutButtonNo(const char *btnNo); +void layoutButtonYes(const char *btnYes); +void layoutDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, + const char *desc, const char *line1, const char *line2, + const char *line3, const char *line4, const char *line5, + const char *line6); +void layoutProgressUpdate(bool refresh); +void layoutProgress(const char *desc, int permil); + +#endif diff --git a/legacy/memory.c b/legacy/memory.c new file mode 100644 index 0000000000..2eaa4368f6 --- /dev/null +++ b/legacy/memory.c @@ -0,0 +1,80 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "memory.h" +#include +#include +#include "sha2.h" + +#define FLASH_OPTION_BYTES_1 (*(const uint64_t *)0x1FFFC000) +#define FLASH_OPTION_BYTES_2 (*(const uint64_t *)0x1FFFC008) + +void memory_protect(void) { +#if MEMORY_PROTECT + // Reference STM32F205 Flash programming manual revision 5 + // http://www.st.com/resource/en/programming_manual/cd00233952.pdf Section 2.6 + // Option bytes + // set RDP level 2 WRP for sectors 0 and + // 1 flash option control register matches + if (((FLASH_OPTION_BYTES_1 & 0xFFEC) == 0xCCEC) && + ((FLASH_OPTION_BYTES_2 & 0xFFF) == 0xFFC) && + (FLASH_OPTCR == 0x0FFCCCED)) { + return; // already set up correctly - bail out + } + for (int i = FLASH_STORAGE_SECTOR_FIRST; i <= FLASH_STORAGE_SECTOR_LAST; + i++) { + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + flash_unlock_option_bytes(); + // Section 2.8.6 Flash option control register (FLASH_OPTCR) + // Bits 31:28 Reserved, must be kept cleared. + // Bits 27:16 nWRP: Not write protect: write protect bootloader code in + // flash main memory sectors 0 and 1 (Section 2.3; table 2) Bits 15:8 RDP: + // Read protect: level 2 chip read protection active Bits 7:5 USER: User + // option bytes: no reset on standby, no reset on stop, software watchdog + // Bit 4 Reserved, must be kept cleared. + // Bits 3:2 BOR_LEV: BOR reset Level: BOR off + // Bit 1 OPTSTRT: Option start: ignored by flash_program_option_bytes + // Bit 0 OPTLOCK: Option lock: ignored by flash_program_option_bytes + flash_program_option_bytes(0x0FFCCCEC); + flash_lock_option_bytes(); +#endif +} + +// Remove write-protection on all flash sectors. +// +// This is an undocumented feature/bug of STM32F205/F405 microcontrollers, +// where flash controller reads its write protection bits from FLASH_OPTCR +// register not from OPTION_BYTES, rendering write protection useless. +// This behaviour is fixed in future designs of flash controller used for +// example in STM32F427, where the protection bits are read correctly +// from OPTION_BYTES and not form FLASH_OPCTR register. +// +// Read protection is unaffected and always stays locked to the desired value. +void memory_write_unlock(void) { + flash_unlock_option_bytes(); + flash_program_option_bytes(0x0FFFCCEC); + flash_lock_option_bytes(); +} + +int memory_bootloader_hash(uint8_t *hash) { + sha256_Raw(FLASH_PTR(FLASH_BOOT_START), FLASH_BOOT_LEN, hash); + sha256_Raw(hash, 32, hash); + return 32; +} diff --git a/legacy/memory.h b/legacy/memory.h new file mode 100644 index 0000000000..2494eff765 --- /dev/null +++ b/legacy/memory.h @@ -0,0 +1,104 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __MEMORY_H__ +#define __MEMORY_H__ + +#include + +/* + + Flash memory layout: + + name | range | size | function +-----------+-------------------------+---------+------------------ + Sector 0 | 0x08000000 - 0x08003FFF | 16 KiB | bootloader + Sector 1 | 0x08004000 - 0x08007FFF | 16 KiB | bootloader +-----------+-------------------------+---------+------------------ + Sector 2 | 0x08008000 - 0x0800BFFF | 16 KiB | storage area + Sector 3 | 0x0800C000 - 0x0800FFFF | 16 KiB | storage area +-----------+-------------------------+---------+------------------ + Sector 4 | 0x08010000 - 0x0801FFFF | 64 KiB | firmware + Sector 5 | 0x08020000 - 0x0803FFFF | 128 KiB | firmware + Sector 6 | 0x08040000 - 0x0805FFFF | 128 KiB | firmware + Sector 7 | 0x08060000 - 0x0807FFFF | 128 KiB | firmware + Sector 8 | 0x08080000 - 0x0809FFFF | 128 KiB | firmware + Sector 9 | 0x080A0000 - 0x080BFFFF | 128 KiB | firmware + Sector 10 | 0x080C0000 - 0x080DFFFF | 128 KiB | firmware + Sector 11 | 0x080E0000 - 0x080FFFFF | 128 KiB | firmware + + firmware header (occupies first 1 KB of the firmware) + - very similar to trezor-core firmware header described in: + https://github.com/trezor/trezor-core/blob/master/docs/bootloader.md#firmware-header + - differences: + * we don't use sigmask or sig field (these are reserved and set to zero) + * we introduce new fields immediately following the hash16 field: + - sig1[64], sig2[64], sig3[64] + - sigindex1[1], sigindex2[1], sigindex3[1] + * reserved[415] area is reduced to reserved[220] + - see signatures.c for more details + + We pad the firmware chunks with zeroes if they are shorted. + + */ + +#define FLASH_ORIGIN (0x08000000) + +#if EMULATOR +extern uint8_t *emulator_flash_base; +#define FLASH_PTR(x) (emulator_flash_base + (x - FLASH_ORIGIN)) +#else +#define FLASH_PTR(x) (const uint8_t *)(x) +#endif + +#define FLASH_TOTAL_SIZE (1024 * 1024) + +#define FLASH_BOOT_START (FLASH_ORIGIN) +#define FLASH_BOOT_LEN (0x8000) + +#define FLASH_STORAGE_START (FLASH_BOOT_START + FLASH_BOOT_LEN) +#define FLASH_STORAGE_LEN (0x8000) + +#define FLASH_FWHEADER_START (FLASH_STORAGE_START + FLASH_STORAGE_LEN) +#define FLASH_FWHEADER_LEN (0x400) + +#define FLASH_APP_START (FLASH_FWHEADER_START + FLASH_FWHEADER_LEN) +#define FLASH_APP_LEN (FLASH_TOTAL_SIZE - (FLASH_APP_START - FLASH_ORIGIN)) + +#define FLASH_BOOT_SECTOR_FIRST 0 +#define FLASH_BOOT_SECTOR_LAST 1 + +#define FLASH_STORAGE_SECTOR_FIRST 2 +#define FLASH_STORAGE_SECTOR_LAST 3 + +#define FLASH_CODE_SECTOR_FIRST 4 +#define FLASH_CODE_SECTOR_LAST 11 + +void memory_protect(void); +void memory_write_unlock(void); +int memory_bootloader_hash(uint8_t *hash); + +static inline void flash_write32(uint32_t addr, uint32_t word) { + *(volatile uint32_t *)FLASH_PTR(addr) = word; +} +static inline void flash_write8(uint32_t addr, uint8_t byte) { + *(volatile uint8_t *)FLASH_PTR(addr) = byte; +} + +#endif diff --git a/legacy/memory.ld b/legacy/memory.ld new file mode 100644 index 0000000000..2f94613e49 --- /dev/null +++ b/legacy/memory.ld @@ -0,0 +1,25 @@ +/* STM32F205RG - 1024K Flash, 128K RAM */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K +} + +SECTIONS +{ + .confidential (NOLOAD) : { + *(confidential) + ASSERT ((SIZEOF(.confidential) <= 32K), "Error: Confidential section too big!"); + } >ram +} + +INCLUDE cortex-m-generic.ld + +_ram_start = ORIGIN(ram); +_ram_end = ORIGIN(ram) + LENGTH(ram); +_stack = _ram_end - 8; +__stack_chk_guard = _ram_end - 8; +system_millis = _ram_end - 4; + +_data_size = SIZEOF(.data); diff --git a/legacy/memory_app_0.0.0.ld b/legacy/memory_app_0.0.0.ld new file mode 100644 index 0000000000..8f322e4e8b --- /dev/null +++ b/legacy/memory_app_0.0.0.ld @@ -0,0 +1,9 @@ +/* STM32F205RG - 1024K Flash, 128K RAM */ +/* program starts at 0x08010000 */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08004000, LENGTH = 1024K - 16K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K +} + +INCLUDE cortex-m-generic.ld diff --git a/legacy/memory_app_1.0.0.ld b/legacy/memory_app_1.0.0.ld new file mode 100644 index 0000000000..688e775627 --- /dev/null +++ b/legacy/memory_app_1.0.0.ld @@ -0,0 +1,25 @@ +/* STM32F205RG - 1024K Flash, 128K RAM */ +/* program starts at 0x08010000 */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08010000, LENGTH = 1024K - 64K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K +} + +SECTIONS +{ + .confidential (NOLOAD) : { + *(confidential) + ASSERT ((SIZEOF(.confidential) <= 32K), "Error: Confidential section too big!"); + } >ram +} + +INCLUDE cortex-m-generic.ld + +_ram_start = ORIGIN(ram); +_ram_end = ORIGIN(ram) + LENGTH(ram); +_stack = _ram_end - 8; +__stack_chk_guard = _ram_end - 8; +system_millis = _ram_end - 4; + +_data_size = SIZEOF(.data); diff --git a/legacy/memory_app_1.8.0.ld b/legacy/memory_app_1.8.0.ld new file mode 100644 index 0000000000..b11b8bf1e7 --- /dev/null +++ b/legacy/memory_app_1.8.0.ld @@ -0,0 +1,31 @@ +/* STM32F205RG - 1024K Flash, 128K RAM */ +/* program starts at 0x08010400 */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08010000, LENGTH = 1024K - 64K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K +} + +SECTIONS +{ + .confidential (NOLOAD) : { + *(confidential) + ASSERT ((SIZEOF(.confidential) <= 32K), "Error: Confidential section too big!"); + } >ram + + .header : ALIGN(4) { + KEEP(*(.header)); + } >rom AT>rom +} + +INCLUDE cortex-m-generic.ld + +_codelen = SIZEOF(.text) + SIZEOF(.data) + SIZEOF(.ARM.exidx); + +_ram_start = ORIGIN(ram); +_ram_end = ORIGIN(ram) + LENGTH(ram); +_stack = _ram_end - 8; +__stack_chk_guard = _ram_end - 8; +system_millis = _ram_end - 4; + +_data_size = SIZEOF(.data); diff --git a/legacy/memory_app_fastflash.ld b/legacy/memory_app_fastflash.ld new file mode 100644 index 0000000000..7bba7443ea --- /dev/null +++ b/legacy/memory_app_fastflash.ld @@ -0,0 +1,23 @@ +/* STM32F205RG - 1024K Flash, 128K RAM */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x20000000, LENGTH = 32K + ram (rwx) : ORIGIN = 0x20000000 + LENGTH(rom), + LENGTH = 128K - LENGTH(rom) +} + +SECTIONS +{ + .confidential (NOLOAD) : { + *(confidential) + ASSERT ((SIZEOF(.confidential) <= 32K), "Error: Confidential section too big!"); + } >ram +} + +INCLUDE cortex-m-generic.ld + +_ram_start = ORIGIN(ram); +_ram_end = ORIGIN(ram) + LENGTH(ram); + +_data_size = SIZEOF(.data); diff --git a/legacy/norcow_config.h b/legacy/norcow_config.h new file mode 100644 index 0000000000..14fc3d6667 --- /dev/null +++ b/legacy/norcow_config.h @@ -0,0 +1,41 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __NORCOW_CONFIG_H__ +#define __NORCOW_CONFIG_H__ + +#include "flash.h" + +#define NORCOW_SECTOR_COUNT 2 +#define NORCOW_SECTOR_SIZE (16 * 1024) +#define NORCOW_SECTORS \ + { 2, 3 } + +/* + * The length of the sector header in bytes. The header is preserved between + * sector erasures. + */ +#define NORCOW_HEADER_LEN (0) + +/* + * Current storage version. + */ +#define NORCOW_VERSION ((uint32_t)0x00000001) + +#endif diff --git a/legacy/oled.c b/legacy/oled.c new file mode 100644 index 0000000000..d59d880ac5 --- /dev/null +++ b/legacy/oled.c @@ -0,0 +1,415 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include + +#include + +#include "memzero.h" +#include "oled.h" +#include "util.h" + +#define OLED_SETCONTRAST 0x81 +#define OLED_DISPLAYALLON_RESUME 0xA4 +#define OLED_DISPLAYALLON 0xA5 +#define OLED_NORMALDISPLAY 0xA6 +#define OLED_INVERTDISPLAY 0xA7 +#define OLED_DISPLAYOFF 0xAE +#define OLED_DISPLAYON 0xAF +#define OLED_SETDISPLAYOFFSET 0xD3 +#define OLED_SETCOMPINS 0xDA +#define OLED_SETVCOMDETECT 0xDB +#define OLED_SETDISPLAYCLOCKDIV 0xD5 +#define OLED_SETPRECHARGE 0xD9 +#define OLED_SETMULTIPLEX 0xA8 +#define OLED_SETLOWCOLUMN 0x00 +#define OLED_SETHIGHCOLUMN 0x10 +#define OLED_SETSTARTLINE 0x40 +#define OLED_MEMORYMODE 0x20 +#define OLED_COMSCANINC 0xC0 +#define OLED_COMSCANDEC 0xC8 +#define OLED_SEGREMAP 0xA0 +#define OLED_CHARGEPUMP 0x8D + +#define SPI_BASE SPI1 +#define OLED_DC_PORT GPIOB +#define OLED_DC_PIN GPIO0 // PB0 | Data/Command +#define OLED_CS_PORT GPIOA +#define OLED_CS_PIN GPIO4 // PA4 | SPI Select +#define OLED_RST_PORT GPIOB +#define OLED_RST_PIN GPIO1 // PB1 | Reset display + +/* TREZOR has a display of size OLED_WIDTH x OLED_HEIGHT (128x64). + * The contents of this display are buffered in _oledbuffer. This is + * an array of OLED_WIDTH * OLED_HEIGHT/8 bytes. At byte y*OLED_WIDTH + x + * it stores the column of pixels from (x,8y) to (x,8y+7); the LSB stores + * the top most pixel. The pixel (0,0) is the top left corner of the + * display. + */ + +static uint8_t _oledbuffer[OLED_BUFSIZE]; +static bool is_debug_link = 0; + +/* + * macros to convert coordinate to bit position + */ +#define OLED_OFFSET(x, y) (OLED_BUFSIZE - 1 - (x) - ((y) / 8) * OLED_WIDTH) +#define OLED_MASK(x, y) (1 << (7 - (y) % 8)) + +/* + * Draws a white pixel at x, y + */ +void oledDrawPixel(int x, int y) { + if ((x < 0) || (y < 0) || (x >= OLED_WIDTH) || (y >= OLED_HEIGHT)) { + return; + } + _oledbuffer[OLED_OFFSET(x, y)] |= OLED_MASK(x, y); +} + +/* + * Clears pixel at x, y + */ +void oledClearPixel(int x, int y) { + if ((x < 0) || (y < 0) || (x >= OLED_WIDTH) || (y >= OLED_HEIGHT)) { + return; + } + _oledbuffer[OLED_OFFSET(x, y)] &= ~OLED_MASK(x, y); +} + +/* + * Inverts pixel at x, y + */ +void oledInvertPixel(int x, int y) { + if ((x < 0) || (y < 0) || (x >= OLED_WIDTH) || (y >= OLED_HEIGHT)) { + return; + } + _oledbuffer[OLED_OFFSET(x, y)] ^= OLED_MASK(x, y); +} + +#if !EMULATOR +/* + * Send a block of data via the SPI bus. + */ +static inline void SPISend(uint32_t base, const uint8_t *data, int len) { + delay(1); + for (int i = 0; i < len; i++) { + spi_send(base, data[i]); + } + while (!(SPI_SR(base) & SPI_SR_TXE)) + ; + while ((SPI_SR(base) & SPI_SR_BSY)) + ; +} + +/* + * Initialize the display. + */ +void oledInit() { + static const uint8_t s[25] = {OLED_DISPLAYOFF, + OLED_SETDISPLAYCLOCKDIV, + 0x80, + OLED_SETMULTIPLEX, + 0x3F, // 128x64 + OLED_SETDISPLAYOFFSET, + 0x00, + OLED_SETSTARTLINE | 0x00, + OLED_CHARGEPUMP, + 0x14, + OLED_MEMORYMODE, + 0x00, + OLED_SEGREMAP | 0x01, + OLED_COMSCANDEC, + OLED_SETCOMPINS, + 0x12, // 128x64 + OLED_SETCONTRAST, + 0xCF, + OLED_SETPRECHARGE, + 0xF1, + OLED_SETVCOMDETECT, + 0x40, + OLED_DISPLAYALLON_RESUME, + OLED_NORMALDISPLAY, + OLED_DISPLAYON}; + + gpio_clear(OLED_DC_PORT, OLED_DC_PIN); // set to CMD + gpio_set(OLED_CS_PORT, OLED_CS_PIN); // SPI deselect + + // Reset the LCD + gpio_set(OLED_RST_PORT, OLED_RST_PIN); + delay(40); + gpio_clear(OLED_RST_PORT, OLED_RST_PIN); + delay(400); + gpio_set(OLED_RST_PORT, OLED_RST_PIN); + + // init + gpio_clear(OLED_CS_PORT, OLED_CS_PIN); // SPI select + SPISend(SPI_BASE, s, 25); + gpio_set(OLED_CS_PORT, OLED_CS_PIN); // SPI deselect + + oledClear(); + oledRefresh(); +} +#endif + +/* + * Clears the display buffer (sets all pixels to black) + */ +void oledClear() { memzero(_oledbuffer, sizeof(_oledbuffer)); } + +void oledInvertDebugLink() { + if (is_debug_link) { + oledInvertPixel(OLED_WIDTH - 5, 0); + oledInvertPixel(OLED_WIDTH - 4, 0); + oledInvertPixel(OLED_WIDTH - 3, 0); + oledInvertPixel(OLED_WIDTH - 2, 0); + oledInvertPixel(OLED_WIDTH - 1, 0); + oledInvertPixel(OLED_WIDTH - 4, 1); + oledInvertPixel(OLED_WIDTH - 3, 1); + oledInvertPixel(OLED_WIDTH - 2, 1); + oledInvertPixel(OLED_WIDTH - 1, 1); + oledInvertPixel(OLED_WIDTH - 3, 2); + oledInvertPixel(OLED_WIDTH - 2, 2); + oledInvertPixel(OLED_WIDTH - 1, 2); + oledInvertPixel(OLED_WIDTH - 2, 3); + oledInvertPixel(OLED_WIDTH - 1, 3); + oledInvertPixel(OLED_WIDTH - 1, 4); + } +} + +/* + * Refresh the display. This copies the buffer to the display to show the + * contents. This must be called after every operation to the buffer to + * make the change visible. All other operations only change the buffer + * not the content of the display. + */ +#if !EMULATOR +void oledRefresh() { + static const uint8_t s[3] = {OLED_SETLOWCOLUMN | 0x00, + OLED_SETHIGHCOLUMN | 0x00, + OLED_SETSTARTLINE | 0x00}; + + // draw triangle in upper right corner + oledInvertDebugLink(); + + gpio_clear(OLED_CS_PORT, OLED_CS_PIN); // SPI select + SPISend(SPI_BASE, s, 3); + gpio_set(OLED_CS_PORT, OLED_CS_PIN); // SPI deselect + + gpio_set(OLED_DC_PORT, OLED_DC_PIN); // set to DATA + gpio_clear(OLED_CS_PORT, OLED_CS_PIN); // SPI select + SPISend(SPI_BASE, _oledbuffer, sizeof(_oledbuffer)); + gpio_set(OLED_CS_PORT, OLED_CS_PIN); // SPI deselect + gpio_clear(OLED_DC_PORT, OLED_DC_PIN); // set to CMD + + // return it back + oledInvertDebugLink(); +} +#endif + +const uint8_t *oledGetBuffer() { return _oledbuffer; } + +void oledSetDebugLink(bool set) { + is_debug_link = set; + oledRefresh(); +} + +void oledSetBuffer(uint8_t *buf) { + memcpy(_oledbuffer, buf, sizeof(_oledbuffer)); +} + +void oledDrawChar(int x, int y, char c, int font) { + if (x >= OLED_WIDTH || y >= OLED_HEIGHT || y <= -FONT_HEIGHT) { + return; + } + + int zoom = (font & FONT_DOUBLE ? 2 : 1); + int char_width = fontCharWidth(font & 0x7f, c); + const uint8_t *char_data = fontCharData(font & 0x7f, c); + + if (x <= -char_width * zoom) { + return; + } + + for (int xo = 0; xo < char_width; xo++) { + for (int yo = 0; yo < FONT_HEIGHT; yo++) { + if (char_data[xo] & (1 << (FONT_HEIGHT - 1 - yo))) { + if (zoom <= 1) { + oledDrawPixel(x + xo, y + yo); + } else { + oledBox(x + xo * zoom, y + yo * zoom, x + (xo + 1) * zoom - 1, + y + (yo + 1) * zoom - 1, true); + } + } + } + } +} + +char oledConvertChar(const char c) { + uint8_t a = c; + if (a < 0x80) return c; + // UTF-8 handling: https://en.wikipedia.org/wiki/UTF-8#Description + // bytes 11xxxxxx are first byte of UTF-8 characters + // bytes 10xxxxxx are successive UTF-8 characters + if (a >= 0xC0) return '_'; + return 0; +} + +int oledStringWidth(const char *text, int font) { + if (!text) return 0; + int size = (font & FONT_DOUBLE ? 2 : 1); + int l = 0; + for (; *text; text++) { + char c = oledConvertChar(*text); + if (c) { + l += size * (fontCharWidth(font & 0x7f, c) + 1); + } + } + return l; +} + +void oledDrawString(int x, int y, const char *text, int font) { + if (!text) return; + int l = 0; + int size = (font & FONT_DOUBLE ? 2 : 1); + for (; *text; text++) { + char c = oledConvertChar(*text); + if (c) { + oledDrawChar(x + l, y, c, font); + l += size * (fontCharWidth(font & 0x7f, c) + 1); + } + } +} + +void oledDrawStringCenter(int x, int y, const char *text, int font) { + x = x - oledStringWidth(text, font) / 2; + oledDrawString(x, y, text, font); +} + +void oledDrawStringRight(int x, int y, const char *text, int font) { + x -= oledStringWidth(text, font); + oledDrawString(x, y, text, font); +} + +void oledDrawBitmap(int x, int y, const BITMAP *bmp) { + for (int i = 0; i < bmp->width; i++) { + for (int j = 0; j < bmp->height; j++) { + if (bmp->data[(i / 8) + j * bmp->width / 8] & (1 << (7 - i % 8))) { + oledDrawPixel(x + i, y + j); + } else { + oledClearPixel(x + i, y + j); + } + } + } +} + +/* + * Inverts box between (x1,y1) and (x2,y2) inclusive. + */ +void oledInvert(int x1, int y1, int x2, int y2) { + x1 = MAX(x1, 0); + y1 = MAX(y1, 0); + x2 = MIN(x2, OLED_WIDTH - 1); + y2 = MIN(y2, OLED_HEIGHT - 1); + for (int x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y++) { + oledInvertPixel(x, y); + } + } +} + +/* + * Draw a filled rectangle. + */ +void oledBox(int x1, int y1, int x2, int y2, bool set) { + x1 = MAX(x1, 0); + y1 = MAX(y1, 0); + x2 = MIN(x2, OLED_WIDTH - 1); + y2 = MIN(y2, OLED_HEIGHT - 1); + for (int x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y++) { + set ? oledDrawPixel(x, y) : oledClearPixel(x, y); + } + } +} + +void oledHLine(int y) { + if (y < 0 || y >= OLED_HEIGHT) { + return; + } + for (int x = 0; x < OLED_WIDTH; x++) { + oledDrawPixel(x, y); + } +} + +/* + * Draw a rectangle frame. + */ +void oledFrame(int x1, int y1, int x2, int y2) { + for (int x = x1; x <= x2; x++) { + oledDrawPixel(x, y1); + oledDrawPixel(x, y2); + } + for (int y = y1 + 1; y < y2; y++) { + oledDrawPixel(x1, y); + oledDrawPixel(x2, y); + } +} + +/* + * Animates the display, swiping the current contents out to the left. + * This clears the display. + */ +void oledSwipeLeft(void) { + for (int i = 0; i < OLED_WIDTH; i++) { + for (int j = 0; j < OLED_HEIGHT / 8; j++) { + for (int k = OLED_WIDTH - 1; k > 0; k--) { + _oledbuffer[j * OLED_WIDTH + k] = _oledbuffer[j * OLED_WIDTH + k - 1]; + } + _oledbuffer[j * OLED_WIDTH] = 0; + } + oledRefresh(); + } +} + +/* + * Animates the display, swiping the current contents out to the right. + * This clears the display. + */ +void oledSwipeRight(void) { + for (int i = 0; i < OLED_WIDTH / 4; i++) { + for (int j = 0; j < OLED_HEIGHT / 8; j++) { + for (int k = 0; k < OLED_WIDTH / 4 - 1; k++) { + _oledbuffer[k * 4 + 0 + j * OLED_WIDTH] = + _oledbuffer[k * 4 + 4 + j * OLED_WIDTH]; + _oledbuffer[k * 4 + 1 + j * OLED_WIDTH] = + _oledbuffer[k * 4 + 5 + j * OLED_WIDTH]; + _oledbuffer[k * 4 + 2 + j * OLED_WIDTH] = + _oledbuffer[k * 4 + 6 + j * OLED_WIDTH]; + _oledbuffer[k * 4 + 3 + j * OLED_WIDTH] = + _oledbuffer[k * 4 + 7 + j * OLED_WIDTH]; + } + _oledbuffer[j * OLED_WIDTH + OLED_WIDTH - 1] = 0; + _oledbuffer[j * OLED_WIDTH + OLED_WIDTH - 2] = 0; + _oledbuffer[j * OLED_WIDTH + OLED_WIDTH - 3] = 0; + _oledbuffer[j * OLED_WIDTH + OLED_WIDTH - 4] = 0; + } + oledRefresh(); + } +} diff --git a/legacy/oled.h b/legacy/oled.h new file mode 100644 index 0000000000..a4c9c91ac4 --- /dev/null +++ b/legacy/oled.h @@ -0,0 +1,59 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __OLED_H__ +#define __OLED_H__ + +#include +#include + +#include "bitmaps.h" +#include "fonts.h" + +#define OLED_WIDTH 128 +#define OLED_HEIGHT 64 +#define OLED_BUFSIZE (OLED_WIDTH * OLED_HEIGHT / 8) + +void oledInit(void); +void oledClear(void); +void oledRefresh(void); + +void oledSetDebugLink(bool set); +void oledInvertDebugLink(void); + +void oledSetBuffer(uint8_t *buf); +const uint8_t *oledGetBuffer(void); +void oledDrawPixel(int x, int y); +void oledClearPixel(int x, int y); +void oledInvertPixel(int x, int y); +void oledDrawChar(int x, int y, char c, int zoom); +int oledStringWidth(const char *text, int font); + +void oledDrawString(int x, int y, const char *text, int font); +void oledDrawStringCenter(int x, int y, const char *text, int font); +void oledDrawStringRight(int x, int y, const char *text, int font); +void oledDrawBitmap(int x, int y, const BITMAP *bmp); +void oledInvert(int x1, int y1, int x2, int y2); +void oledBox(int x1, int y1, int x2, int y2, bool set); +void oledHLine(int y); +void oledFrame(int x1, int y1, int x2, int y2); +void oledSwipeLeft(void); +void oledSwipeRight(void); + +#endif diff --git a/legacy/rng.c b/legacy/rng.c new file mode 100644 index 0000000000..6266a82eaa --- /dev/null +++ b/legacy/rng.c @@ -0,0 +1,37 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include + +#include "rng.h" + +#if !EMULATOR +uint32_t random32(void) { + static uint32_t last = 0, new = 0; + while (new == last) { + if ((RNG_SR & (RNG_SR_SECS | RNG_SR_CECS | RNG_SR_DRDY)) == RNG_SR_DRDY) { + new = RNG_DR; + } + } + last = new; + return new; +} +#endif diff --git a/legacy/rng.h b/legacy/rng.h new file mode 100644 index 0000000000..c28ef22f3a --- /dev/null +++ b/legacy/rng.h @@ -0,0 +1,25 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __RNG_H__ +#define __RNG_H__ + +#include "rand.h" + +#endif diff --git a/legacy/script/bootstrap b/legacy/script/bootstrap new file mode 100755 index 0000000000..500e9a0612 --- /dev/null +++ b/legacy/script/bootstrap @@ -0,0 +1,10 @@ +#!/bin/bash + +# script/bootstrap: Resolve all dependencies that the application requires to +# run. + +set -e + +cd "$(dirname "$0")/.." + +git submodule update --init --recursive diff --git a/legacy/script/cibuild b/legacy/script/cibuild new file mode 100755 index 0000000000..b02691b702 --- /dev/null +++ b/legacy/script/cibuild @@ -0,0 +1,29 @@ +#!/bin/bash + +# script/cibuild: Setup environment for CI to run tests. This is primarily +# designed to run on the continuous integration server. + +set -e + +cd "$(dirname "$0")/.." + +if [ "$EMULATOR" = 1 ]; then + make -C emulator +else + make -C vendor/libopencm3 lib/stm32/f2 +fi + +make + +if [ "$EMULATOR" != 1 ]; then + make -C bootloader align +fi + +make -C vendor/nanopb/generator/proto +make -C firmware/protob + +make -C firmware + +if [ "$EMULATOR" != 1 ]; then + make -C firmware sign +fi diff --git a/legacy/script/fingerprint b/legacy/script/fingerprint new file mode 100755 index 0000000000..de9d90ed5b --- /dev/null +++ b/legacy/script/fingerprint @@ -0,0 +1,41 @@ +#!/usr/bin/env python +from __future__ import print_function + +import binascii +import hashlib + + +def H(x): + return hashlib.sha256(x).digest() + + +def compute_fingerprint(x, double): + digest = H(H(x)) if double else H(x) + return binascii.hexlify(digest).decode() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + + parser.add_argument("file", type=argparse.FileType("rb"), + help="input file") + parser.add_argument("--offset", type=int, default=0, + help="skip bytes at start of input") + parser.add_argument("--max-size", type=int, + help="maximum input file size") + parser.add_argument("--double", action="store_true", + help="use SHA-256d instead of SHA-256") + + args = parser.parse_args() + + data = args.file.read() + size = len(data) + fingerprint = compute_fingerprint(data[args.offset:], args.double) + + print("Filename :", args.file.name) + print("Fingerprint :", fingerprint) + + print("Size : {} bytes (out of {} maximum)" + .format(size, args.max_size)) diff --git a/legacy/script/fullbuild b/legacy/script/fullbuild new file mode 100755 index 0000000000..f8ddb87424 --- /dev/null +++ b/legacy/script/fullbuild @@ -0,0 +1,131 @@ +#!/bin/bash + +# script/build: Build the TREZOR firmware in a clean working tree. +# + +# this needs to be there, otherwise python click installer vomits an error +export LC_ALL=C.UTF-8 +export LANG=C.UTF-8 + +set -eu + +cd "$(dirname "$0")/.." + +readonly ARTIFACT_EXTENSIONS=(bin elf) +readonly BUILD_DIR="$(readlink -f build)" + +readonly BOOTLOADER_DIR="$BUILD_DIR/bootloader" +readonly BOOTLOADER_FILENAME="bootloader/bootloader.bin" +readonly BOOTLOADER_PATH="$BOOTLOADER_DIR/$BOOTLOADER_FILENAME" + +readonly FIRMWARE_DIR="$BUILD_DIR/firmware" +readonly FIRMWARE_FILENAME="firmware/trezor.bin" +readonly FIRMWARE_PATH="$FIRMWARE_DIR/$FIRMWARE_FILENAME" + +readonly EMULATOR_DIR="$FIRMWARE_DIR" +readonly EMULATOR_FILENAME="firmware/trezor-emulator.elf" +readonly EMULATOR_PATH="$EMULATOR_DIR/firmware/trezor.elf" + +worktree_setup() { + local path="$1" + local commit="$2" + + rm -rf "$path" + git clone -n --reference=. . "$path" --recurse-submodules + + # Use `git rev-parse` so that we can use any reference from the working repository. + git -C "$path" checkout "$(git rev-parse "$commit")" + + ( cd "$path" && script/setup ) +} + +worktree_build() { + local path="$1" + + if [ -e "$path/Pipfile" ]; then + pushd $path + if ! pipenv install; then + # older tags can fail because they don't have protobuf in Pipfile + pipenv run pip install "protobuf==3.4.0" + pipenv install + fi + pipenv run script/cibuild + popd + else + # even older tags don't have Pipfile! + # use current one + pipenv install + ( cd "$path" && pipenv run script/cibuild ) + fi +} + +worktree_copy() { + local path="$1" + local filename="$2" + local pattern="$3" + + local describe="$(git -C "$path" describe --tags --match "$pattern")" + + local src="$path/$filename" + + local basename="$(basename "$filename")" + local dest="$BUILD_DIR/${basename%.*}-$describe.${basename##*.}" + + for extension in "${ARTIFACT_EXTENSIONS[@]}"; do + install -Dm0644 \ + "${src%.*}.$extension" \ + "${dest%.*}.$extension" + done + + printf "%s" "$dest" +} + +main() { + local bootloader_commit="$1" + local firmware_commit="$2" + + script/bootstrap + worktree_setup "$FIRMWARE_DIR" "$firmware_commit" + + if [ "$EMULATOR" != 1 ]; then + worktree_setup "$BOOTLOADER_DIR" "$bootloader_commit" + worktree_build "$BOOTLOADER_DIR" + + cp "$BOOTLOADER_PATH" "$FIRMWARE_DIR/$BOOTLOADER_FILENAME" + fi + + worktree_build "$FIRMWARE_DIR" + + if [ "$EMULATOR" = 1 ]; then + cp "$EMULATOR_PATH" "$EMULATOR_DIR/$EMULATOR_FILENAME" + local firmware_path="$(worktree_copy \ + "$EMULATOR_DIR" \ + "$EMULATOR_FILENAME" \ + "v*")" + chmod +x "$firmware_path" + + else + + local firmware_path="$(worktree_copy \ + "$FIRMWARE_DIR" \ + "$FIRMWARE_FILENAME" \ + "v*")" + + local bootloader_path="$(worktree_copy \ + "$BOOTLOADER_DIR" \ + "$BOOTLOADER_FILENAME" \ + "bl*")" + + printf "\n\n"; $PYTHON script/fingerprint \ + "$bootloader_path" \ + --max-size 32768 \ + --double + fi + + printf "\n\n"; $PYTHON script/fingerprint \ + "$firmware_path" \ + --offset 256 \ + --max-size 983296 # 256 + 64*1024 + 3*128*1024 + 4*128*1024 +} + +main "$@" diff --git a/legacy/script/setup b/legacy/script/setup new file mode 100755 index 0000000000..704168ece6 --- /dev/null +++ b/legacy/script/setup @@ -0,0 +1,13 @@ +#!/bin/bash + +# script/setup: Set up application for the first time after cloning, or set it +# back to the initial first unused state. + +set -e + +cd "$(dirname "$0")/.." + +script/bootstrap + +git clean -fdX +git submodule foreach git clean -fdX diff --git a/legacy/script/test b/legacy/script/test new file mode 100755 index 0000000000..58ef45ffa3 --- /dev/null +++ b/legacy/script/test @@ -0,0 +1,18 @@ +#!/bin/bash + +# script/test: Run test suite for application. + +set -e + +cd "$(dirname "$0")/.." + +if [ "$EMULATOR" = 1 ]; then + trap "kill %1" EXIT + + firmware/trezor.elf & + export TREZOR_PATH=udp:127.0.0.1:21324 + "${PYTHON:-python}" script/wait_for_emulator.py +fi + +export TREZOR_TRANSPORT_V1=1 +"${PYTHON:-python}" -m pytest --pyarg trezorlib.tests.device_tests "$@" diff --git a/legacy/script/toolchain-download b/legacy/script/toolchain-download new file mode 100755 index 0000000000..6314e5dc8e --- /dev/null +++ b/legacy/script/toolchain-download @@ -0,0 +1,54 @@ +#!/bin/bash + +# script/toolchain-download: Download and extract the GNU Arm Embedded toolchain +# for building the TREZOR firmware. + +set -e + +cd "$(dirname "$0")/../vendor" + +readonly URL="https://launchpad.net/gcc-arm-embedded/5.0/5-2016-q2-update/+download/gcc-arm-none-eabi-5_4-2016q2-20160622-linux.tar.bz2" +readonly SHA256SUM="9910b6b5df12efe564dbb3856bf1599d4c16178a6f28bd8a23c9e5c3edc219e4" + +readonly FILENAME="$(basename "$URL")" +readonly DIRECTORY="gcc-arm-none-eabi-5_4-2016q2" + +if [ "$(uname)" != "Linux" ]; then + printf "Unsupported platform\n" >&2 + exit 1 +fi + +validate_download() { + printf "$SHA256SUM $FILENAME" | sha256sum -c +} + +download_file() { + curl -LC- "$URL" -o "$FILENAME" +} + +extract_download() { + local temporary_dir="$(mktemp -d ./toolchain.XXXXXXXXXX)" + trap "rm -rf $temporary_dir" EXIT + + tar -xf "$FILENAME" -C "$temporary_dir" + mv "$temporary_dir/$DIRECTORY" . + rm -f "$FILENAME" +} + +if [ ! -d "$DIRECTORY" ]; then + if ! validate_download; then + download_file + validate_download + fi + + extract_download +fi + +# init toolchain as repository so Git will skip it +git init -q "$DIRECTORY" + +# update toolchain symlink +ln -snf "$DIRECTORY" toolchain + +# sanity-check extracted toolchain +toolchain/bin/arm-none-eabi-gcc --version >/dev/null diff --git a/legacy/script/toolchain-run b/legacy/script/toolchain-run new file mode 100755 index 0000000000..09e2a63d87 --- /dev/null +++ b/legacy/script/toolchain-run @@ -0,0 +1,18 @@ +#!/bin/bash + +# script/toolchain-run: Run command with downloaded GNU Arm Embedded toolchain. +# + +set -e + +readonly TOOLCHAIN_RELATIVE_DIR="vendor/toolchain" +readonly TOOLCHAIN_DIR="$(readlink -f "$(dirname "$0")/../$TOOLCHAIN_RELATIVE_DIR")" + +if [ ! -d "$TOOLCHAIN_DIR" ]; then + printf "Could not find toolchain in %s.\n" "$TOOLCHAIN_RELATIVE_DIR" >&2 + printf "Run script/toolchain-download to download it.\n" >&2 + exit 1 +fi + +export PATH="$TOOLCHAIN_DIR/bin:$PATH" +exec "$@" diff --git a/legacy/script/wait_for_emulator.py b/legacy/script/wait_for_emulator.py new file mode 100755 index 0000000000..035104b151 --- /dev/null +++ b/legacy/script/wait_for_emulator.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import socket +import sys +import time + +DEFAULT_ADDR = "127.0.0.1:21324" + +if len(sys.argv) > 1: + addr = sys.argv[1] +else: + addr = DEFAULT_ADDR + +host, port = addr.split(":") +SOCK_ADDR = (host, int(port)) + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock.connect(SOCK_ADDR) +sock.settimeout(0) + +start = time.monotonic() +while True: + try: + sock.sendall(b"PINGPING") + r = sock.recv(8) + if r == b"PONGPONG": + break + except Exception: + time.sleep(0.05) +end = time.monotonic() +print("waited for {:.3f}s".format(end - start)) diff --git a/legacy/secbool.h b/legacy/secbool.h new file mode 100644 index 0000000000..65860dd76f --- /dev/null +++ b/legacy/secbool.h @@ -0,0 +1,33 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_SECBOOL_H +#define TREZORHAL_SECBOOL_H + +#include + +typedef uint32_t secbool; +#define sectrue 0xAAAAAAAAU +#define secfalse 0x00000000U + +#ifndef __wur +#define __wur __attribute__((warn_unused_result)) +#endif + +#endif diff --git a/legacy/setup.c b/legacy/setup.c new file mode 100644 index 0000000000..62cedf7ef9 --- /dev/null +++ b/legacy/setup.c @@ -0,0 +1,283 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "layout.h" +#include "rng.h" +#include "util.h" + +uint32_t __stack_chk_guard; + +static inline void __attribute__((noreturn)) fault_handler(const char *line1) { + layoutDialog(&bmp_icon_error, NULL, NULL, NULL, line1, "detected.", NULL, + "Please unplug", "the device.", NULL); + shutdown(); +} + +void __attribute__((noreturn)) __stack_chk_fail(void) { + fault_handler("Stack smashing"); +} + +void nmi_handler(void) { + // Clock Security System triggered NMI + if ((RCC_CIR & RCC_CIR_CSSF) != 0) { + fault_handler("Clock instability"); + } +} + +void hard_fault_handler(void) { fault_handler("Hard fault"); } + +void mem_manage_handler(void) { fault_handler("Memory fault"); } + +void setup(void) { + // set SCB_CCR STKALIGN bit to make sure 8-byte stack alignment on exception + // entry is in effect. This is not strictly necessary for the current TREZOR + // system. This is here to comply with guidance from section 3.3.3 "Binary + // compatibility with other Cortex processors" of the ARM Cortex-M3 Processor + // Technical Reference Manual. According to section 4.4.2 and 4.4.7 of the + // "STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3 programming manual", STM32F2 + // series MCUs are r2p0 and always have this bit set on reset already. + SCB_CCR |= SCB_CCR_STKALIGN; + + // setup clock + struct rcc_clock_scale clock = rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_120MHZ]; + rcc_clock_setup_hse_3v3(&clock); + + // enable GPIO clock - A (oled), B(oled), C (buttons) + rcc_periph_clock_enable(RCC_GPIOA); + rcc_periph_clock_enable(RCC_GPIOB); + rcc_periph_clock_enable(RCC_GPIOC); + + // enable SPI clock + rcc_periph_clock_enable(RCC_SPI1); + + // enable RNG + rcc_periph_clock_enable(RCC_RNG); + RNG_CR |= RNG_CR_RNGEN; + // to be extra careful and heed the STM32F205xx Reference manual, + // Section 20.3.1 we don't use the first random number generated after setting + // the RNGEN bit in setup + random32(); + + // enable CSS (Clock Security System) + RCC_CR |= RCC_CR_CSSON; + + // set GPIO for buttons + gpio_mode_setup(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO2 | GPIO5); + + // set GPIO for OLED display + gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO4); + gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0 | GPIO1); + + // enable SPI 1 for OLED display + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO5 | GPIO7); + gpio_set_af(GPIOA, GPIO_AF5, GPIO5 | GPIO7); + + // spi_disable_crc(SPI1); + spi_init_master( + SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_8, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, + SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); + spi_enable_ss_output(SPI1); + // spi_enable_software_slave_management(SPI1); + // spi_set_nss_high(SPI1); + // spi_clear_mode_fault(SPI1); + spi_enable(SPI1); + + // enable OTG_FS + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO10); + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO11 | GPIO12); + gpio_set_af(GPIOA, GPIO_AF10, GPIO10 | GPIO11 | GPIO12); + + // enable OTG FS clock + rcc_periph_clock_enable(RCC_OTGFS); + // clear USB OTG_FS peripheral dedicated RAM + memset_reg((void *)0x50020000, (void *)0x50020500, 0); +} + +void setupApp(void) { + // for completeness, disable RNG peripheral interrupts for old bootloaders + // that had enabled them in RNG control register (the RNG interrupt was never + // enabled in the NVIC) + RNG_CR &= ~RNG_CR_IE; + // the static variables in random32 are separate between the bootloader and + // firmware. therefore, they need to be initialized here so that we can be + // sure to avoid dupes. this is to try to comply with STM32F205xx Reference + // manual - Section 20.3.1: "Each subsequent generated random number has to be + // compared with the previously generated number. The test fails if any two + // compared numbers are equal (continuous random number generator test)." + random32(); + + // enable CSS (Clock Security System) + RCC_CR |= RCC_CR_CSSON; + + // hotfix for old bootloader + gpio_mode_setup(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO9); + spi_init_master( + SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_8, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, + SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); + + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO10); + gpio_set_af(GPIOA, GPIO_AF10, GPIO10); +} + +#define MPU_RASR_SIZE_32B (0x04UL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_1KB (0x09UL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_4KB (0x0BUL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_8KB (0x0CUL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_16KB (0x0DUL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_32KB (0x0EUL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_64KB (0x0FUL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_128KB (0x10UL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_256KB (0x11UL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_512KB (0x12UL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_1MB (0x13UL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_512MB (0x1CUL << MPU_RASR_SIZE_LSB) +#define MPU_RASR_SIZE_4GB (0x1FUL << MPU_RASR_SIZE_LSB) + +// http://infocenter.arm.com/help/topic/com.arm.doc.dui0552a/BABDJJGF.html +#define MPU_RASR_ATTR_FLASH (MPU_RASR_ATTR_C) +#define MPU_RASR_ATTR_SRAM (MPU_RASR_ATTR_C | MPU_RASR_ATTR_S) +#define MPU_RASR_ATTR_PERIPH (MPU_RASR_ATTR_B | MPU_RASR_ATTR_S) + +#define FLASH_BASE (0x08000000U) +#define SRAM_BASE (0x20000000U) + +void mpu_config_off(void) { + // Disable MPU + MPU_CTRL = 0; + + __asm__ volatile("dsb"); + __asm__ volatile("isb"); +} + +void mpu_config_bootloader(void) { + // Disable MPU + MPU_CTRL = 0; + + // Note: later entries overwrite previous ones + + // Everything (0x00000000 - 0xFFFFFFFF, 4 GiB, read-write) + MPU_RBAR = 0 | MPU_RBAR_VALID | (0 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_FLASH | MPU_RASR_SIZE_4GB | + MPU_RASR_ATTR_AP_PRW_URW; + + // Flash (0x8007FE0 - 0x08007FFF, 32 B, no-access) + MPU_RBAR = + (FLASH_BASE + 0x7FE0) | MPU_RBAR_VALID | (1 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_FLASH | MPU_RASR_SIZE_32B | + MPU_RASR_ATTR_AP_PNO_UNO; + + // SRAM (0x20000000 - 0x2001FFFF, read-write, execute never) + MPU_RBAR = SRAM_BASE | MPU_RBAR_VALID | (2 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_SRAM | MPU_RASR_SIZE_128KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + + // Peripherals (0x40000000 - 0x4001FFFF, read-write, execute never) + MPU_RBAR = PERIPH_BASE | MPU_RBAR_VALID | (3 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_128KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + // Peripherals (0x40020000 - 0x40023FFF, read-write, execute never) + MPU_RBAR = 0x40020000 | MPU_RBAR_VALID | (4 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_16KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + // Don't enable DMA controller access + // Peripherals (0x50000000 - 0x5007ffff, read-write, execute never) + MPU_RBAR = 0x50000000 | MPU_RBAR_VALID | (5 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_512KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + + // Enable MPU + MPU_CTRL = MPU_CTRL_ENABLE | MPU_CTRL_HFNMIENA; + + // Enable memory fault handler + SCB_SHCSR |= SCB_SHCSR_MEMFAULTENA; + + __asm__ volatile("dsb"); + __asm__ volatile("isb"); +} + +// Never use in bootloader! Disables access to PPB (including MPU, NVIC, SCB) +void mpu_config_firmware(void) { +#if MEMORY_PROTECT + // Disable MPU + MPU_CTRL = 0; + + // Note: later entries overwrite previous ones + + // Flash (0x08000000 - 0x0807FFFF, 1 MiB, read-only) + MPU_RBAR = FLASH_BASE | MPU_RBAR_VALID | (0 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_FLASH | MPU_RASR_SIZE_1MB | + MPU_RASR_ATTR_AP_PRO_URO; + + // Metadata in Flash is read-write when unlocked + // (0x08008000 - 0x0800FFFF, 32 KiB, read-write, execute never) + MPU_RBAR = + (FLASH_BASE + 0x8000) | MPU_RBAR_VALID | (1 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_FLASH | MPU_RASR_SIZE_32KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + + // SRAM (0x20000000 - 0x2001FFFF, read-write, execute never) + MPU_RBAR = SRAM_BASE | MPU_RBAR_VALID | (2 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_SRAM | MPU_RASR_SIZE_128KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + + // Peripherals (0x40000000 - 0x4001FFFF, read-write, execute never) + MPU_RBAR = PERIPH_BASE | MPU_RBAR_VALID | (3 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_128KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + // Peripherals (0x40020000 - 0x40023FFF, read-write, execute never) + MPU_RBAR = 0x40020000 | MPU_RBAR_VALID | (4 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_16KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + // Flash controller is protected + // (0x40023C00 - 0x40023FFF, privileged read-write, user no, execute never) + MPU_RBAR = 0x40023c00 | MPU_RBAR_VALID | (5 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_1KB | + MPU_RASR_ATTR_AP_PRW_UNO | MPU_RASR_ATTR_XN; + // Don't enable DMA controller access + // Peripherals (0x50000000 - 0x5007ffff, read-write, execute never) + MPU_RBAR = 0x50000000 | MPU_RBAR_VALID | (6 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_512KB | + MPU_RASR_ATTR_AP_PRW_URW | MPU_RASR_ATTR_XN; + // SYSCFG_* registers are disabled + // (0x40013800 - 0x40013BFF, read-only, execute never) + MPU_RBAR = 0x40013800 | MPU_RBAR_VALID | (7 << MPU_RBAR_REGION_LSB); + MPU_RASR = MPU_RASR_ENABLE | MPU_RASR_ATTR_PERIPH | MPU_RASR_SIZE_1KB | + MPU_RASR_ATTR_AP_PRO_URO | MPU_RASR_ATTR_XN; + + // Enable MPU + MPU_CTRL = MPU_CTRL_ENABLE | MPU_CTRL_HFNMIENA; + + // Enable memory fault handler + SCB_SHCSR |= SCB_SHCSR_MEMFAULTENA; + + __asm__ volatile("dsb"); + __asm__ volatile("isb"); + + // Switch to unprivileged software execution to prevent access to MPU + set_mode_unprivileged(); +#endif +} diff --git a/legacy/setup.h b/legacy/setup.h new file mode 100644 index 0000000000..503e418cad --- /dev/null +++ b/legacy/setup.h @@ -0,0 +1,34 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __SETUP_H__ +#define __SETUP_H__ + +#include + +extern uint32_t __stack_chk_guard; + +void setup(void); +void setupApp(void); + +void mpu_config_off(void); +void mpu_config_bootloader(void); +void mpu_config_firmware(void); + +#endif diff --git a/legacy/shell.nix b/legacy/shell.nix new file mode 100644 index 0000000000..87f5ce3442 --- /dev/null +++ b/legacy/shell.nix @@ -0,0 +1,9 @@ +with import {}; + +let + myPython = python3.withPackages(p: [p.trezor p.Mako p.munch p.pillow]); +in + stdenv.mkDerivation { + name = "trezor-mcu-dev"; + buildInputs = [ myPython protobuf gnumake gcc gcc-arm-embedded pkgconfig SDL2 SDL2_image clang-tools ]; + } diff --git a/legacy/startup.s b/legacy/startup.s new file mode 100644 index 0000000000..ce75ba1a7a --- /dev/null +++ b/legacy/startup.s @@ -0,0 +1,85 @@ + .syntax unified + + .text + + .global memset_reg + .type memset_reg, STT_FUNC +memset_reg: + // call with the following (note that the arguments are not validated prior to use): + // r0 - address of first word to write (inclusive) + // r1 - address of first word following the address in r0 to NOT write (exclusive) + // r2 - word value to be written + // both addresses in r0 and r1 needs to be divisible by 4! + .L_loop_begin: + str r2, [r0], 4 // store the word in r2 to the address in r0, post-indexed + cmp r0, r1 + bne .L_loop_begin + bx lr + + .global reset_handler + .type reset_handler, STT_FUNC +reset_handler: +// we need to perform this in case an old bootloader +// is starting the new firmware, these will be set incorrectly + ldr r0, =0xE000ED08 // r0 = VTOR address + ldr r1, =0x08010400 // r1 = FLASH_APP_START + str r1, [r0] // assign + ldr r0, =_stack // r0 = stack pointer + msr msp, r0 // set stack pointer + dsb + isb + + ldr r0, =_ram_start // r0 - point to beginning of SRAM + ldr r1, =_ram_end // r1 - point to byte after the end of SRAM + ldr r2, =0 // r2 - the byte-sized value to be written + bl memset_reg + + // copy .data section from flash to SRAM + ldr r0, =_data // dst addr + ldr r1, =_data_loadaddr // src addr + ldr r2, =_data_size // length in bytes + bl memcpy + + // enter the application code + bl main + + // shutdown if the application code returns + b shutdown + + .global shutdown + .type shutdown, STT_FUNC +shutdown: + cpsid f + ldr r0, =0 + mov r1, r0 + mov r2, r0 + mov r3, r0 + mov r4, r0 + mov r5, r0 + mov r6, r0 + mov r7, r0 + mov r8, r0 + mov r9, r0 + mov r10, r0 + mov r11, r0 + mov r12, r0 + ldr lr, =0xffffffff + ldr r0, =_ram_start + ldr r1, =_ram_end + // set to value in r2 + bl memset_reg + b . // loop forever + + .ltorg // dump literal pool (for the ldr ...,=... commands above) + + .global sv_call_handler + .type sv_call_handler, STT_FUNC + +sv_call_handler: + tst lr, #4 + ite eq + mrseq r0, msp + mrsne r0, psp + b svc_handler_main + + .end diff --git a/legacy/supervise.c b/legacy/supervise.c new file mode 100644 index 0000000000..445881405b --- /dev/null +++ b/legacy/supervise.c @@ -0,0 +1,91 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Jochen Hoenicke + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "supervise.h" +#include +#include +#include "memory.h" + +#if !EMULATOR + +static void svhandler_flash_unlock(void) { + flash_wait_for_last_operation(); + flash_clear_status_flags(); + flash_unlock(); +} + +static void svhandler_flash_program(uint32_t psize) { + /* Wait for any write operation to complete. */ + flash_wait_for_last_operation(); + /* check program size argument */ + if (psize != FLASH_CR_PROGRAM_X8 && psize != FLASH_CR_PROGRAM_X16 && + psize != FLASH_CR_PROGRAM_X32 && psize != FLASH_CR_PROGRAM_X64) + return; + FLASH_CR = (FLASH_CR & ~(FLASH_CR_PROGRAM_MASK << FLASH_CR_PROGRAM_SHIFT)) | + (psize << FLASH_CR_PROGRAM_SHIFT); + FLASH_CR |= FLASH_CR_PG; +} + +static void svhandler_flash_erase_sector(uint16_t sector) { + /* we only allow erasing storage sectors 2 and 3. */ + if (sector < FLASH_STORAGE_SECTOR_FIRST || + sector > FLASH_STORAGE_SECTOR_LAST) { + return; + } + flash_erase_sector(sector, FLASH_CR_PROGRAM_X32); +} + +static uint32_t svhandler_flash_lock(void) { + /* Wait for any write operation to complete. */ + flash_wait_for_last_operation(); + /* Disable writes to flash. */ + FLASH_CR &= ~FLASH_CR_PG; + /* lock flash register */ + FLASH_CR |= FLASH_CR_LOCK; + /* return flash status register */ + return FLASH_SR; +} + +extern volatile uint32_t system_millis; + +void svc_handler_main(uint32_t *stack) { + uint8_t svc_number = ((uint8_t *)stack[6])[-2]; + switch (svc_number) { + case SVC_FLASH_UNLOCK: + svhandler_flash_unlock(); + break; + case SVC_FLASH_PROGRAM: + svhandler_flash_program(stack[0]); + break; + case SVC_FLASH_ERASE: + svhandler_flash_erase_sector(stack[0]); + break; + case SVC_FLASH_LOCK: + stack[0] = svhandler_flash_lock(); + break; + case SVC_TIMER_MS: + stack[0] = system_millis; + break; + default: + stack[0] = 0xffffffff; + break; + } +} + +#endif diff --git a/legacy/supervise.h b/legacy/supervise.h new file mode 100644 index 0000000000..db889a58ad --- /dev/null +++ b/legacy/supervise.h @@ -0,0 +1,84 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2018 Jochen Hoenicke + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __SUPERVISE_H__ +#define __SUPERVISE_H__ + +#include + +#if !EMULATOR + +#define SVC_FLASH_UNLOCK 0 +#define SVC_FLASH_ERASE 1 +#define SVC_FLASH_PROGRAM 2 +#define SVC_FLASH_LOCK 3 +#define SVC_TIMER_MS 4 + +/* Unlocks flash. This function needs to be called before programming + * or erasing. Multiple calls of flash_program and flash_erase can + * follow and should be completed with flash_lock(). + */ +inline void svc_flash_unlock(void) { + __asm__ __volatile__("svc %0" ::"i"(SVC_FLASH_UNLOCK) : "memory"); +} + +/* Enable flash write operations. + * @param program_size (8-bit, 16-bit, 32-bit or 64-bit) + * should be one of the FLASH_CR_PROGRAM_X.. constants + */ +inline void svc_flash_program(uint32_t program_size) { + register uint32_t r0 __asm__("r0") = program_size; + __asm__ __volatile__("svc %0" ::"i"(SVC_FLASH_PROGRAM), "r"(r0) : "memory"); +} + +/* Erase a flash sector. + * @param sector sector number 0..11 + * (this only allows erasing meta sectors 2 and 3 though). + */ +inline void svc_flash_erase_sector(uint8_t sector) { + register uint32_t r0 __asm__("r0") = sector; + __asm__ __volatile__("svc %0" ::"i"(SVC_FLASH_ERASE), "r"(r0) : "memory"); +} + +/* Lock flash after programming or erasing. + * @return flash status register (FLASH_SR) + */ +inline uint32_t svc_flash_lock(void) { + register uint32_t r0 __asm__("r0"); + __asm__ __volatile__("svc %1" : "=r"(r0) : "i"(SVC_FLASH_LOCK) : "memory"); + return r0; +} + +inline uint32_t svc_timer_ms(void) { + register uint32_t r0 __asm__("r0"); + __asm__ __volatile__("svc %1" : "=r"(r0) : "i"(SVC_TIMER_MS) : "memory"); + return r0; +} + +#else + +extern void svc_flash_unlock(void); +extern void svc_flash_program(uint32_t program_size); +extern void svc_flash_erase_sector(uint16_t sector); +extern uint32_t svc_flash_lock(void); +extern uint32_t svc_timer_ms(void); + +#endif + +#endif diff --git a/legacy/timer.c b/legacy/timer.c new file mode 100644 index 0000000000..0fea37d82b --- /dev/null +++ b/legacy/timer.c @@ -0,0 +1,59 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2016 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "timer.h" + +#include +#include +#include + +/* 1 tick = 1 ms */ +extern volatile uint32_t system_millis; + +/* + * Initialise the Cortex-M3 SysTick timer + */ +void timer_init(void) { + system_millis = 0; + + /* + * MCU clock (120 MHz) as source + * + * (120 MHz / 8) = 15 clock pulses + * + */ + systick_set_clocksource(STK_CSR_CLKSOURCE_AHB_DIV8); + STK_CVR = 0; + + /* + * 1 tick = 1 ms @ 120 MHz + * + * (15 clock pulses * 1000 ms) = 15000 clock pulses + * + * Send an interrupt every (N - 1) clock pulses + */ + systick_set_reload(14999); + + /* SysTick as interrupt */ + systick_interrupt_enable(); + + systick_counter_enable(); +} + +void sys_tick_handler(void) { system_millis++; } diff --git a/legacy/timer.h b/legacy/timer.h new file mode 100644 index 0000000000..93fe22962b --- /dev/null +++ b/legacy/timer.h @@ -0,0 +1,34 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2016 Saleem Rashid + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __TIMER_H__ +#define __TIMER_H__ + +#include +#include "supervise.h" + +void timer_init(void); + +#if EMULATOR +uint32_t timer_ms(void); +#else +#define timer_ms svc_timer_ms +#endif + +#endif diff --git a/legacy/usb21_standard.c b/legacy/usb21_standard.c new file mode 100644 index 0000000000..f5ca3546d8 --- /dev/null +++ b/legacy/usb21_standard.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "usb21_standard.h" +#include +#include +#include "util.h" + +static uint16_t build_bos_descriptor(const struct usb_bos_descriptor *bos, + uint8_t *buf, uint16_t len) { + uint8_t *tmpbuf = buf; + uint16_t count, total = 0, totallen = 0; + uint16_t i; + + memcpy(buf, bos, count = MIN(len, bos->bLength)); + buf += count; + len -= count; + total += count; + totallen += bos->bLength; + + /* For each device capability */ + for (i = 0; i < bos->bNumDeviceCaps; i++) { + /* Copy device capability descriptor. */ + const struct usb_device_capability_descriptor *cap = bos->capabilities[i]; + + memcpy(buf, cap, count = MIN(len, cap->bLength)); + buf += count; + len -= count; + total += count; + totallen += cap->bLength; + } + + /* Fill in wTotalLength. */ + *(uint16_t *)(tmpbuf + 2) = totallen; + + return total; +} + +static const struct usb_bos_descriptor *usb21_bos; + +static enum usbd_request_return_codes usb21_standard_get_descriptor( + usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, + uint16_t *len, usbd_control_complete_callback *complete) { + (void)complete; + (void)usbd_dev; + + wait_random(); + + if (req->bRequest == USB_REQ_GET_DESCRIPTOR) { + int descr_type = req->wValue >> 8; + if (descr_type == USB_DT_BOS) { + if (!usb21_bos) { + return USBD_REQ_NOTSUPP; + } + *len = MIN_8bits(*len, build_bos_descriptor(usb21_bos, *buf, *len)); + return USBD_REQ_HANDLED; + } + } + + return USBD_REQ_NEXT_CALLBACK; +} + +static void usb21_set_config(usbd_device *usbd_dev, uint16_t wValue) { + (void)wValue; + + usbd_register_control_callback( + usbd_dev, USB_REQ_TYPE_IN | USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_DEVICE, + USB_REQ_TYPE_DIRECTION | USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + &usb21_standard_get_descriptor); +} + +void usb21_setup(usbd_device *usbd_dev, + const struct usb_bos_descriptor *binary_object_store) { + usb21_bos = binary_object_store; + + /* Register the control request handler _before_ the config is set */ + usb21_set_config(usbd_dev, 0x0000); + usbd_register_set_config_callback(usbd_dev, usb21_set_config); +} diff --git a/legacy/usb21_standard.h b/legacy/usb21_standard.h new file mode 100644 index 0000000000..41637db910 --- /dev/null +++ b/legacy/usb21_standard.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef USB21_STANDARD_H_INCLUDED +#define USB21_STANDARD_H_INCLUDED + +#include + +/* USB 3.1 Descriptor Types - Table 9-6 */ +#define USB_DT_BOS 15 +#define USB_DT_DEVICE_CAPABILITY 16 + +struct usb_device_capability_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDevCapabilityType; +} __attribute__((packed)); + +struct usb_bos_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumDeviceCaps; + /* Descriptor ends here. The following are used internally: */ + const struct usb_device_capability_descriptor** capabilities; +} __attribute__((packed)); + +#define USB_DT_BOS_SIZE 5 + +/* USB Device Capability Types - USB 3.1 Table 9-14 */ +#define USB_DC_PLATFORM 5 + +extern void usb21_setup(usbd_device* usbd_dev, + const struct usb_bos_descriptor* binary_object_store); + +#endif diff --git a/legacy/usb_private.h b/legacy/usb_private.h new file mode 100644 index 0000000000..00d4086d45 --- /dev/null +++ b/legacy/usb_private.h @@ -0,0 +1,162 @@ +// clang-format off +/** @defgroup usb_private_defines USB Private Structures + +@brief Defined Constants and Types for the USB Private Structures + +@ingroup USB_defines + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2010 +Gareth McMullin + +@date 10 March 2013 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#ifndef __USB_PRIVATE_H +#define __USB_PRIVATE_H + +#define MAX_USER_CONTROL_CALLBACK 4 +#define MAX_USER_SET_CONFIG_CALLBACK 4 + +/** Internal collection of device information. */ +struct _usbd_device { + const struct usb_device_descriptor *desc; + const struct usb_config_descriptor *config; + const char * const *strings; + int num_strings; + + uint8_t *ctrl_buf; /**< Internal buffer used for control transfers */ + uint16_t ctrl_buf_len; + + uint8_t current_address; + uint8_t current_config; + + uint16_t pm_top; /**< Top of allocated endpoint buffer memory */ + + /* User callback functions for various USB events */ + void (*user_callback_reset)(void); + void (*user_callback_suspend)(void); + void (*user_callback_resume)(void); + void (*user_callback_sof)(void); + + struct usb_control_state { + enum { + IDLE, STALLED, + DATA_IN, LAST_DATA_IN, STATUS_IN, + DATA_OUT, LAST_DATA_OUT, STATUS_OUT, + } state; + struct usb_setup_data req __attribute__((aligned(4))); + uint8_t *ctrl_buf; + uint16_t ctrl_len; + usbd_control_complete_callback complete; + bool needs_zlp; + } control_state; + + struct user_control_callback { + usbd_control_callback cb; + uint8_t type; + uint8_t type_mask; + } user_control_callback[MAX_USER_CONTROL_CALLBACK]; + + usbd_endpoint_callback user_callback_ctr[8][3]; + + /* User callback function for some standard USB function hooks */ + usbd_set_config_callback user_callback_set_config[MAX_USER_SET_CONFIG_CALLBACK]; + + usbd_set_altsetting_callback user_callback_set_altsetting; + + const struct _usbd_driver *driver; + + /* private driver data */ + + uint16_t fifo_mem_top; + uint16_t fifo_mem_top_ep0; + uint8_t force_nak[4]; + /* + * We keep a backup copy of the out endpoint size registers to restore + * them after a transaction. + */ + uint32_t doeptsiz[4]; + /* + * Received packet size for each endpoint. This is assigned in + * stm32f107_poll() which reads the packet status push register GRXSTSP + * for use in stm32f107_ep_read_packet(). + */ + uint16_t rxbcnt; +}; + +enum _usbd_transaction { + USB_TRANSACTION_IN, + USB_TRANSACTION_OUT, + USB_TRANSACTION_SETUP, +}; + +/* Do not appear to belong to the API, so are omitted from docs */ +/**@}*/ + +void _usbd_control_in(usbd_device *usbd_dev, uint8_t ea); +void _usbd_control_out(usbd_device *usbd_dev, uint8_t ea); +void _usbd_control_setup(usbd_device *usbd_dev, uint8_t ea); + +enum usbd_request_return_codes _usbd_standard_request_device(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len); +enum usbd_request_return_codes _usbd_standard_request_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len); +enum usbd_request_return_codes _usbd_standard_request_endpoint(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len); +enum usbd_request_return_codes _usbd_standard_request(usbd_device *usbd_dev, struct usb_setup_data *req, + uint8_t **buf, uint16_t *len); + +void _usbd_reset(usbd_device *usbd_dev); + +/* Functions provided by the hardware abstraction. */ +struct _usbd_driver { + usbd_device *(*init)(void); + void (*set_address)(usbd_device *usbd_dev, uint8_t addr); + void (*ep_setup)(usbd_device *usbd_dev, uint8_t addr, uint8_t type, + uint16_t max_size, usbd_endpoint_callback cb); + void (*ep_reset)(usbd_device *usbd_dev); + void (*ep_stall_set)(usbd_device *usbd_dev, uint8_t addr, + uint8_t stall); + void (*ep_nak_set)(usbd_device *usbd_dev, uint8_t addr, uint8_t nak); + uint8_t (*ep_stall_get)(usbd_device *usbd_dev, uint8_t addr); + uint16_t (*ep_write_packet)(usbd_device *usbd_dev, uint8_t addr, + const void *buf, uint16_t len); + uint16_t (*ep_read_packet)(usbd_device *usbd_dev, uint8_t addr, + void *buf, uint16_t len); + void (*poll)(usbd_device *usbd_dev); + void (*disconnect)(usbd_device *usbd_dev, bool disconnected); + uint32_t base_address; + bool set_address_before_status; + uint16_t rx_fifo_size; +}; + +#endif + diff --git a/legacy/usb_standard.c b/legacy/usb_standard.c new file mode 100644 index 0000000000..26f5925a24 --- /dev/null +++ b/legacy/usb_standard.c @@ -0,0 +1,627 @@ +// clang-format off +/** @defgroup usb_standard_file Generic USB Standard Request Interface + +@ingroup USB + +@brief Generic USB Standard Request Interface + +@version 1.0.0 + +@author @htmlonly © @endhtmlonly 2010 +Gareth McMullin + +@date 10 March 2013 + +LGPL License Terms @ref lgpl_license +*/ + +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/**@{*/ + +#include +#include +#include "usb_private.h" +#include "util.h" + +int usbd_register_set_config_callback(usbd_device *usbd_dev, + usbd_set_config_callback callback) +{ + int i; + + for (i = 0; i < MAX_USER_SET_CONFIG_CALLBACK; i++) { + if (usbd_dev->user_callback_set_config[i]) { + if (usbd_dev->user_callback_set_config[i] == callback) { + return 0; + } + continue; + } + + usbd_dev->user_callback_set_config[i] = callback; + return 0; + } + + return -1; +} + +void usbd_register_set_altsetting_callback(usbd_device *usbd_dev, + usbd_set_altsetting_callback callback) +{ + usbd_dev->user_callback_set_altsetting = callback; +} + +static uint16_t build_config_descriptor(usbd_device *usbd_dev, + uint8_t index, uint8_t *buf, uint16_t len) +{ + uint8_t *tmpbuf = buf; + const struct usb_config_descriptor *cfg = &usbd_dev->config[index]; + uint16_t count, total = 0, totallen = 0; + uint16_t i, j, k; + + memcpy(buf, cfg, count = MIN(len, cfg->bLength)); + buf += count; + len -= count; + total += count; + totallen += cfg->bLength; + + /* For each interface... */ + for (i = 0; i < cfg->bNumInterfaces; i++) { + /* Interface Association Descriptor, if any */ + if (cfg->interface[i].iface_assoc) { + const struct usb_iface_assoc_descriptor *assoc = + cfg->interface[i].iface_assoc; + memcpy(buf, assoc, count = MIN(len, assoc->bLength)); + buf += count; + len -= count; + total += count; + totallen += assoc->bLength; + } + /* For each alternate setting... */ + for (j = 0; j < cfg->interface[i].num_altsetting; j++) { + const struct usb_interface_descriptor *iface = + &cfg->interface[i].altsetting[j]; + /* Copy interface descriptor. */ + memcpy(buf, iface, count = MIN(len, iface->bLength)); + buf += count; + len -= count; + total += count; + totallen += iface->bLength; + /* Copy extra bytes (function descriptors). */ + if (iface->extra) { + memcpy(buf, iface->extra, + count = MIN(len, iface->extralen)); + buf += count; + len -= count; + total += count; + totallen += iface->extralen; + } + /* For each endpoint... */ + for (k = 0; k < iface->bNumEndpoints; k++) { + const struct usb_endpoint_descriptor *ep = + &iface->endpoint[k]; + memcpy(buf, ep, count = MIN(len, ep->bLength)); + buf += count; + len -= count; + total += count; + totallen += ep->bLength; + /* Copy extra bytes (class specific). */ + if (ep->extra) { + memcpy(buf, ep->extra, + count = MIN(len, ep->extralen)); + buf += count; + len -= count; + total += count; + totallen += ep->extralen; + } + } + } + } + + /* Fill in wTotalLength. + * Note that tmpbuf is sometimes not halfword-aligned */ + memcpy((tmpbuf + 2), &totallen, sizeof(uint16_t)); + + return total; +} + +static int usb_descriptor_type(uint16_t wValue) +{ + return wValue >> 8; +} + +static int usb_descriptor_index(uint16_t wValue) +{ + return wValue & 0xFF; +} + +static enum usbd_request_return_codes +usb_standard_get_descriptor(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + + wait_random(); + + int i, array_idx, descr_idx; + struct usb_string_descriptor *sd; + + descr_idx = usb_descriptor_index(req->wValue); + + switch (usb_descriptor_type(req->wValue)) { + case USB_DT_DEVICE: + *buf = (uint8_t *) usbd_dev->desc; + *len = MIN_8bits(*len, usbd_dev->desc->bLength); + return USBD_REQ_HANDLED; + case USB_DT_CONFIGURATION: + *buf = usbd_dev->ctrl_buf; + *len = build_config_descriptor(usbd_dev, descr_idx, *buf, *len); + return USBD_REQ_HANDLED; + case USB_DT_STRING: + sd = (struct usb_string_descriptor *)usbd_dev->ctrl_buf; + + if (descr_idx == 0) { + /* Send sane Language ID descriptor... */ + sd->wData[0] = USB_LANGID_ENGLISH_US; + sd->bLength = sizeof(sd->bLength) + + sizeof(sd->bDescriptorType) + + sizeof(sd->wData[0]); + + *len = MIN_8bits(*len, sd->bLength); + } else { + array_idx = descr_idx - 1; + + if (!usbd_dev->strings) { + /* Device doesn't support strings. */ + return USBD_REQ_NOTSUPP; + } + + /* Check that string index is in range. */ + if (array_idx >= usbd_dev->num_strings) { + return USBD_REQ_NOTSUPP; + } + + /* Strings with Language ID differnet from + * USB_LANGID_ENGLISH_US are not supported */ + if (req->wIndex != USB_LANGID_ENGLISH_US) { + return USBD_REQ_NOTSUPP; + } + + /* This string is returned as UTF16, hence the + * multiplication + */ + sd->bLength = strlen(usbd_dev->strings[array_idx]) * 2 + + sizeof(sd->bLength) + + sizeof(sd->bDescriptorType); + + *len = MIN_8bits(*len, sd->bLength); + + for (i = 0; i < (*len / 2) - 1; i++) { + sd->wData[i] = + usbd_dev->strings[array_idx][i]; + } + } + + sd->bDescriptorType = USB_DT_STRING; + *buf = (uint8_t *)sd; + + return USBD_REQ_HANDLED; + } + return USBD_REQ_NOTSUPP; +} + +static enum usbd_request_return_codes +usb_standard_set_address(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + (void)req; + (void)buf; + (void)len; + + /* The actual address is only latched at the STATUS IN stage. */ + if ((req->bmRequestType != 0) || (req->wValue >= 128)) { + return USBD_REQ_NOTSUPP; + } + + usbd_dev->current_address = req->wValue; + + /* + * Special workaround for STM32F10[57] that require the address + * to be set here. This is undocumented! + */ + if (usbd_dev->driver->set_address_before_status) { + usbd_dev->driver->set_address(usbd_dev, req->wValue); + } + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_set_configuration(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + unsigned i; + int found_index = -1; + const struct usb_config_descriptor *cfg; + + (void)req; + (void)buf; + (void)len; + + if (req->wValue > 0) { + for (i = 0; i < usbd_dev->desc->bNumConfigurations; i++) { + if (req->wValue + == usbd_dev->config[i].bConfigurationValue) { + found_index = i; + break; + } + } + if (found_index < 0) { + return USBD_REQ_NOTSUPP; + } + } + + usbd_dev->current_config = found_index + 1; + + if (usbd_dev->current_config > 0) { + cfg = &usbd_dev->config[usbd_dev->current_config - 1]; + + /* reset all alternate settings configuration */ + for (i = 0; i < cfg->bNumInterfaces; i++) { + if (cfg->interface[i].cur_altsetting) { + *cfg->interface[i].cur_altsetting = 0; + } + } + } + + /* Reset all endpoints. */ + usbd_dev->driver->ep_reset(usbd_dev); + + if (usbd_dev->user_callback_set_config[0]) { + /* + * Flush control callbacks. These will be reregistered + * by the user handler. + */ + for (i = 0; i < MAX_USER_CONTROL_CALLBACK; i++) { + usbd_dev->user_control_callback[i].cb = NULL; + } + + for (i = 0; i < MAX_USER_SET_CONFIG_CALLBACK; i++) { + if (usbd_dev->user_callback_set_config[i]) { + usbd_dev->user_callback_set_config[i](usbd_dev, + req->wValue); + } + } + } + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_get_configuration(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)req; + + if (*len > 1) { + *len = 1; + } + if (usbd_dev->current_config > 0) { + const struct usb_config_descriptor *cfg = + &usbd_dev->config[usbd_dev->current_config - 1]; + (*buf)[0] = cfg->bConfigurationValue; + } else { + (*buf)[0] = 0; + } + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_set_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + const struct usb_config_descriptor *cfx = + &usbd_dev->config[usbd_dev->current_config - 1]; + const struct usb_interface *iface; + + (void)buf; + + if (req->wIndex >= cfx->bNumInterfaces) { + return USBD_REQ_NOTSUPP; + } + + iface = &cfx->interface[req->wIndex]; + + if (req->wValue >= iface->num_altsetting) { + return USBD_REQ_NOTSUPP; + } + + if (iface->cur_altsetting) { + *iface->cur_altsetting = req->wValue; + } else if (req->wValue > 0) { + return USBD_REQ_NOTSUPP; + } + + if (usbd_dev->user_callback_set_altsetting) { + usbd_dev->user_callback_set_altsetting(usbd_dev, + req->wIndex, + req->wValue); + } + + *len = 0; + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_get_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + uint8_t *cur_altsetting; + const struct usb_config_descriptor *cfx = + &usbd_dev->config[usbd_dev->current_config - 1]; + + if (req->wIndex >= cfx->bNumInterfaces) { + return USBD_REQ_NOTSUPP; + } + + *len = 1; + cur_altsetting = cfx->interface[req->wIndex].cur_altsetting; + (*buf)[0] = (cur_altsetting) ? *cur_altsetting : 0; + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_device_get_status(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)usbd_dev; + (void)req; + + /* bit 0: self powered */ + /* bit 1: remote wakeup */ + if (*len > 2) { + *len = 2; + } + (*buf)[0] = 0; + (*buf)[1] = 0; + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_interface_get_status(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)usbd_dev; + (void)req; + /* not defined */ + + if (*len > 2) { + *len = 2; + } + (*buf)[0] = 0; + (*buf)[1] = 0; + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_endpoint_get_status(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)req; + + if (*len > 2) { + *len = 2; + } + (*buf)[0] = usbd_ep_stall_get(usbd_dev, req->wIndex) ? 1 : 0; + (*buf)[1] = 0; + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_endpoint_stall(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)buf; + (void)len; + + usbd_ep_stall_set(usbd_dev, req->wIndex, 1); + + return USBD_REQ_HANDLED; +} + +static enum usbd_request_return_codes +usb_standard_endpoint_unstall(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + (void)buf; + (void)len; + + usbd_ep_stall_set(usbd_dev, req->wIndex, 0); + + return USBD_REQ_HANDLED; +} + +/* Do not appear to belong to the API, so are omitted from docs */ +/**@}*/ + +enum usbd_request_return_codes +_usbd_standard_request_device(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + enum usbd_request_return_codes (*command)(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) = NULL; + + switch (req->bRequest) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + if (req->wValue == USB_FEAT_DEVICE_REMOTE_WAKEUP) { + /* Device wakeup code goes here. */ + } + + if (req->wValue == USB_FEAT_TEST_MODE) { + /* Test mode code goes here. */ + } + + break; + case USB_REQ_SET_ADDRESS: + /* + * SET ADDRESS is an exception. + * It is only processed at STATUS stage. + */ + command = usb_standard_set_address; + break; + case USB_REQ_SET_CONFIGURATION: + command = usb_standard_set_configuration; + break; + case USB_REQ_GET_CONFIGURATION: + command = usb_standard_get_configuration; + break; + case USB_REQ_GET_DESCRIPTOR: + command = usb_standard_get_descriptor; + break; + case USB_REQ_GET_STATUS: + /* + * GET_STATUS always responds with zero reply. + * The application may override this behaviour. + */ + command = usb_standard_device_get_status; + break; + case USB_REQ_SET_DESCRIPTOR: + /* SET_DESCRIPTOR is optional and not implemented. */ + break; + } + + if (!command) { + return USBD_REQ_NOTSUPP; + } + + return command(usbd_dev, req, buf, len); +} + +enum usbd_request_return_codes +_usbd_standard_request_interface(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + enum usbd_request_return_codes (*command)(usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) = NULL; + + switch (req->bRequest) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* not defined */ + break; + case USB_REQ_GET_INTERFACE: + command = usb_standard_get_interface; + break; + case USB_REQ_SET_INTERFACE: + command = usb_standard_set_interface; + break; + case USB_REQ_GET_STATUS: + command = usb_standard_interface_get_status; + break; + } + + if (!command) { + return USBD_REQ_NOTSUPP; + } + + return command(usbd_dev, req, buf, len); +} + +enum usbd_request_return_codes +_usbd_standard_request_endpoint(usbd_device *usbd_dev, + struct usb_setup_data *req, uint8_t **buf, + uint16_t *len) +{ + enum usbd_request_return_codes (*command) (usbd_device *usbd_dev, + struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) = NULL; + + switch (req->bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (req->wValue == USB_FEAT_ENDPOINT_HALT) { + command = usb_standard_endpoint_unstall; + } + break; + case USB_REQ_SET_FEATURE: + if (req->wValue == USB_FEAT_ENDPOINT_HALT) { + command = usb_standard_endpoint_stall; + } + break; + case USB_REQ_GET_STATUS: + command = usb_standard_endpoint_get_status; + break; + case USB_REQ_SET_SYNCH_FRAME: + /* FIXME: SYNCH_FRAME is not implemented. */ + /* + * SYNCH_FRAME is used for synchronization of isochronous + * endpoints which are not yet implemented. + */ + break; + } + + if (!command) { + return USBD_REQ_NOTSUPP; + } + + return command(usbd_dev, req, buf, len); +} + +enum usbd_request_return_codes +_usbd_standard_request(usbd_device *usbd_dev, struct usb_setup_data *req, + uint8_t **buf, uint16_t *len) +{ + /* FIXME: Have class/vendor requests as well. */ + if ((req->bmRequestType & USB_REQ_TYPE_TYPE) != USB_REQ_TYPE_STANDARD) { + return USBD_REQ_NOTSUPP; + } + + switch (req->bmRequestType & USB_REQ_TYPE_RECIPIENT) { + case USB_REQ_TYPE_DEVICE: + return _usbd_standard_request_device(usbd_dev, req, buf, len); + case USB_REQ_TYPE_INTERFACE: + return _usbd_standard_request_interface(usbd_dev, req, + buf, len); + case USB_REQ_TYPE_ENDPOINT: + return _usbd_standard_request_endpoint(usbd_dev, req, buf, len); + default: + return USBD_REQ_NOTSUPP; + } +} + diff --git a/legacy/util.c b/legacy/util.c new file mode 100644 index 0000000000..5a96381de2 --- /dev/null +++ b/legacy/util.c @@ -0,0 +1,82 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "util.h" +#include "rng.h" + +inline void delay(uint32_t wait) { + while (--wait > 0) __asm__("nop"); +} + +void wait_random(void) { + int wait = random32() & 0xff; + volatile int i = 0; + volatile int j = wait; + while (i < wait) { + if (i + j != wait) { + shutdown(); + } + ++i; + --j; + } + // Double-check loop completion. + if (i != wait || j != 0) { + shutdown(); + } +} + +static const char *hexdigits = "0123456789ABCDEF"; + +void uint32hex(uint32_t num, char *str) { + for (uint32_t i = 0; i < 8; i++) { + str[i] = hexdigits[(num >> (28 - i * 4)) & 0xF]; + } +} + +// converts data to hexa +void data2hex(const void *data, uint32_t len, char *str) { + const uint8_t *cdata = (uint8_t *)data; + for (uint32_t i = 0; i < len; i++) { + str[i * 2] = hexdigits[(cdata[i] >> 4) & 0xF]; + str[i * 2 + 1] = hexdigits[cdata[i] & 0xF]; + } + str[len * 2] = 0; +} + +uint32_t readprotobufint(const uint8_t **ptr) { + uint32_t result = (**ptr & 0x7F); + if (**ptr & 0x80) { + (*ptr)++; + result += (**ptr & 0x7F) * 128; + if (**ptr & 0x80) { + (*ptr)++; + result += (**ptr & 0x7F) * 128 * 128; + if (**ptr & 0x80) { + (*ptr)++; + result += (**ptr & 0x7F) * 128 * 128 * 128; + if (**ptr & 0x80) { + (*ptr)++; + result += (**ptr & 0x7F) * 128 * 128 * 128 * 128; + } + } + } + } + (*ptr)++; + return result; +} diff --git a/legacy/util.h b/legacy/util.h new file mode 100644 index 0000000000..54c610430e --- /dev/null +++ b/legacy/util.h @@ -0,0 +1,116 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __UTIL_H_ +#define __UTIL_H_ + +#include +#include +#include + +#if !EMULATOR +#include +#include +#include "timer.h" +#endif + +// Statement expressions make these macros side-effect safe +#define MIN_8bits(a, b) \ + ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? (_a & 0xFF) : (_b & 0xFF); \ + }) +#define MIN(a, b) \ + ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) +#define MAX(a, b) \ + ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) + +void delay(uint32_t wait); + +void wait_random(void); + +// converts uint32 to hexa (8 digits) +void uint32hex(uint32_t num, char *str); + +// converts data to hexa +void data2hex(const void *data, uint32_t len, char *str); + +// read protobuf integer and advance pointer +uint32_t readprotobufint(const uint8_t **ptr); + +// defined in startup.s (or setup.c for emulator) +extern void __attribute__((noreturn)) shutdown(void); + +#if !EMULATOR +// defined in memory.ld +extern uint8_t _ram_start[], _ram_end[]; + +// defined in startup.s +extern void memset_reg(void *start, void *stop, uint32_t val); + +#define FW_SIGNED 0x5A3CA5C3 +#define FW_UNTRUSTED 0x00000000 + +static inline void __attribute__((noreturn)) +jump_to_firmware(const vector_table_t *ivt, int trust) { + if (FW_SIGNED == trust) { // trusted signed firmware + SCB_VTOR = (uint32_t)ivt; // * relocate vector table + // Set stack pointer + __asm__ volatile("msr msp, %0" ::"r"(ivt->initial_sp_value)); + } else { // untrusted firmware + timer_init(); + mpu_config_firmware(); // * configure MPU for the firmware + __asm__ volatile("msr msp, %0" ::"r"(_stack)); + } + + // Jump to address + ivt->reset(); + + // Prevent compiler from generating stack protector code (which causes CPU + // fault because the stack is moved) + for (;;) + ; +} + +static inline void set_mode_unprivileged(void) { + // http://infocenter.arm.com/help/topic/com.arm.doc.dui0552a/CHDBIBGJ.html + __asm__ volatile("msr control, %0" ::"r"(0x1)); +} + +static inline bool is_mode_unprivileged(void) { + uint32_t r0; + __asm__ volatile("mrs %0, control" : "=r"(r0)); + return r0 & 1; +} + +#else /* EMULATOR */ + +static inline bool is_mode_unprivileged(void) { return true; } +#endif + +#endif diff --git a/legacy/vendor/.gitignore b/legacy/vendor/.gitignore new file mode 100644 index 0000000000..47acdc5484 --- /dev/null +++ b/legacy/vendor/.gitignore @@ -0,0 +1,4 @@ +# toolchain-download +gcc-arm-none-eabi-* +toolchain +toolchain.*/ diff --git a/legacy/vendor/QR-Code-generator b/legacy/vendor/QR-Code-generator new file mode 160000 index 0000000000..40d24f38aa --- /dev/null +++ b/legacy/vendor/QR-Code-generator @@ -0,0 +1 @@ +Subproject commit 40d24f38aa0a8180b271b6c88be8633f842ed9d4 diff --git a/legacy/vendor/libopencm3 b/legacy/vendor/libopencm3 new file mode 160000 index 0000000000..0fd4f74ee3 --- /dev/null +++ b/legacy/vendor/libopencm3 @@ -0,0 +1 @@ +Subproject commit 0fd4f74ee301af5de4e9b036f391bf17c5a52f02 diff --git a/legacy/vendor/nanopb b/legacy/vendor/nanopb new file mode 160000 index 0000000000..3c69a905b1 --- /dev/null +++ b/legacy/vendor/nanopb @@ -0,0 +1 @@ +Subproject commit 3c69a905b16df149e1bda12f25e0522073a24678 diff --git a/legacy/vendor/trezor-common b/legacy/vendor/trezor-common new file mode 160000 index 0000000000..90bd68f55a --- /dev/null +++ b/legacy/vendor/trezor-common @@ -0,0 +1 @@ +Subproject commit 90bd68f55adeebacfca05c0f54aa29283aa0d760 diff --git a/legacy/vendor/trezor-crypto b/legacy/vendor/trezor-crypto new file mode 160000 index 0000000000..4211ce389f --- /dev/null +++ b/legacy/vendor/trezor-crypto @@ -0,0 +1 @@ +Subproject commit 4211ce389f6795d844809b0ba66a84082038ca04 diff --git a/legacy/vendor/trezor-storage b/legacy/vendor/trezor-storage new file mode 160000 index 0000000000..511fc205b2 --- /dev/null +++ b/legacy/vendor/trezor-storage @@ -0,0 +1 @@ +Subproject commit 511fc205b284605651348512c5c5c2c95a642fa1 diff --git a/legacy/webusb.c b/legacy/webusb.c new file mode 100644 index 0000000000..24f28a4ca5 --- /dev/null +++ b/legacy/webusb.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "usb21_standard.h" +#include "util.h" +#include "webusb.h" + +const struct webusb_platform_descriptor webusb_platform_capability_descriptor = + {.bLength = WEBUSB_PLATFORM_DESCRIPTOR_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_DC_PLATFORM, + .bReserved = 0, + .platformCapabilityUUID = WEBUSB_UUID, + .bcdVersion = 0x0100, + .bVendorCode = WEBUSB_VENDOR_CODE, + .iLandingPage = 1}; + +const struct webusb_platform_descriptor + webusb_platform_capability_descriptor_no_landing_page = { + .bLength = WEBUSB_PLATFORM_DESCRIPTOR_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_DC_PLATFORM, + .bReserved = 0, + .platformCapabilityUUID = WEBUSB_UUID, + .bcdVersion = 0x0100, + .bVendorCode = WEBUSB_VENDOR_CODE, + .iLandingPage = 0}; + +static const char* webusb_https_url; + +static enum usbd_request_return_codes webusb_control_vendor_request( + usbd_device* usbd_dev, struct usb_setup_data* req, uint8_t** buf, + uint16_t* len, usbd_control_complete_callback* complete) { + (void)complete; + (void)usbd_dev; + + wait_random(); + + if (req->bRequest != WEBUSB_VENDOR_CODE) { + return USBD_REQ_NEXT_CALLBACK; + } + + int status = USBD_REQ_NOTSUPP; + switch (req->wIndex) { + case WEBUSB_REQ_GET_URL: { + struct webusb_url_descriptor* url = (struct webusb_url_descriptor*)(*buf); + uint16_t index = req->wValue; + if (index == 0) { + return USBD_REQ_NOTSUPP; + } + + if (index == 1) { + size_t url_len = strlen(webusb_https_url); + url->bLength = WEBUSB_DT_URL_DESCRIPTOR_SIZE + url_len; + url->bDescriptorType = WEBUSB_DT_URL; + url->bScheme = WEBUSB_URL_SCHEME_HTTPS; + memcpy(&url->URL, webusb_https_url, url_len); + *len = MIN_8bits(*len, url->bLength); + status = USBD_REQ_HANDLED; + } else { + // TODO: stall instead? + status = USBD_REQ_NOTSUPP; + } + break; + } + default: { + status = USBD_REQ_NOTSUPP; + break; + } + } + + return status; +} + +static void webusb_set_config(usbd_device* usbd_dev, uint16_t wValue) { + (void)wValue; + usbd_register_control_callback(usbd_dev, + USB_REQ_TYPE_VENDOR | USB_REQ_TYPE_DEVICE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + webusb_control_vendor_request); +} + +void webusb_setup(usbd_device* usbd_dev, const char* https_url) { + webusb_https_url = https_url; + + usbd_register_set_config_callback(usbd_dev, webusb_set_config); +} diff --git a/legacy/webusb.h b/legacy/webusb.h new file mode 100644 index 0000000000..01feda4e5b --- /dev/null +++ b/legacy/webusb.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WEBUSB_H_INCLUDED +#define WEBUSB_H_INCLUDED + +#include +#include "webusb_defs.h" + +// Arbitrary +#define WEBUSB_VENDOR_CODE 0x01 + +extern const struct webusb_platform_descriptor + webusb_platform_capability_descriptor; +extern const struct webusb_platform_descriptor + webusb_platform_capability_descriptor_no_landing_page; + +extern void webusb_setup(usbd_device* usbd_dev, const char* https_url); + +#endif diff --git a/legacy/webusb_defs.h b/legacy/webusb_defs.h new file mode 100644 index 0000000000..b8566f12c8 --- /dev/null +++ b/legacy/webusb_defs.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WEBUSB_DEFS_H_INCLUDED +#define WEBUSB_DEFS_H_INCLUDED + +#include + +#define WEBUSB_REQ_GET_URL 0x02 + +#define WEBUSB_DT_DESCRIPTOR_SET_HEADER 0 +#define WEBUSB_DT_CONFIGURATION_SUBSET_HEADER 1 +#define WEBUSB_DT_FUNCTION_SUBSET_HEADER 2 +#define WEBUSB_DT_URL 3 + +#define WEBUSB_URL_SCHEME_HTTP 0 +#define WEBUSB_URL_SCHEME_HTTPS 1 + +struct webusb_platform_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDevCapabilityType; + uint8_t bReserved; + uint8_t platformCapabilityUUID[16]; + uint16_t bcdVersion; + uint8_t bVendorCode; + uint8_t iLandingPage; +} __attribute__((packed)); + +#define WEBUSB_PLATFORM_DESCRIPTOR_SIZE \ + sizeof(struct webusb_platform_descriptor) + +// from https://wicg.github.io/webusb/#webusb-platform-capability-descriptor +// see also this (for endianness explanation) +// https://github.com/WICG/webusb/issues/115#issuecomment-352206549 +#define WEBUSB_UUID \ + { \ + 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, \ + 0x88, 0x15, 0xB6, 0x65 \ + } + +struct webusb_url_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bScheme; + char URL[]; +} __attribute__((packed)); + +#define WEBUSB_DT_URL_DESCRIPTOR_SIZE 3 + +#endif diff --git a/legacy/winusb.c b/legacy/winusb.c new file mode 100644 index 0000000000..e18f9d0e07 --- /dev/null +++ b/legacy/winusb.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "winusb.h" +#include +#include "util.h" + +static int usb_descriptor_type(uint16_t wValue) { return wValue >> 8; } + +static int usb_descriptor_index(uint16_t wValue) { return wValue & 0xFF; } + +static struct winusb_compatible_id_descriptor winusb_wcid = { + .header = + { + .dwLength = + sizeof(struct winusb_compatible_id_descriptor_header) + + 1 * sizeof(struct winusb_compatible_id_function_section), + .bcdVersion = WINUSB_BCD_VERSION, + .wIndex = WINUSB_REQ_GET_COMPATIBLE_ID_FEATURE_DESCRIPTOR, + .bNumSections = 1, + .reserved = {0, 0, 0, 0, 0, 0, 0}, + }, + .functions = { + {// note - bInterfaceNumber is rewritten in winusb_setup with the + // correct interface number + .bInterfaceNumber = 0, + .reserved0 = {1}, + .compatibleId = "WINUSB", + .subCompatibleId = "", + .reserved1 = {0, 0, 0, 0, 0, 0}}, + }}; + +static const struct usb_string_descriptor winusb_string_descriptor = { + .bLength = 0x12, + .bDescriptorType = USB_DT_STRING, + .wData = WINUSB_EXTRA_STRING}; + +static const struct winusb_extended_properties_descriptor guid = { + .header = + { + .dwLength = + sizeof(struct winusb_extended_properties_descriptor_header) + + 1 * sizeof( + struct winusb_extended_properties_feature_descriptor), + .bcdVersion = WINUSB_BCD_VERSION, + .wIndex = WINUSB_REQ_GET_EXTENDED_PROPERTIES_OS_FEATURE_DESCRIPTOR, + .wNumFeatures = 1, + }, + .features = { + { + .dwLength = + sizeof(struct winusb_extended_properties_feature_descriptor), + .dwPropertyDataType = WINUSB_EXTENDED_PROPERTIES_MULTISZ_DATA_TYPE, + .wNameLength = WINUSB_EXTENDED_PROPERTIES_GUID_NAME_SIZE_C, + .name = WINUSB_EXTENDED_PROPERTIES_GUID_NAME, + .dwPropertyDataLength = WINUSB_EXTENDED_PROPERTIES_GUID_DATA_SIZE_C, + .propertyData = WINUSB_EXTENDED_PROPERTIES_GUID_DATA, + }, + }}; + +static enum usbd_request_return_codes winusb_descriptor_request( + usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, + uint16_t *len, usbd_control_complete_callback *complete) { + (void)complete; + (void)usbd_dev; + + wait_random(); + + if ((req->bmRequestType & USB_REQ_TYPE_TYPE) != USB_REQ_TYPE_STANDARD) { + return USBD_REQ_NEXT_CALLBACK; + } + + if (req->bRequest == USB_REQ_GET_DESCRIPTOR && + usb_descriptor_type(req->wValue) == USB_DT_STRING) { + if (usb_descriptor_index(req->wValue) == WINUSB_EXTRA_STRING_INDEX) { + *buf = (uint8_t *)(&winusb_string_descriptor); + *len = MIN_8bits(*len, winusb_string_descriptor.bLength); + return USBD_REQ_HANDLED; + } + } + return USBD_REQ_NEXT_CALLBACK; +} + +static enum usbd_request_return_codes winusb_control_vendor_request( + usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, + uint16_t *len, usbd_control_complete_callback *complete) { + (void)complete; + (void)usbd_dev; + + wait_random(); + + if (req->bRequest != WINUSB_MS_VENDOR_CODE) { + return USBD_REQ_NEXT_CALLBACK; + } + + int status = USBD_REQ_NOTSUPP; + if (((req->bmRequestType & USB_REQ_TYPE_RECIPIENT) == USB_REQ_TYPE_DEVICE) && + (req->wIndex == WINUSB_REQ_GET_COMPATIBLE_ID_FEATURE_DESCRIPTOR)) { + *buf = (uint8_t *)(&winusb_wcid); + *len = MIN_8bits(*len, winusb_wcid.header.dwLength); + status = USBD_REQ_HANDLED; + + } else if (((req->bmRequestType & USB_REQ_TYPE_RECIPIENT) == + USB_REQ_TYPE_INTERFACE) && + (req->wIndex == + WINUSB_REQ_GET_EXTENDED_PROPERTIES_OS_FEATURE_DESCRIPTOR) && + (usb_descriptor_index(req->wValue) == + winusb_wcid.functions[0].bInterfaceNumber)) { + *buf = (uint8_t *)(&guid); + *len = MIN_8bits(*len, guid.header.dwLength); + status = USBD_REQ_HANDLED; + + } else { + status = USBD_REQ_NOTSUPP; + } + + return status; +} + +static void winusb_set_config(usbd_device *usbd_dev, uint16_t wValue) { + (void)wValue; + usbd_register_control_callback(usbd_dev, USB_REQ_TYPE_VENDOR, + USB_REQ_TYPE_TYPE, + winusb_control_vendor_request); +} + +void winusb_setup(usbd_device *usbd_dev, uint8_t interface) { + winusb_wcid.functions[0].bInterfaceNumber = interface; + + usbd_register_set_config_callback(usbd_dev, winusb_set_config); + + /* Windows probes the compatible ID before setting the configuration, + so also register the callback now */ + + usbd_register_control_callback(usbd_dev, USB_REQ_TYPE_VENDOR, + USB_REQ_TYPE_TYPE, + winusb_control_vendor_request); + + usbd_register_control_callback(usbd_dev, USB_REQ_TYPE_DEVICE, + USB_REQ_TYPE_RECIPIENT, + winusb_descriptor_request); +} diff --git a/legacy/winusb.h b/legacy/winusb.h new file mode 100644 index 0000000000..071f262fc8 --- /dev/null +++ b/legacy/winusb.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WINUSB_H_INCLUDED +#define WINUSB_H_INCLUDED + +#include +#include "winusb_defs.h" + +// Arbitrary, but must be equivalent to the last character in extra string +#define WINUSB_MS_VENDOR_CODE '!' +#define WINUSB_EXTRA_STRING \ + { 'M', 'S', 'F', 'T', '1', '0', '0', WINUSB_MS_VENDOR_CODE } + +extern void winusb_setup(usbd_device* usbd_dev, uint8_t interface); + +#endif diff --git a/legacy/winusb_defs.h b/legacy/winusb_defs.h new file mode 100644 index 0000000000..dd779b8577 --- /dev/null +++ b/legacy/winusb_defs.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016, Devan Lai + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice + * appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WINUSB_DEFS_H_INCLUDED +#define WINUSB_DEFS_H_INCLUDED + +#include + +/* Microsoft OS 1.0 descriptors */ + +/* Extended Compat ID OS Feature Descriptor Specification */ +#define WINUSB_REQ_GET_COMPATIBLE_ID_FEATURE_DESCRIPTOR 0x04 +#define WINUSB_REQ_GET_EXTENDED_PROPERTIES_OS_FEATURE_DESCRIPTOR 0x05 +#define WINUSB_BCD_VERSION 0x0100 + +// Apparently using DeviceInterfaceGUID does not always work on Windows 7. +// DeviceInterfaceGUIDs does seem to work. +#define WINUSB_EXTENDED_PROPERTIES_GUID_NAME u"DeviceInterfaceGUIDs" +#define WINUSB_EXTENDED_PROPERTIES_GUID_NAME_SIZE_C \ + sizeof(WINUSB_EXTENDED_PROPERTIES_GUID_NAME) +#define WINUSB_EXTENDED_PROPERTIES_GUID_NAME_SIZE_U \ + (sizeof(WINUSB_EXTENDED_PROPERTIES_GUID_NAME) / 2) + +// extra null is intentional - it's an array of GUIDs with 1 item +#define WINUSB_EXTENDED_PROPERTIES_GUID_DATA \ + u"{0263b512-88cb-4136-9613-5c8e109d8ef5}\x00" +#define WINUSB_EXTENDED_PROPERTIES_GUID_DATA_SIZE_C \ + sizeof(WINUSB_EXTENDED_PROPERTIES_GUID_DATA) +#define WINUSB_EXTENDED_PROPERTIES_GUID_DATA_SIZE_U \ + (sizeof(WINUSB_EXTENDED_PROPERTIES_GUID_DATA) / 2) +#define WINUSB_EXTENDED_PROPERTIES_MULTISZ_DATA_TYPE 7 + +#define WINUSB_EXTRA_STRING_INDEX 0xee + +/* Table 2. Function Section */ +struct winusb_compatible_id_function_section { + uint8_t bInterfaceNumber; + uint8_t reserved0[1]; + char compatibleId[8]; + char subCompatibleId[8]; + uint8_t reserved1[6]; +} __attribute__((packed)); + +/* Table 1. Header Section */ +struct winusb_compatible_id_descriptor_header { + uint32_t dwLength; + uint16_t bcdVersion; + uint16_t wIndex; + uint8_t bNumSections; + uint8_t reserved[7]; +} __attribute__((packed)); + +struct winusb_compatible_id_descriptor { + struct winusb_compatible_id_descriptor_header header; + struct winusb_compatible_id_function_section functions[]; +} __attribute__((packed)); + +struct winusb_extended_properties_feature_descriptor { + uint32_t dwLength; + uint32_t dwPropertyDataType; + uint16_t wNameLength; + uint16_t name[WINUSB_EXTENDED_PROPERTIES_GUID_NAME_SIZE_U]; + uint32_t dwPropertyDataLength; + uint16_t propertyData[WINUSB_EXTENDED_PROPERTIES_GUID_DATA_SIZE_U]; +} __attribute__((packed)); + +struct winusb_extended_properties_descriptor_header { + uint32_t dwLength; + uint16_t bcdVersion; + uint16_t wIndex; + uint16_t wNumFeatures; +} __attribute__((packed)); + +struct winusb_extended_properties_descriptor { + struct winusb_extended_properties_descriptor_header header; + struct winusb_extended_properties_feature_descriptor features[]; +} __attribute__((packed)); + +#endif