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