diff --git a/core/.clang-format b/.clang-format similarity index 100% rename from core/.clang-format rename to .clang-format diff --git a/.gitignore b/.gitignore index 8090026217..492144ebd5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ .vscode/ __pycache__/ *.pyc +/build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..96441b85ee --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +variables: + # Init submodules. + # See https://docs.gitlab.com/ee/ci/yaml/#git-submodule-strategy + GIT_SUBMODULE_STRATEGY: "recursive" + + # Use shallow cloning to speed up git clone. This can fail, if retrying an older build on CI + # and the old commit is not in the shallow history any more. + # See https://docs.gitlab.com/ee/ci/yaml/#shallow-cloning + GIT_DEPTH: "50" + + # run make paralel + MAKEFLAGS: "-j10" + +stages: + - environment + - prebuild + - build + - test + +before_script: + - pipenv install + +include: + - ci/environment.yml + - ci/prebuild.yml # common, style + - ci/core.yml + - ci/legacy.yml + # - ci/python.yml TODO + - ci/crypto.yml + - ci/storage.yml diff --git a/.gitmodules b/.gitmodules index fe32821490..d4e39156fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,6 +23,7 @@ [submodule "legacy/vendor/nanopb"] path = legacy/vendor/nanopb url = https://github.com/nanopb/nanopb.git + ignore = untracked [submodule "legacy/vendor/QR-Code-generator"] path = legacy/vendor/QR-Code-generator url = https://github.com/nayuki/QR-Code-generator.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..d4aa18166c --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +## help commands: + +help: ## show this help + @awk -f ./tools/help.awk $(MAKEFILE_LIST) + +## style commands: + +style_check: ## run code style check on application sources and tests + flake8 --version + isort --version | awk '/VERSION/{print $$2}' + black --version + flake8 $(shell find -type f -name '*.py' | grep -f ./tools/style.py.include | grep -v -f ./tools/style.py.exclude ) + isort --check-only $(shell find -type f -name '*.py' | grep -f ./tools/style.py.include | grep -v -f ./tools/style.py.exclude ) + black --check $(shell find -type f -name '*.py' | grep -f ./tools/style.py.include | grep -v -f ./tools/style.py.exclude ) + +style: ## apply code style on application sources and tests + isort $(shell find -type f -name '*.py' | grep -f ./tools/style.py.include | grep -v -f ./tools/style.py.exclude ) + black $(shell find -type f -name '*.py' | grep -f ./tools/style.py.include | grep -v -f ./tools/style.py.exclude ) + +cstyle_check: ## run code style check on low-level C code + ./tools/clang-format-check $(shell find -type f -name '*.c' -o -name '*.h' | grep -f ./tools/style.c.include | grep -v -f ./tools/style.c.exclude ) + +cstyle: ## apply code style on low-level C code + clang-format -i $(shell find -type f -name '*.c' -o -name '*.h' | grep -f ./tools/style.c.include | grep -v -f ./tools/style.c.exclude ) diff --git a/core/Pipfile b/Pipfile similarity index 55% rename from core/Pipfile rename to Pipfile index ff26d37038..2ee9dd6826 100644 --- a/core/Pipfile +++ b/Pipfile @@ -4,31 +4,49 @@ name = "pypi" verify_ssl = true [packages] -trezor = {git = "https://github.com/trezor/python-trezor", editable = true, ref = "master"} +# all +trezor = {editable = true, path = "./python"} +scons = "*" protobuf = "==3.4.0" - -# python-trezor tests +pyblake2 = "*" +## tests pytest = "*" mock = "*" - -# make style +## style isort = "*" flake8 = "*" black = "*" +mako = ">=1.0.7" +munch = ">=2.3.2" -# trezor-common cointool +# common +demjson = "*" +graphviz = "*" # TODO this was '8' do we need that? +## cointool click = ">=6" "ed25519" = ">=1.4" requests = ">=2.19" -munch = ">=2.3.2" termcolor = ">=0.1.2" Pillow = ">=5.2.0" -Mako = ">=1.0.7" -# monero +# crypto +ecdsa = "*" +curve25519-donna = "*" +pyasn1 = "*" + +# core +## monero monero_agent = {version = ">=2.0.1", extras = ["tcry", "dev"]} py_trezor_crypto_ph4 = {version = ">=0.1.1"} +# legacy +setuptools = ">=24.2.0" +typing = "*" + +# storage +cryptography = "*" +hypothesis = "*" + [dev-packages] scan-build = "*" diff --git a/core/Pipfile.lock b/Pipfile.lock similarity index 84% rename from core/Pipfile.lock rename to Pipfile.lock index 529769e90c..9f9cf4f756 100644 --- a/core/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f038c016ebf0f0551ad1a2d066dd5861661925767b56bcf3c82b6764156b3b60" + "sha256": "4c4b090a727ea8f5db592444bf5930bb9ec06898d5b58d7c4eba4fd9a1317ebb" }, "pipfile-spec": 6, "requires": {}, @@ -79,36 +79,36 @@ }, "cffi": { "hashes": [ - "sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f", - "sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11", - "sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d", - "sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891", - "sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf", - "sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c", - "sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed", - "sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b", - "sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a", - "sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585", - "sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea", - "sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f", - "sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33", - "sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145", - "sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a", - "sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3", - "sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f", - "sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd", - "sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804", - "sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d", - "sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92", - "sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f", - "sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84", - "sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb", - "sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7", - "sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7", - "sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35", - "sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889" + "sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", + "sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", + "sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", + "sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", + "sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", + "sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", + "sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", + "sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", + "sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", + "sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", + "sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", + "sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", + "sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", + "sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", + "sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", + "sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", + "sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", + "sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", + "sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", + "sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", + "sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", + "sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", + "sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", + "sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", + "sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", + "sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", + "sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", + "sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201" ], - "version": "==1.12.2" + "version": "==1.12.3" }, "chacha20poly1305": { "hashes": [ @@ -160,6 +160,7 @@ "sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd", "sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6" ], + "index": "pypi", "version": "==2.6.1" }, "ctypeslib2": { @@ -169,11 +170,26 @@ ], "version": "==2.2.2" }, + "curve25519-donna": { + "hashes": [ + "sha256:1818a9d5356a05c022cd504f44fe1d2f641a5c020f8a4c51b2294e02bd9c1bf0" + ], + "index": "pypi", + "version": "==1.3" + }, + "demjson": { + "hashes": [ + "sha256:31de2038a0fdd9c4c11f8bf3b13fe77bc2a128307f965c8d5fb4dc6d6f6beb79" + ], + "index": "pypi", + "version": "==2.2.4" + }, "ecdsa": { "hashes": [ "sha256:20c17e527e75acad8f402290e158a6ac178b91b881f941fc6ea305bfdfb9657c", "sha256:5c034ffa23413ac923541ceb3ac14ec15a0d2530690413bff58c12b80e56d884" ], + "index": "pypi", "version": "==0.13.2" }, "ed25519": { @@ -205,6 +221,23 @@ "index": "pypi", "version": "==3.7.7" }, + "graphviz": { + "hashes": [ + "sha256:0e1744a45b0d707bc44f99c7b8e5f25dc22cf96b6aaf2432ac308ed9822a9cb6", + "sha256:d311be4fddfe832a56986ac5e1d6e8715d7fcb0208560da79d1bb0f72abef41f" + ], + "index": "pypi", + "version": "==0.10.1" + }, + "hypothesis": { + "hashes": [ + "sha256:4e1378aec40b109f2212f8416a0ef27635b40fdc22a7a8116cd5527776ce1f9e", + "sha256:63d33b2394410ab09a05a19354d826e2aa7814288b0800eb5e89857181def40a", + "sha256:905395b9da7fe04e5ce32b41eace83b613586a104db4b4b0a2552db980a40dd2" + ], + "index": "pypi", + "version": "==4.18.0" + }, "idna": { "hashes": [ "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", @@ -441,6 +474,14 @@ "index": "pypi", "version": "==0.1.1" }, + "pyasn1": { + "hashes": [ + "sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7", + "sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e" + ], + "index": "pypi", + "version": "==0.4.5" + }, "pyblake2": { "hashes": [ "sha256:3757f7ad709b0e1b2a6b3919fa79fe3261f166fc375cd521f2be480f8319dde9", @@ -453,6 +494,7 @@ "sha256:c53417ee0bbe77db852d5fd1036749f03696ebc2265de359fe17418d800196c4", "sha256:fbc9fcde75713930bc2a91b149e97be2401f7c9c56d735b46a109210f58d7358" ], + "index": "pypi", "version": "==1.1.2" }, "pycodestyle": { @@ -543,6 +585,14 @@ "index": "pypi", "version": "==2.21.0" }, + "scons": { + "hashes": [ + "sha256:8c2353ae3ce559e9361baa254c0c85d3eb099d5f96e44b5bc586801b7c756a06", + "sha256:e95eaae17d9e490cf12cd37f091a6cbee8a628b5c8dbd3cab1f348f602f46462" + ], + "index": "pypi", + "version": "==3.0.5" + }, "shlib": { "hashes": [ "sha256:f9798b0a3e37407171f06efca7c213269f034eee2c94dd9933a819730a6d528b" @@ -579,8 +629,16 @@ }, "trezor": { "editable": true, - "git": "https://github.com/trezor/python-trezor", - "ref": "2813522b05cef4e0e545a101f8b3559a3183b45b" + "path": "./python" + }, + "typing": { + "hashes": [ + "sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d", + "sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4", + "sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a" + ], + "index": "pypi", + "version": "==3.6.6" }, "typing-extensions": { "hashes": [ @@ -599,10 +657,10 @@ }, "virtualenv": { "hashes": [ - "sha256:6aebaf4dd2568a0094225ebbca987859e369e3e5c22dc7d52e5406d504890417", - "sha256:984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39" + "sha256:15ee248d13e4001a691d9583948ad3947bcb8a289775102e4c4aa98a8b7a6d73", + "sha256:bfc98bb9b42a3029ee41b96dc00a34c2f254cbf7716bec824477b2c82741a5c4" ], - "version": "==16.4.3" + "version": "==16.5.0" }, "wheel": { "hashes": [ @@ -626,6 +684,7 @@ "sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4", "sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a" ], + "index": "pypi", "version": "==3.6.6" } } diff --git a/legacy/Dockerfile b/ci/Dockerfile similarity index 89% rename from legacy/Dockerfile rename to ci/Dockerfile index 720c210761..f0fe5966c1 100644 --- a/legacy/Dockerfile +++ b/ci/Dockerfile @@ -1,17 +1,17 @@ # initialize from the image -FROM debian:9 +FROM python 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 + build-essential wget git libsodium-dev graphviz \ + valgrind check libssl-dev libusb-1.0-0-dev libudev-dev + +# TODO are all apt packages actually needed? # install dependencies from toolchain source build @@ -31,10 +31,6 @@ ENV TOOLCHAIN_URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/$TOOL 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 @@ -64,14 +60,19 @@ RUN echo "${PROTOBUF_HASH} protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" | sha256 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 +ENV PYTHON=python + 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 +RUN pip install pipenv + +RUN $PYTHON --version +RUN pip --version +RUN pipenv --version diff --git a/ci/core.yml b/ci/core.yml new file mode 100644 index 0000000000..d4e6d53633 --- /dev/null +++ b/ci/core.yml @@ -0,0 +1,58 @@ +image: registry.corp.sldev.cz/trezor/trezor-firmware/environment + +build core firmware: + stage: build + script: + - cd core + - pipenv run make build_cross + - pipenv run make build_boardloader + - pipenv run make build_bootloader + - pipenv run make build_prodtest + - pipenv run make build_firmware + # - test "$TREZOR_MODEL" = "1" || pipenv run make sizecheck + artifacts: + name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" + paths: + - core/build/firmware/firmware.bin + - core/build/bootloader/bootloader.bin + expire_in: 1 week + +build core unix: + stage: build + script: + - cd core + - pipenv run make build_unix_noui + artifacts: + name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" + untracked: true + expire_in: 1 day + +test core unix unit: + stage: test + variables: + GIT_SUBMODULE_STRATEGY: none # no need to fetch submodules + dependencies: + - build core unix + script: + - cd core + - pipenv run make test + +test core unix device: + stage: test + variables: + GIT_SUBMODULE_STRATEGY: none # no need to fetch submodules + dependencies: + - build core unix + script: + - cd core + - pipenv run make test_emu + +test core unix monero: + stage: test + variables: + GIT_SUBMODULE_STRATEGY: none # no need to fetch submodules + dependencies: + - build core unix + script: + - cd core + - pipenv run make test_emu_monero diff --git a/ci/crypto.yml b/ci/crypto.yml new file mode 100644 index 0000000000..5a95b2a5f2 --- /dev/null +++ b/ci/crypto.yml @@ -0,0 +1,23 @@ +image: registry.corp.sldev.cz/trezor/trezor-firmware/environment + +build crypto: + stage: build + script: + - cd crypto + - pipenv run make + artifacts: + name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" + untracked: true + expire_in: 1 day + +test crypto: + stage: test + dependencies: + - build crypto + script: + - cd crypto + - ./tests/aestst + - ./tests/test_check + - CK_TIMEOUT_MULTIPLIER=20 valgrind -q --error-exitcode=1 ./tests/test_check + - ./tests/test_openssl 1000 + - ITERS=10 pipenv run pytest tests/ # TODO are ITERS=10 propagated? diff --git a/ci/environment.yml b/ci/environment.yml new file mode 100644 index 0000000000..604587736f --- /dev/null +++ b/ci/environment.yml @@ -0,0 +1,16 @@ +environment: + stage: environment + image: docker:latest + variables: + GIT_SUBMODULE_STRATEGY: none # no need to fetch submodules + CONTAINER_NAME: "$CI_REGISTRY/trezor/trezor-firmware/environment" + services: + - docker:dind + before_script: + - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD + when: manual + script: + - docker pull $CONTAINER_NAME:latest || true + - docker build --cache-from $CONTAINER_NAME:latest --tag $CONTAINER_NAME:$CI_COMMIT_SHA --tag $CONTAINER_NAME:latest ci/ + - docker push $CONTAINER_NAME:$CI_COMMIT_SHA + - docker push $CONTAINER_NAME:latest diff --git a/ci/legacy.yml b/ci/legacy.yml new file mode 100644 index 0000000000..8af8aebb0d --- /dev/null +++ b/ci/legacy.yml @@ -0,0 +1,56 @@ +image: registry.corp.sldev.cz/trezor/trezor-firmware/environment + +build legacy firmware: + stage: build + script: + - cd legacy + - pipenv run script/cibuild + - pipenv run make -C bootloader + - pipenv run make -C demo + +build legacy firmware debug: + stage: build + variables: + DEBUG_LINK: "1" + script: + - cd legacy + - pipenv run script/cibuild + - pipenv run make -C bootloader + - pipenv run make -C demo + +build legacy firmware bitcoinonly: + stage: build + variables: + BITCOIN_ONLY: "1" + script: + - cd legacy + - pipenv run script/cibuild + - pipenv run make -C bootloader + - pipenv run make -C demo + +build legacy emu: + stage: build + variables: + HEADLESS: "1" + EMULATOR: "1" + DEBUG_LINK: "1" + script: + - cd legacy + - pipenv run script/cibuild + artifacts: + name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" + untracked: true + expire_in: 1 day + +# TODO: aren't some tests from .travis.yml missing? +test legacy emu: + variables: + GIT_SUBMODULE_STRATEGY: none # no need to fetch submodules + stage: test + dependencies: + - build legacy emu + variables: + EMULATOR: "1" + script: + - cd legacy + - pipenv run script/test \ No newline at end of file diff --git a/ci/prebuild.yml b/ci/prebuild.yml new file mode 100644 index 0000000000..ac83fb64c1 --- /dev/null +++ b/ci/prebuild.yml @@ -0,0 +1,18 @@ +image: registry.corp.sldev.cz/trezor/trezor-firmware/environment + +prebuild style: + stage: prebuild + script: + - pipenv run make style_check + - cd core && pipenv run make templates_check # TODO + +prebuild common: + stage: prebuild + script: + - cd common + - pipenv run jsonlint defs/*.json + - pipenv run jsonlint defs/*/*.json + - pipenv run python tools/cointool.py check + - pipenv run python tools/support.py check --ignore-missing + - pipenv run python protob/check.py + - pipenv run python protob/graph.py protob/*.proto # TODO: artifacts? diff --git a/ci/storage.yml b/ci/storage.yml new file mode 100644 index 0000000000..6d6a93785e --- /dev/null +++ b/ci/storage.yml @@ -0,0 +1,11 @@ +image: registry.corp.sldev.cz/trezor/trezor-firmware/environment + +test storage: + variables: + GIT_SUBMODULE_STRATEGY: none # no need to fetch submodules + stage: test + dependencies: [] + script: + - cd storage/tests + - pipenv run make build + - pipenv run make tests_all diff --git a/common/.travis.yml b/common/.travis.yml deleted file mode 100644 index 978c264a1e..0000000000 --- a/common/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: python - -# Runs jobs on container based infrastructure -sudo: false - -# Saves pip downloads/wheels between builds -cache: - directories: - - $HOME/.cache/pip - -addons: - apt: - packages: - - graphviz - -python: - - "3.6" - -install: - - pip install demjson graphviz - - pip install -r tools/requirements.txt - -script: - - jsonlint defs/*.json - - jsonlint defs/*/*.json - - python tools/cointool.py check - - python tools/support.py check --ignore-missing - - python protob/check.py - - python protob/graph.py protob/*.proto - -notifications: - webhooks: - urls: - - http://ci-bot.satoshilabs.com:5000/travis - on_success: always - on_failure: always - on_start: always diff --git a/common/defs/bitcoin/blockstamp.json b/common/defs/bitcoin/blockstamp.json new file mode 100644 index 0000000000..11c0975b7f --- /dev/null +++ b/common/defs/bitcoin/blockstamp.json @@ -0,0 +1,42 @@ +{ + "coin_name": "BlockStamp", + "coin_shortcut": "BST", + "coin_label": "BlockStamp", + "website": "https://blockstamp.info", + "github": "https://github.com/BlockStamp/bst", + "maintainer": "Krzysztof Kuchta ", + "curve_name": "secp256k1", + "address_type": 26, + "address_type_p2sh": 5, + "maxfee_kb": 2000000, + "minfee_kb": 1000, + "signed_message_header": "BST Signed Message:\n", + "hash_genesis_block": "8000000049a2e26b0185be50b4b8ed58b707c8893762959f0b1673641cae1828", + "xprv_magic": 76066276, + "xpub_magic": 76067358, + "xpub_magic_segwit_p2sh": 77429938, + "xpub_magic_segwit_native": 78792518, + "bech32_prefix": "bst", + "cashaddr_prefix": null, + "slip44": 254, + "segwit": true, + "decred": false, + "fork_id": null, + "force_bip143": false, + "bip115": false, + "default_fee_b": { + "Low": 10, + "Economy": 70, + "Normal": 140, + "High": 200 + }, + "dust_limit": 546, + "blocktime_seconds": 60, + "uri_prefix": "blockstamp", + "min_address_length": 27, + "max_address_length": 34, + "bitcore": [], + "blockbook": [], + "cooldown": 1000, + "consensus_branch_id": null +} diff --git a/common/defs/bitcoin/blockstamp.png b/common/defs/bitcoin/blockstamp.png new file mode 100644 index 0000000000..a92dc339d5 Binary files /dev/null and b/common/defs/bitcoin/blockstamp.png differ diff --git a/common/defs/bitcoin/faircoin.png b/common/defs/bitcoin/faircoin.png index f4ec63ea8d..f2f7c7a2fc 100644 Binary files a/common/defs/bitcoin/faircoin.png and b/common/defs/bitcoin/faircoin.png differ diff --git a/common/defs/bitcoin/fujicoin.json b/common/defs/bitcoin/fujicoin.json index 35fe18dc41..cfe8c572a6 100644 --- a/common/defs/bitcoin/fujicoin.json +++ b/common/defs/bitcoin/fujicoin.json @@ -8,8 +8,8 @@ "curve_name": "secp256k1", "address_type": 36, "address_type_p2sh": 16, - "maxfee_kb": 10000000, - "minfee_kb": 100000, + "maxfee_kb": 1000000000, + "minfee_kb": 10000000, "signed_message_header": "FujiCoin Signed Message:\n", "hash_genesis_block": "adb6d9cfd74075e7f91608add4bd2a2ea636f70856183086842667a1597714a0", "xpub_magic": 76067358, @@ -25,10 +25,10 @@ "force_bip143": false, "bip115": false, "default_fee_b": { - "Low": 100, - "Economy": 200, - "Normal": 500, - "High": 1000 + "Low": 10000, + "Economy": 20000, + "Normal": 50000, + "High": 100000 }, "dust_limit": 546, "blocktime_seconds": 60, diff --git a/common/defs/support.json b/common/defs/support.json index 4bd81189ee..65de9178b5 100644 --- a/common/defs/support.json +++ b/common/defs/support.json @@ -96,6 +96,7 @@ "bitcoin:BCH": "1.6.2", "bitcoin:BITC": "1.7.2", "bitcoin:BSD": "1.7.2", + "bitcoin:BST": "soon", "bitcoin:BTC": "1.5.2", "bitcoin:BTCP": "1.6.2", "bitcoin:BTDX": "1.7.2", @@ -106,6 +107,7 @@ "bitcoin:DGB": "1.6.3", "bitcoin:DNR": "1.7.1", "bitcoin:DOGE": "1.5.2", + "bitcoin:FAIR": "soon", "bitcoin:FJC": "1.6.1", "bitcoin:FLASH": "1.7.1", "bitcoin:FLO": "1.7.2", @@ -122,8 +124,10 @@ "bitcoin:NIX": "1.7.2", "bitcoin:NMC": "1.5.2", "bitcoin:PIVX": "1.8.0", + "bitcoin:POLIS": "soon", "bitcoin:PTC": "1.7.1", "bitcoin:QTUM": "1.8.1", + "bitcoin:REGTEST": "1.8.2", "bitcoin:RVN": "1.7.2", "bitcoin:SMART": "1.7.1", "bitcoin:TAZ": "1.6.2", @@ -135,6 +139,7 @@ "bitcoin:VTC": "1.6.1", "bitcoin:XMY": "1.7.1", "bitcoin:XPM": "1.8.0", + "bitcoin:XRC": "soon", "bitcoin:XSN": "1.8.0", "bitcoin:XZC": "1.6.2", "bitcoin:ZCL": "1.8.0", @@ -284,7 +289,6 @@ "erc20:eth:BRD": "1.6.2", "erc20:eth:BRLN": "1.8.0", "erc20:eth:BSDC": "1.6.2", - "erc20:eth:BST": "1.6.2", "erc20:eth:BTCA": "1.8.0", "erc20:eth:BTCE": "1.6.2", "erc20:eth:BTCL": "1.6.2", @@ -1232,9 +1236,11 @@ "eth:ETC": "1.6.2", "eth:ETH": "1.6.2", "eth:ETHO": "1.6.3", + "eth:ETI": "soon", "eth:ETSC": "1.6.2", "eth:EXP": "1.6.2", "eth:GO": "1.6.2", + "eth:META": "soon", "eth:MIX": "1.7.2", "eth:MUSIC": "1.6.3", "eth:PIRL": "1.6.3", @@ -1261,6 +1267,7 @@ "nem:XEM": "1.6.2" }, "unsupported": { + "bitcoin:CPC": "not implemented", "bitcoin:CRW": "address_type collides with Bitcoin", "bitcoin:TRC": "address_type collides with Bitcoin", "bitcoin:ZEN": "not implemented", @@ -1278,6 +1285,7 @@ "erc20:eth:BNC:ef51": "(AUTO) duplicate key", "erc20:eth:BOX:63f5": "(AUTO) duplicate key", "erc20:eth:BOX:e1a1": "(AUTO) duplicate key", + "erc20:eth:BST": "(AUTO) duplicate key", "erc20:eth:BTL (Battle)": "(AUTO) duplicate key", "erc20:eth:BTL (Bitlle)": "(AUTO) duplicate key", "erc20:eth:BTR:499a": "(AUTO) duplicate key", @@ -1417,12 +1425,14 @@ "erc20:eth:YEED:6f7a": "(AUTO) duplicate key", "erc20:eth:YEED:ca27": "(AUTO) duplicate key", "misc:ADA": "not implemented", + "misc:BNB": "not implemented", "misc:EOS": "not implemented", "misc:ONT": "not implemented", "misc:TRX": "not implemented", "misc:XMR": "not implemented", "misc:XRP": "not implemented", - "misc:XTZ": "not implemented" + "misc:XTZ": "not implemented", + "misc:tXRP": "not implemented" } }, "trezor2": { @@ -1432,6 +1442,7 @@ "bitcoin:BCH": "2.0.7", "bitcoin:BITC": "2.0.10", "bitcoin:BSD": "2.0.10", + "bitcoin:BST": "soon", "bitcoin:BTC": "2.0.5", "bitcoin:BTCP": "2.0.7", "bitcoin:BTDX": "2.0.10", @@ -1443,6 +1454,7 @@ "bitcoin:DGB": "2.0.7", "bitcoin:DNR": "2.0.8", "bitcoin:DOGE": "2.0.5", + "bitcoin:FAIR": "soon", "bitcoin:FJC": "2.0.5", "bitcoin:FLASH": "2.0.8", "bitcoin:FLO": "2.0.11", @@ -1459,8 +1471,10 @@ "bitcoin:NIX": "2.0.11", "bitcoin:NMC": "2.0.5", "bitcoin:PIVX": "2.0.11", + "bitcoin:POLIS": "soon", "bitcoin:PTC": "2.0.8", "bitcoin:QTUM": "2.1.1", + "bitcoin:REGTEST": "2.1.2", "bitcoin:RVN": "2.0.10", "bitcoin:SMART": "2.0.8", "bitcoin:TAZ": "2.0.7", @@ -1472,6 +1486,7 @@ "bitcoin:VTC": "2.0.5", "bitcoin:XMY": "2.0.8", "bitcoin:XPM": "2.0.11", + "bitcoin:XRC": "soon", "bitcoin:XSN": "2.0.11", "bitcoin:XZC": "2.0.7", "bitcoin:ZCL": "2.0.11", @@ -1622,7 +1637,6 @@ "erc20:eth:BRD": "2.0.7", "erc20:eth:BRLN": "2.0.10", "erc20:eth:BSDC": "2.0.7", - "erc20:eth:BST": "2.0.7", "erc20:eth:BTCA": "2.0.10", "erc20:eth:BTCE": "2.0.7", "erc20:eth:BTCL": "2.0.7", @@ -2570,9 +2584,11 @@ "eth:ETC": "2.0.7", "eth:ETH": "2.0.7", "eth:ETHO": "2.0.8", + "eth:ETI": "soon", "eth:ETSC": "2.0.7", "eth:EXP": "2.0.7", "eth:GO": "2.0.7", + "eth:META": "soon", "eth:MIX": "2.0.10", "eth:MUSIC": "2.0.8", "eth:PIRL": "2.0.8", @@ -2620,6 +2636,7 @@ "erc20:eth:BNC:ef51": "(AUTO) duplicate key", "erc20:eth:BOX:63f5": "(AUTO) duplicate key", "erc20:eth:BOX:e1a1": "(AUTO) duplicate key", + "erc20:eth:BST": "(AUTO) duplicate key", "erc20:eth:BTL (Battle)": "(AUTO) duplicate key", "erc20:eth:BTL (Bitlle)": "(AUTO) duplicate key", "erc20:eth:BTR:499a": "(AUTO) duplicate key", diff --git a/common/defs/webauthn/gen.py b/common/defs/webauthn/gen.py index e7b094439d..420cd6602d 100755 --- a/common/defs/webauthn/gen.py +++ b/common/defs/webauthn/gen.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 +import json import sys from glob import glob -import json from hashlib import sha256 - try: opt = sys.argv[1] -except: +except IndexError: print("Usage: gen.py [core|mcu|check])") sys.exit(1) @@ -22,12 +21,12 @@ def gen_core(data): for d in data: if "u2f" in d: url, label = d["u2f"], d["label"] - print(" \"%s\": \"%s\"," % (url, label)) + print(' "%s": "%s",' % (url, label)) print(" # WebAuthn") for d in data: if "webauthn" in d: origin, label = d["webauthn"], d["label"] - print(" \"%s\": \"%s\"," % (origin, label)) + print(' "%s": "%s",' % (origin, label)) print("}") @@ -36,12 +35,17 @@ def gen_mcu(data): if "u2f" in d: url, label = d["u2f"], d["label"] h = sha256(url.encode()).digest() - print("\t{\n\t\t// U2F: %s\n\t\t%s,\n\t\t\"%s\"\n\t}," % (url, c_bytes(h), label)) + print( + '\t{\n\t\t// U2F: %s\n\t\t%s,\n\t\t"%s"\n\t},' + % (url, c_bytes(h), label) + ) if "webauthn" in d: origin, label = d["webauthn"], d["label"] h = sha256(origin.encode()).digest() - print("\t{\n\t\t// WebAuthn: %s\n\t\t%s,\n\t\t\"%s\"\n\t}," % (origin, c_bytes(h), label)) - + print( + '\t{\n\t\t// WebAuthn: %s\n\t\t%s,\n\t\t"%s"\n\t},' + % (origin, c_bytes(h), label) + ) data = [] diff --git a/common/keys/public_trezor1.h b/common/keys/public_trezor1.h index ebd7d5b3e1..f4e1d83739 100644 --- a/common/keys/public_trezor1.h +++ b/common/keys/public_trezor1.h @@ -1,3 +1,4 @@ +// clang-format off // TREZORv1 production public keys "\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", "\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", diff --git a/common/keys/sample.h b/common/keys/sample.h index 1518d4f3b1..ce9f2d417f 100644 --- a/common/keys/sample.h +++ b/common/keys/sample.h @@ -1,2 +1,3 @@ +// clang-format off // sample public key "correct horse battery staple" "\x04\x78\xd4\x30\x27\x4f\x8c\x5e\xc1\x32\x13\x38\x15\x1e\x9f\x27\xf4\xc6\x76\xa0\x08\xbd\xf8\x63\x8d\x07\xc0\xb6\xbe\x9a\xb3\x5c\x71\xa1\x51\x80\x63\x24\x3a\xcd\x4d\xfe\x96\xb6\x6e\x3f\x2e\xc8\x01\x3c\x8e\x07\x2c\xd0\x9b\x38\x34\xa1\x9f\x81\xf6\x59\xcc\x34\x55" diff --git a/common/protob/check.py b/common/protob/check.py index 0b612d4e8d..9c64682166 100755 --- a/common/protob/check.py +++ b/common/protob/check.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -from glob import glob import os import re import sys +from glob import glob error = False @@ -21,7 +21,9 @@ for fn in sorted(glob(os.path.join(MYDIR, "messages-*.proto"))): line = line.strip().split(" ") if line[0] not in ["enum", "message"]: continue - if not line[1].startswith(prefix) and not line[1].startswith("Debug" + prefix): + if not line[1].startswith(prefix) and not line[1].startswith( + "Debug" + prefix + ): print("ERROR:", fn, line[1]) error = True diff --git a/common/protob/messages-tezos.proto b/common/protob/messages-tezos.proto index e429ce8b86..d6fbfea65a 100644 --- a/common/protob/messages-tezos.proto +++ b/common/protob/messages-tezos.proto @@ -55,6 +55,8 @@ message TezosSignTx { optional TezosTransactionOp transaction = 4; // Tezos transaction operation optional TezosOriginationOp origination = 5; // Tezos origination operation optional TezosDelegationOp delegation = 6; // Tezos delegation operation + optional TezosProposalOp proposal = 7; // Tezos proposal operation + optional TezosBallotOp ballot = 8; // Tezos ballot operation /* * Tezos contract ID */ @@ -120,6 +122,29 @@ message TezosSignTx { optional uint64 storage_limit = 5; optional bytes delegate = 6; } + /** + * Structure representing information for proposal + */ + message TezosProposalOp { + optional bytes source = 1; //Contains only public_key_hash, not to be confused with TezosContractID + optional uint64 period = 2; + repeated bytes proposals = 4; + } + /** + * Structure representing information for ballot + */ + message TezosBallotOp { + optional bytes source = 1; //Contains only public_key_hash, not to be confused with TezosContractID + optional uint64 period = 2; + optional bytes proposal = 3; + optional TezosBallotType ballot = 4; + + enum TezosBallotType { + Yay = 0; + Nay = 1; + Pass = 2; + } + } } /** diff --git a/common/tools/coin_info.py b/common/tools/coin_info.py index 9ca8026af1..df0880ebf9 100755 --- a/common/tools/coin_info.py +++ b/common/tools/coin_info.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -from collections import defaultdict, OrderedDict -import re -import os -import json import glob +import json import logging +import os +import re +from collections import OrderedDict, defaultdict try: import requests diff --git a/common/tools/coindef.py b/common/tools/coindef.py index e96246054e..50eb50c79a 100644 --- a/common/tools/coindef.py +++ b/common/tools/coindef.py @@ -3,38 +3,38 @@ from trezorlib import protobuf as p class CoinDef(p.MessageType): FIELDS = { - 1: ('coin_name', p.UnicodeType, 0), - 2: ('coin_shortcut', p.UnicodeType, 0), - 3: ('coin_label', p.UnicodeType, 0), - 4: ('curve_name', p.UnicodeType, 0), - 5: ('address_type', p.UVarintType, 0), - 6: ('address_type_p2sh', p.UVarintType, 0), - 7: ('maxfee_kb', p.UVarintType, 0), - 8: ('minfee_kb', p.UVarintType, 0), - 9: ('signed_message_header', p.BytesType, 0), - 10: ('hash_genesis_block', p.BytesType, 0), - 11: ('xprv_magic', p.UVarintType, 0), - 12: ('xpub_magic', p.UVarintType, 0), - 13: ('xpub_magic_segwit_p2sh', p.UVarintType, 0), - 14: ('xpub_magic_segwit_native', p.UVarintType, 0), - 15: ('bech32_prefix', p.UnicodeType, 0), - 16: ('cashaddr_prefix', p.UnicodeType, 0), - 17: ('slip44', p.UVarintType, 0), - 18: ('segwit', p.BoolType, 0), - 19: ('decred', p.BoolType, 0), - 20: ('fork_id', p.UVarintType, 0), - 21: ('force_bip143', p.BoolType, 0), - 22: ('dust_limit', p.UVarintType, 0), - 23: ('uri_prefix', p.UnicodeType, 0), - 24: ('min_address_length', p.UVarintType, 0), - 25: ('max_address_length', p.UVarintType, 0), - 26: ('icon', p.BytesType, 0), - 28: ('website', p.UnicodeType, 0), - 29: ('github', p.UnicodeType, 0), - 30: ('maintainer', p.UnicodeType, 0), - 31: ('blocktime_seconds', p.UVarintType, 0), - 32: ('bip115', p.BoolType, 0), - 33: ('cooldown', p.UVarintType, 0), + 1: ("coin_name", p.UnicodeType, 0), + 2: ("coin_shortcut", p.UnicodeType, 0), + 3: ("coin_label", p.UnicodeType, 0), + 4: ("curve_name", p.UnicodeType, 0), + 5: ("address_type", p.UVarintType, 0), + 6: ("address_type_p2sh", p.UVarintType, 0), + 7: ("maxfee_kb", p.UVarintType, 0), + 8: ("minfee_kb", p.UVarintType, 0), + 9: ("signed_message_header", p.BytesType, 0), + 10: ("hash_genesis_block", p.BytesType, 0), + 11: ("xprv_magic", p.UVarintType, 0), + 12: ("xpub_magic", p.UVarintType, 0), + 13: ("xpub_magic_segwit_p2sh", p.UVarintType, 0), + 14: ("xpub_magic_segwit_native", p.UVarintType, 0), + 15: ("bech32_prefix", p.UnicodeType, 0), + 16: ("cashaddr_prefix", p.UnicodeType, 0), + 17: ("slip44", p.UVarintType, 0), + 18: ("segwit", p.BoolType, 0), + 19: ("decred", p.BoolType, 0), + 20: ("fork_id", p.UVarintType, 0), + 21: ("force_bip143", p.BoolType, 0), + 22: ("dust_limit", p.UVarintType, 0), + 23: ("uri_prefix", p.UnicodeType, 0), + 24: ("min_address_length", p.UVarintType, 0), + 25: ("max_address_length", p.UVarintType, 0), + 26: ("icon", p.BytesType, 0), + 28: ("website", p.UnicodeType, 0), + 29: ("github", p.UnicodeType, 0), + 30: ("maintainer", p.UnicodeType, 0), + 31: ("blocktime_seconds", p.UVarintType, 0), + 32: ("bip115", p.BoolType, 0), + 33: ("cooldown", p.UVarintType, 0), } def __init__( @@ -73,7 +73,7 @@ class CoinDef(p.MessageType): default_fee_b: dict = None, bitcore: dict = None, blockbook: dict = None, - cooldown: int = None + cooldown: int = None, ): self.coin_name = coin_name self.coin_shortcut = coin_shortcut diff --git a/common/tools/coins_details.py b/common/tools/coins_details.py index a637fc2d5f..e9bcd986bb 100755 --- a/common/tools/coins_details.py +++ b/common/tools/coins_details.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 """Fetch information about coins and tokens supported by Trezor and update it in coins_details.json.""" -import os -import time import json import logging -import requests +import os import sys -import coin_info +import time import click +import requests + +import coin_info LOG = logging.getLogger(__name__) @@ -149,7 +150,7 @@ def summary(coins, api_key): try: ret = coinmarketcap_call("global-metrics/quotes/latest", api_key) total_marketcap = int(ret["data"]["quote"]["USD"]["total_market_cap"]) - except: + except Exception: pass return dict( diff --git a/common/tools/cointool.py b/common/tools/cointool.py index d9ad5526ff..706d1bf724 100755 --- a/common/tools/cointool.py +++ b/common/tools/cointool.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 import fnmatch +import glob import io import json import logging -import re -import sys import os -import glob +import re import struct +import sys import zlib from collections import defaultdict from hashlib import sha256 @@ -209,7 +209,7 @@ def check_btc(coins): for coin in bucket: name = coin["name"] prefix = "" - if name.endswith("Testnet"): + if name.endswith("Testnet") or name.endswith("Regtest"): color = "green" elif name == "Bitcoin": color = "red" @@ -232,7 +232,12 @@ def check_btc(coins): """ failed = False for key, bucket in buckets.items(): - mainnets = [c for c in bucket if not c["name"].endswith("Testnet")] + mainnets = [ + c + for c in bucket + if not c["name"].endswith("Testnet") + and not c["name"].endswith("Regtest") + ] have_bitcoin = False for coin in mainnets: @@ -524,8 +529,7 @@ def cli(colors): # fmt: off @click.option("--backend/--no-backend", "-b", default=False, help="Check blockbook/bitcore responses") @click.option("--icons/--no-icons", default=True, help="Check icon files") -@click.option("-d", "--show-duplicates", type=click.Choice(("all", "nontoken", "errors")), - default="errors", help="How much information about duplicate shortcuts should be shown.") +@click.option("-d", "--show-duplicates", type=click.Choice(("all", "nontoken", "errors")), default="errors", help="How much information about duplicate shortcuts should be shown.") # fmt: on def check(backend, icons, show_duplicates): """Validate coin definitions. @@ -779,8 +783,9 @@ def coindefs(outfile): @click.argument("paths", metavar="[path]...", nargs=-1) @click.option("-o", "--outfile", type=click.File("w"), help="Alternate output file") @click.option("-v", "--verbose", is_flag=True, help="Print rendered file names") +@click.option("-b", "--bitcoin-only", is_flag=True, help="Accept only Bitcoin coins") # fmt: on -def render(paths, outfile, verbose): +def render(paths, outfile, verbose, bitcoin_only): """Generate source code from Mako templates. For every "foo.bar.mako" filename passed, runs the template and @@ -801,6 +806,13 @@ def render(paths, outfile, verbose): defs = coin_info.coin_info() support_info = coin_info.support_info(defs) + if bitcoin_only: + defs["bitcoin"] = [ + x + for x in defs["bitcoin"] + if x["coin_name"] in ("Bitcoin", "Testnet", "Regtest") + ] + # munch dicts - make them attribute-accessible for key, value in defs.items(): defs[key] = [Munch(coin) for coin in value] diff --git a/common/tools/diffize_coins_details.py b/common/tools/diffize_coins_details.py index 1e1a697960..2ad579a8f3 100755 --- a/common/tools/diffize_coins_details.py +++ b/common/tools/diffize_coins_details.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 -import click import json import os -import requests -import tempfile import subprocess -import sys +import tempfile +import click +import requests LIVE_URL = "https://trezor.io/static/json/coins_details.json" COINS_DETAILS = os.path.join( diff --git a/common/tools/requirements.txt b/common/tools/requirements.txt deleted file mode 100644 index 59cab2694a..0000000000 --- a/common/tools/requirements.txt +++ /dev/null @@ -1,16 +0,0 @@ -click>=6 - -# for `coin_gen coindefs` and checking icons: -ed25519>=1.4 -Pillow>=5.2.0 -trezor>=0.10 - -# for checking backends, generating coins_details.json -requests>=2.19 - -# for rendering templates: -Mako>=1.0.7 -munch>=2.3.2 - -# for pretty colors in checks -termcolor >= 0.1.2 diff --git a/common/tools/support.py b/common/tools/support.py index 593387eb57..57bf24d2b6 100755 --- a/common/tools/support.py +++ b/common/tools/support.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 -import re +import json import os +import re import subprocess import sys + import click + import coin_info -import json SUPPORT_INFO = coin_info.get_support_data() diff --git a/core/.travis.yml b/core/.travis.yml deleted file mode 100644 index 10b250a848..0000000000 --- a/core/.travis.yml +++ /dev/null @@ -1,78 +0,0 @@ -sudo: false -dist: trusty -language: c - -addons: - apt: - sources: - - deadsnakes - packages: - - build-essential - - python3.6 - - python3.6-dev - - python3.6-venv - - libusb-1.0-0-dev - - libudev-dev - -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: - - GOAL=stm32 - - GOAL=unix - - GOAL=src - -matrix: - include: - - compiler: clang - env: GOAL=unix - -cache: - directories: - - $HOME/libsodium - -before_install: - - $PYTHON -m ensurepip --user - - $PYTHON -m pip install --user pipenv - -install: - - ./travis-install-libsodium.sh - - export PKG_CONFIG_PATH=$HOME/libsodium/lib/pkgconfig:$PKG_CONFIG_PATH - - export LD_LIBRARY_PATH=$HOME/libsodium/lib:$LD_LIBRARY_PATH - - 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 "$GOAL" != "stm32" || wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/$TOOLCHAIN_SHORTVER/$TOOLCHAIN_LONGVER-linux.tar.bz2 - - test "$GOAL" != "stm32" || tar xfj $TOOLCHAIN_LONGVER-linux.tar.bz2 - - test "$GOAL" != "stm32" || export PATH=$PWD/$TOOLCHAIN_LONGVER/bin:$PATH - -script: - - test "$GOAL" != "src" || pipenv run make style_check - - test "$GOAL" != "src" || pipenv run make templates_check - - - test "$GOAL" != "stm32" || pipenv run make build_cross - - test "$GOAL" != "stm32" || pipenv run make build_boardloader - - test "$GOAL" != "stm32" || pipenv run make build_bootloader - - test "$GOAL" != "stm32" || pipenv run make build_prodtest - - test "$GOAL" != "stm32" || pipenv run make build_firmware - - test "$GOAL" != "stm32" || test "$TREZOR_MODEL" = "1" || pipenv run make sizecheck - - - test "$GOAL" != "unix" || pipenv run make build_unix_noui - - test "$GOAL" != "unix" || pipenv run make test - - test "$GOAL" != "unix" || test "$TREZOR_MODEL" = "1" || pipenv run make test_emu - - test "$GOAL" != "unix" || test "$TREZOR_MODEL" = "1" || pipenv run make test_emu_monero - -notifications: - webhooks: - urls: - - http://ci-bot.satoshilabs.com:5000/travis - on_success: always - on_failure: always - on_start: always diff --git a/core/ChangeLog b/core/ChangeLog index 1f0eb7c99a..dde615edc2 100644 --- a/core/ChangeLog +++ b/core/ChangeLog @@ -1,5 +1,11 @@ -Version 2.1.0 -* stable release, optional update +Version 2.1.1 [unreleased] +* More strict path validations +* Display non-zero locktime values +* Don't rotate the screen via swipe gesture +* Set screen rotation via user setting +* Monero UI fixes + +Version 2.1.0 [Mar 2019] * Security improvements * Upgraded to new storage format * Ripple, Stellar, Cardano and NEM fixes @@ -7,20 +13,17 @@ Version 2.1.0 PIVX, REOSC, XPM, XSN, ZCL * New ETH tokens -Version 2.0.10 -* stable release, optional update +Version 2.0.10 [Dec 2018] * Fix Monero payment ID computation * Fix issue with touch screen and flickering * Add support for OMNI layer: OMNI/MAID/USDT * Add support for new coins: BTX, CPC, GAME, RVN * Add support for new Ethereum tokens -Version 2.0.9 -* stable release, optional update +Version 2.0.9 [Nov 2018] * Small Monero and Segwit bugfixes -Version 2.0.8 -* stable release, optional update +Version 2.0.8 [Oct 2018] * Monero support * Cardano support * Stellar support @@ -32,25 +35,22 @@ Version 2.0.8 * Zcash sapling hardfork support * Implemented seedless setup -Version 2.0.7 -* stable release, optional update +Version 2.0.7 [Jun 2018] * Bitcoin Cash cashaddr support * Zcash Overwinter hardfork support * NEM support * Lisk support * Show warning on home screen if PIN is not set * Support for new coins: - - Bitcoin Private, Fujicoin, Vertcoin, Viacoin, Zcoin + Bitcoin Private, Fujicoin, Vertcoin, Viacoin, Zcoin * Support for new Ethereum networks: - - EOS Classic, Ethereum Social, Ellaism, Callisto, EtherGem, Wanchain + EOS Classic, Ethereum Social, Ellaism, Callisto, EtherGem, Wanchain * Support for 500+ new Ethereum tokens -Version 2.0.6 -* stable release, optional update +Version 2.0.6 [Mar 2018] * fix layout for Ethereum transactions * fix public key generation for SSH and GPG * add special characters to passphrase keyboard -Version 2.0.5 -* stable release, required update +Version 2.0.5 [Mar 2018] * first public release diff --git a/core/Dockerfile b/core/Dockerfile deleted file mode 100644 index 3f5aa1c863..0000000000 --- a/core/Dockerfile +++ /dev/null @@ -1,69 +0,0 @@ -# initialize from the image - -FROM debian:9 - -ARG TOOLCHAIN_FLAVOR=linux -ENV TOOLCHAIN_FLAVOR=$TOOLCHAIN_FLAVOR - -# install build tools and dependencies - -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 - -# 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 -RUN ln -s /usr/bin/python3 /usr/bin/python - -# 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 - -# install python dependencies - -RUN $PYTHON -m pip install scons trezor diff --git a/core/Makefile b/core/Makefile index fb945738fb..c684a6eb3a 100644 --- a/core/Makefile +++ b/core/Makefile @@ -39,7 +39,7 @@ CFLAGS += -DGITREV=$(GITREV) ## help commands: help: ## show this help - @awk -f help.awk $(MAKEFILE_LIST) + @awk -f ../tools/help.awk $(MAKEFILE_LIST) ## dependencies commands: @@ -71,27 +71,6 @@ test_emu_monero: ## run selected monero device tests from monero-agent pylint: ## run pylint on application sources and tests pylint -E $(shell find src tests -name *.py) -## style commands: - -style_check: ## run code style check on application sources and tests - flake8 --version - isort --version | grep "VERSION" - black --version - flake8 $(shell find src -name *.py) - isort --check-only $(shell find src -name *.py ! -path 'src/trezor/messages/*') - black --check $(shell find src -name *.py ! -path 'src/trezor/messages/*') - -style: ## apply code style on application sources and tests - isort $(shell find src -name *.py ! -path 'src/trezor/messages/*') - black $(shell find src -name *.py ! -path 'src/trezor/messages/*') - -cstyle_check: ## run code style check on low-level C code - ./tools/clang-format-check embed/*/*.{c,h} embed/extmod/modtrezor*/*.{c,h} - - -cstyle: ## apply code style on low-level C code - clang-format -i embed/*/*.{c,h} embed/extmod/modtrezor*/*.{c,h} - ## code generation: templates: ## render Mako templates (for lists of coins, tokens, etc.) diff --git a/core/build-docker.sh b/core/build-docker.sh index deec6c7bd3..52843e93e1 100755 --- a/core/build-docker.sh +++ b/core/build-docker.sh @@ -1,6 +1,8 @@ #!/bin/sh set -e +cd "$(dirname $0)/.." + if [ "$1" = "--gcc_source" ]; then TOOLCHAIN_FLAVOR=src shift @@ -16,16 +18,19 @@ PRODUCTION=${PRODUCTION:-0} if [ "$REPOSITORY" = "local" ]; then REPOSITORY=file:///local/ else - REPOSITORY=https://github.com/$REPOSITORY/trezor-core.git + REPOSITORY=https://github.com/$REPOSITORY/trezor-firmware.git fi -docker build -t $IMAGE --build-arg TOOLCHAIN_FLAVOR=$TOOLCHAIN_FLAVOR . +docker build -t $IMAGE --build-arg TOOLCHAIN_FLAVOR=$TOOLCHAIN_FLAVOR ci/ -mkdir -p $(pwd)/build-docker -docker run -t -v $(pwd):/local -v $(pwd)/build-docker:/build:z --user="$(stat -c "%u:%g" .)" $IMAGE /bin/sh -c "\ +USER=$(ls -lnd . | awk '{ print $3 }') +GROUP=$(ls -lnd . | awk '{ print $4 }') + +mkdir -p $(pwd)/build/core +docker run -t -v $(pwd):/local -v $(pwd)/build/core:/build:z --user="$USER:$GROUP" $IMAGE /bin/sh -c "\ cd /tmp && \ - git clone $REPOSITORY trezor-core && \ - cd trezor-core && \ + git clone $REPOSITORY trezor-firmware && \ + cd trezor-firmware/core && \ ln -s /build build && git checkout $TAG && \ git submodule update --init --recursive && \ diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index e5e84b6a6c..fc2a387739 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -236,7 +236,7 @@ void ui_screen_install_confirm_newvendor(const vendor_header *const vhdr, void ui_screen_install(void) { display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); - display_loader(0, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install, + display_loader(0, false, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install, sizeof(toi_icon_install), COLOR_BLACK); display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24, "Installing firmware", -1, FONT_NORMAL, COLOR_BLACK, @@ -244,13 +244,13 @@ void ui_screen_install(void) { } void ui_screen_install_progress_erase(int pos, int len) { - display_loader(250 * pos / len, -20, COLOR_BL_PROCESS, COLOR_WHITE, + display_loader(250 * pos / len, false, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install, sizeof(toi_icon_install), COLOR_BLACK); } void ui_screen_install_progress_upload(int pos) { - display_loader(pos, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_install, - sizeof(toi_icon_install), COLOR_BLACK); + display_loader(pos, false, -20, COLOR_BL_PROCESS, COLOR_WHITE, + toi_icon_install, sizeof(toi_icon_install), COLOR_BLACK); } // wipe UI @@ -274,14 +274,14 @@ void ui_screen_wipe_confirm(void) { void ui_screen_wipe(void) { display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); - display_loader(0, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_wipe, + display_loader(0, false, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_wipe, sizeof(toi_icon_wipe), COLOR_BLACK); display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24, "Wiping device", -1, FONT_NORMAL, COLOR_BLACK, COLOR_WHITE); } void ui_screen_wipe_progress(int pos, int len) { - display_loader(1000 * pos / len, -20, COLOR_BL_PROCESS, COLOR_WHITE, + display_loader(1000 * pos / len, false, -20, COLOR_BL_PROCESS, COLOR_WHITE, toi_icon_wipe, sizeof(toi_icon_wipe), COLOR_BLACK); } @@ -300,7 +300,7 @@ void ui_screen_done(int restart_seconds, secbool full_redraw) { if (sectrue == full_redraw) { display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); } - display_loader(1000, -20, COLOR_BL_DONE, COLOR_WHITE, toi_icon_done, + display_loader(1000, false, -20, COLOR_BL_DONE, COLOR_WHITE, toi_icon_done, sizeof(toi_icon_done), COLOR_BLACK); if (secfalse == full_redraw) { display_bar(0, DISPLAY_RESY - 24 - 18, 240, 23, COLOR_WHITE); @@ -313,7 +313,7 @@ void ui_screen_done(int restart_seconds, secbool full_redraw) { void ui_screen_fail(void) { display_bar(0, 0, DISPLAY_RESX, DISPLAY_RESY, COLOR_WHITE); - display_loader(1000, -20, COLOR_BL_FAIL, COLOR_WHITE, toi_icon_fail, + display_loader(1000, false, -20, COLOR_BL_FAIL, COLOR_WHITE, toi_icon_fail, sizeof(toi_icon_fail), COLOR_BLACK); display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 24, "Failed! Please, reconnect.", -1, FONT_NORMAL, diff --git a/core/embed/bootloader/version.h b/core/embed/bootloader/version.h index 5cbcb75b50..05d8755e76 100644 --- a/core/embed/bootloader/version.h +++ b/core/embed/bootloader/version.h @@ -1,6 +1,6 @@ #define VERSION_MAJOR 2 #define VERSION_MINOR 0 -#define VERSION_PATCH 3 +#define VERSION_PATCH 4 #define VERSION_BUILD 0 #define VERSION_UINT32 \ (VERSION_MAJOR | (VERSION_MINOR << 8) | (VERSION_PATCH << 16) | \ diff --git a/core/embed/extmod/modtrezorui/display-stm32_1.h b/core/embed/extmod/modtrezorui/display-stm32_1.h index cf3c82f2e7..7b05938687 100644 --- a/core/embed/extmod/modtrezorui/display-stm32_1.h +++ b/core/embed/extmod/modtrezorui/display-stm32_1.h @@ -241,6 +241,4 @@ void display_refresh(void) { HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET); // set to CMD } -const char *display_save(const char *prefix) { - return NULL; -} +const char *display_save(const char *prefix) { return NULL; } diff --git a/core/embed/extmod/modtrezorui/display-stm32_t.h b/core/embed/extmod/modtrezorui/display-stm32_t.h index 653c83da98..bedd24aa9c 100644 --- a/core/embed/extmod/modtrezorui/display-stm32_t.h +++ b/core/embed/extmod/modtrezorui/display-stm32_t.h @@ -498,6 +498,4 @@ void display_refresh(void) { } } -const char *display_save(const char *prefix) { - return NULL; -} +const char *display_save(const char *prefix) { return NULL; } diff --git a/core/embed/extmod/modtrezorui/display.c b/core/embed/extmod/modtrezorui/display.c index eba7ecfd28..fedda40abf 100644 --- a/core/embed/extmod/modtrezorui/display.c +++ b/core/embed/extmod/modtrezorui/display.c @@ -316,9 +316,9 @@ static void inflate_callback_loader(uint8_t byte, uint32_t pos, #endif -void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor, - uint16_t bgcolor, const uint8_t *icon, uint32_t iconlen, - uint16_t iconfgcolor) { +void display_loader(uint16_t progress, bool indeterminate, int yoffset, + uint16_t fgcolor, uint16_t bgcolor, const uint8_t *icon, + uint32_t iconlen, uint16_t iconfgcolor) { #if TREZOR_MODEL == T uint16_t colortable[16], iconcolortable[16]; set_color_table(colortable, fgcolor, bgcolor); @@ -362,6 +362,7 @@ void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor, } // inside of circle - draw glyph #define LOADER_ICON_CORNER_CUT 2 +#define LOADER_INDETERMINATE_WIDTH 100 if (icon && mx + my > (((LOADER_ICON_SIZE / 2) + LOADER_ICON_CORNER_CUT) * 2) && mx >= img_loader_size - (LOADER_ICON_SIZE / 2) && @@ -378,10 +379,21 @@ void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor, PIXELDATA(iconcolortable[c]); } else { uint8_t c; - if (progress > a) { - c = (img_loader[my][mx] & 0x00F0) >> 4; + if (indeterminate) { + uint16_t diff = + (progress > a) ? (progress - a) : (1000 + progress - a); + if (diff < LOADER_INDETERMINATE_WIDTH || + diff > 1000 - LOADER_INDETERMINATE_WIDTH) { + c = (img_loader[my][mx] & 0x00F0) >> 4; + } else { + c = img_loader[my][mx] & 0x000F; + } } else { - c = img_loader[my][mx] & 0x000F; + if (progress > a) { + c = (img_loader[my][mx] & 0x00F0) >> 4; + } else { + c = img_loader[my][mx] & 0x000F; + } } PIXELDATA(colortable[c]); } diff --git a/core/embed/extmod/modtrezorui/display.h b/core/embed/extmod/modtrezorui/display.h index 578025d49f..baffc93001 100644 --- a/core/embed/extmod/modtrezorui/display.h +++ b/core/embed/extmod/modtrezorui/display.h @@ -21,6 +21,7 @@ #define __DISPLAY_H__ #include +#include #if TREZOR_MODEL == T @@ -82,9 +83,9 @@ void display_avatar(int x, int y, const void *data, int datalen, uint16_t fgcolor, uint16_t bgcolor); void display_icon(int x, int y, int w, int h, const void *data, int datalen, uint16_t fgcolor, uint16_t bgcolor); -void display_loader(uint16_t progress, int yoffset, uint16_t fgcolor, - uint16_t bgcolor, const uint8_t *icon, uint32_t iconlen, - uint16_t iconfgcolor); +void display_loader(uint16_t progress, bool indeterminate, int yoffset, + uint16_t fgcolor, uint16_t bgcolor, const uint8_t *icon, + uint32_t iconlen, uint16_t iconfgcolor); #ifndef TREZOR_PRINT_DISABLE void display_print_color(uint16_t fgcolor, uint16_t bgcolor); diff --git a/core/embed/extmod/modtrezorui/modtrezorui-display.h b/core/embed/extmod/modtrezorui/modtrezorui-display.h index 862cd11991..319b1308c0 100644 --- a/core/embed/extmod/modtrezorui/modtrezorui-display.h +++ b/core/embed/extmod/modtrezorui/modtrezorui-display.h @@ -197,8 +197,8 @@ STATIC mp_obj_t mod_trezorui_Display_icon(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_icon_obj, 6, 6, mod_trezorui_Display_icon); -/// def loader(self, progress: int, yoffset: int, fgcolor: int, bgcolor: int, -/// icon: bytes = None, iconfgcolor: int = None) -> None: +/// def loader(self, progress: int, indeterminate: bool, yoffset: int, fgcolor: +/// int, bgcolor: int, icon: bytes = None, iconfgcolor: int = None) -> None: /// ''' /// Renders a rotating loader graphic. /// Progress determines its position (0-1000), fgcolor is used as foreground @@ -210,12 +210,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_icon_obj, 6, 6, STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args, const mp_obj_t *args) { mp_int_t progress = mp_obj_get_int(args[1]); - mp_int_t yoffset = mp_obj_get_int(args[2]); - mp_int_t fgcolor = mp_obj_get_int(args[3]); - mp_int_t bgcolor = mp_obj_get_int(args[4]); - if (n_args > 5) { // icon provided + bool indeterminate = args[2] == mp_const_true; + mp_int_t yoffset = mp_obj_get_int(args[3]); + mp_int_t fgcolor = mp_obj_get_int(args[4]); + mp_int_t bgcolor = mp_obj_get_int(args[5]); + if (n_args > 6) { // icon provided mp_buffer_info_t icon; - mp_get_buffer_raise(args[5], &icon, MP_BUFFER_READ); + mp_get_buffer_raise(args[6], &icon, MP_BUFFER_READ); const uint8_t *data = icon.buf; if (icon.len < 8 || memcmp(data, "TOIg", 4) != 0) { mp_raise_ValueError("Invalid image format"); @@ -230,20 +231,21 @@ STATIC mp_obj_t mod_trezorui_Display_loader(size_t n_args, mp_raise_ValueError("Invalid size of data"); } uint16_t iconfgcolor; - if (n_args > 6) { // icon color provided - iconfgcolor = mp_obj_get_int(args[6]); + if (n_args > 7) { // icon color provided + iconfgcolor = mp_obj_get_int(args[7]); } else { iconfgcolor = ~bgcolor; // invert } - display_loader(progress, yoffset, fgcolor, bgcolor, icon.buf, icon.len, - iconfgcolor); + display_loader(progress, indeterminate, yoffset, fgcolor, bgcolor, icon.buf, + icon.len, iconfgcolor); } else { - display_loader(progress, yoffset, fgcolor, bgcolor, NULL, 0, 0); + display_loader(progress, indeterminate, yoffset, fgcolor, bgcolor, NULL, 0, + 0); } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_loader_obj, 5, - 7, mod_trezorui_Display_loader); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_loader_obj, 6, + 8, mod_trezorui_Display_loader); /// def print(self, text: str) -> None: /// ''' diff --git a/core/embed/firmware/version.h b/core/embed/firmware/version.h index 0e57ccee0a..1ed18ec6e4 100644 --- a/core/embed/firmware/version.h +++ b/core/embed/firmware/version.h @@ -1,6 +1,6 @@ #define VERSION_MAJOR 2 #define VERSION_MINOR 1 -#define VERSION_PATCH 0 +#define VERSION_PATCH 1 #define VERSION_BUILD 0 #define FIX_VERSION_MAJOR 2 diff --git a/core/embed/unix/mpconfigport.h b/core/embed/unix/mpconfigport.h index 8619bf1a57..6f618d2b76 100644 --- a/core/embed/unix/mpconfigport.h +++ b/core/embed/unix/mpconfigport.h @@ -1,3 +1,5 @@ +// clang-format off + /* * This file is part of the MicroPython project, http://micropython.org/ * diff --git a/core/emu.sh b/core/emu.sh index d6fca1ee44..eddb3000a3 100755 --- a/core/emu.sh +++ b/core/emu.sh @@ -5,7 +5,6 @@ source emu.config 2>/dev/null EXE=build/unix/micropython PYOPT="${PYOPT:-1}" MAIN="${MAIN:-${PWD}/src/main.py}" -BROWSER="${BROWSER:-chromium}" HEAPSIZE="${HEAPSIZE:-50M}" SOURCE_PY_DIR="${SOURCE_PY_DIR:-src}" @@ -33,14 +32,6 @@ case "$1" in kill $UPY_PID done ;; - "-p") - shift - ../$EXE $ARGS $* $MAIN & - perf record -F 100 -p $! -g -- sleep 600 - perf script > perf.trace - ../vendor/flamegraph/stackcollapse-perf.pl perf.trace | ../vendor/flamegraph/flamegraph.pl > perf.svg - $BROWSER perf.svg - ;; *) ../$EXE $ARGS $* $MAIN esac diff --git a/core/src/apps/cardano/layout/progress.py b/core/src/apps/cardano/layout/progress.py index 0068f90c13..fe08d6f0c9 100644 --- a/core/src/apps/cardano/layout/progress.py +++ b/core/src/apps/cardano/layout/progress.py @@ -24,5 +24,5 @@ def report_init(text): def report(): - p = int(1000 * _progress / _steps) - ui.display.loader(p, 18, ui.WHITE, ui.BG) + p = 1000 * _progress // _steps + ui.display.loader(p, False, 18, ui.WHITE, ui.BG) diff --git a/core/src/apps/common/coininfo.py b/core/src/apps/common/coininfo.py index 484a9e2e45..a695b1eddb 100644 --- a/core/src/apps/common/coininfo.py +++ b/core/src/apps/common/coininfo.py @@ -549,7 +549,7 @@ COINS = [ coin_shortcut="FJC", address_type=36, address_type_p2sh=16, - maxfee_kb=10000000, + maxfee_kb=1000000000, signed_message_header="FujiCoin Signed Message:\n", xpub_magic=0x0488b21e, xpub_magic_segwit_p2sh=0x049d7cb2, @@ -984,6 +984,26 @@ COINS = [ decred=False, curve_name='secp256k1', ), + CoinInfo( + coin_name="Regtest", + coin_shortcut="REGTEST", + address_type=111, + address_type_p2sh=196, + maxfee_kb=10000000, + signed_message_header="Bitcoin Signed Message:\n", + xpub_magic=0x043587cf, + xpub_magic_segwit_p2sh=0x044a5262, + xpub_magic_segwit_native=0x045f1cf6, + bech32_prefix="bcrt", + cashaddr_prefix=None, + slip44=1, + segwit=True, + fork_id=None, + force_bip143=False, + bip115=False, + decred=False, + curve_name='secp256k1', + ), CoinInfo( coin_name="SmartCash", coin_shortcut="SMART", diff --git a/core/src/apps/common/mnemonic.py b/core/src/apps/common/mnemonic.py index fc266a0c46..80bb8f599e 100644 --- a/core/src/apps/common/mnemonic.py +++ b/core/src/apps/common/mnemonic.py @@ -44,6 +44,6 @@ def _start_progress(): def _render_progress(progress: int, total: int): - p = int(1000 * progress / total) - ui.display.loader(p, 18, ui.WHITE, ui.BG) + p = 1000 * progress // total + ui.display.loader(p, False, 18, ui.WHITE, ui.BG) ui.display.refresh() diff --git a/core/src/apps/common/writers.py b/core/src/apps/common/writers.py index a4ea21a580..feeb32590e 100644 --- a/core/src/apps/common/writers.py +++ b/core/src/apps/common/writers.py @@ -24,6 +24,13 @@ def write_uint16_le(w: bytearray, n: int) -> int: return 2 +def write_uint16_be(w: bytearray, n: int): + ensure(0 <= n <= 0xFFFF) + w.append((n >> 8) & 0xFF) + w.append(n & 0xFF) + return 2 + + def write_uint32_le(w: bytearray, n: int) -> int: ensure(0 <= n <= 0xFFFFFFFF) w.append(n & 0xFF) diff --git a/core/src/apps/ethereum/tokens.py b/core/src/apps/ethereum/tokens.py index 96df77aa19..1d9ed2e86c 100644 --- a/core/src/apps/ethereum/tokens.py +++ b/core/src/apps/ethereum/tokens.py @@ -159,7 +159,6 @@ tokens = [ (1, b"\x80\x04\x63\x05\xaa\xab\x08\xf6\x03\x3b\x56\xa3\x60\xc1\x84\x39\x11\x65\xdc\x2d", "BRLN", 18), # eth / Berlin Coin (1, b"\xb2\x2c\x27\x86\xa5\x49\xb0\x08\x51\x7b\x67\x62\x5f\x52\x96\xe8\xfa\xf9\x58\x9e", "BRP", 18), # eth / Rental Processor Token (1, b"\xf2\x6e\xf5\xe0\x54\x53\x84\xb7\xdc\xc0\xf2\x97\xf2\x67\x41\x89\x58\x68\x30\xdf", "BSDC", 18), # eth / BSDC - (1, b"\x50\x9a\x38\xb7\xa1\xcc\x0d\xcd\x83\xaa\x9d\x06\x21\x46\x63\xd9\xec\x7c\x7f\x4a", "BST", 18), # eth / BlocksquareToken (1, b"\x02\x72\x58\x36\xeb\xf3\xec\xdb\x1c\xdf\x1c\x7b\x02\xfc\xbb\xfa\xa2\x73\x6a\xf8", "BTCA", 8), # eth / BitAir (1, b"\x08\x86\x94\x9c\x1b\x8c\x41\x28\x60\xc4\x26\x4c\xeb\x80\x83\xd1\x36\x5e\x86\xcf", "BTCE", 8), # eth / EthereumBitcoin (1, b"\x5a\xcd\x19\xb9\xc9\x1e\x59\x6b\x1f\x06\x2f\x18\xe3\xd0\x2d\xa7\xed\x8d\x1e\x50", "BTCL", 8), # eth / BTC Lite diff --git a/core/src/apps/monero/layout/confirms.py b/core/src/apps/monero/layout/confirms.py index b2abb37b9c..64bd7ccf48 100644 --- a/core/src/apps/monero/layout/confirms.py +++ b/core/src/apps/monero/layout/confirms.py @@ -42,7 +42,7 @@ async def require_confirm_tx_key(ctx, export_key=False): return await require_confirm(ctx, content, ButtonRequestType.SignTx) -async def require_confirm_transaction(ctx, tsx_data, network_type): +async def require_confirm_transaction(ctx, state, tsx_data, network_type): """ Ask for confirmation from user. """ @@ -70,10 +70,7 @@ async def require_confirm_transaction(ctx, tsx_data, network_type): await _require_confirm_payment_id(ctx, tsx_data.payment_id) await _require_confirm_fee(ctx, tsx_data.fee) - - text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE) - text.normal("Signing...") - text.render() + await transaction_step(state, 0) async def _require_confirm_output(ctx, dst, network_type, payment_id): @@ -119,46 +116,66 @@ async def _require_confirm_fee(ctx, fee): await require_hold_to_confirm(ctx, content, ButtonRequestType.ConfirmOutput) -@ui.layout -async def transaction_step(ctx, step, sub_step=None, sub_step_total=None): +@ui.layout_no_slide +async def transaction_step(state, step, sub_step=None): info = [] - if step == 100: - info = ["Processing inputs", "%d/%d" % (sub_step + 1, sub_step_total)] + if step == 0: + info = ["Signing..."] + elif step == 100: + info = ["Processing inputs", "%d/%d" % (sub_step + 1, state.input_count)] elif step == 200: - info = ["Sorting"] + info = ["Sorting..."] elif step == 300: - info = [ - "Processing inputs", - "phase 2", - "%d/%d" % (sub_step + 1, sub_step_total), - ] + info = ["Hashing inputs", "%d/%d" % (sub_step + 1, state.input_count)] + elif step == 350: + info = ["Processing..."] elif step == 400: - info = ["Processing outputs", "%d/%d" % (sub_step + 1, sub_step_total)] + info = ["Processing outputs", "%d/%d" % (sub_step + 1, state.output_count)] elif step == 500: info = ["Postprocessing..."] elif step == 600: - info = ["Signing inputs", "%d/%d" % (sub_step + 1, sub_step_total)] + info = ["Signing inputs", "%d/%d" % (sub_step + 1, state.input_count)] else: info = ["Processing..."] + state.progress_cur += 1 + + ui.display.clear() text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE) - text.normal(*info) text.render() + p = 1000 * state.progress_cur // state.progress_total + ui.display.loader(p, False, -4, ui.WHITE, ui.BG) + ui.display.text_center(ui.WIDTH // 2, 210, info[0], ui.NORMAL, ui.FG, ui.BG) + if len(info) > 1: + ui.display.text_center(ui.WIDTH // 2, 235, info[1], ui.NORMAL, ui.FG, ui.BG) + ui.display.refresh() -@ui.layout + +@ui.layout_no_slide async def keyimage_sync_step(ctx, current, total_num): if current is None: return + ui.display.clear() text = Text("Syncing", ui.ICON_SEND, icon_color=ui.BLUE) - text.normal("%d/%d" % (current + 1, total_num)) text.render() + p = (1000 * (current + 1) // total_num) if total_num > 0 else 0 + ui.display.loader(p, False, 18, ui.WHITE, ui.BG) + ui.display.refresh() -@ui.layout + +@ui.layout_no_slide async def live_refresh_step(ctx, current): if current is None: return + ui.display.clear() text = Text("Refreshing", ui.ICON_SEND, icon_color=ui.BLUE) - text.normal("%d" % current) text.render() + + step = 8 + p = (1000 * current // step) % 1000 + + ui.display.loader(p, True, 18, ui.WHITE, ui.BG) + ui.display.text_center(ui.WIDTH // 2, 145, "%d" % current, ui.NORMAL, ui.FG, ui.BG) + ui.display.refresh() diff --git a/core/src/apps/monero/live_refresh.py b/core/src/apps/monero/live_refresh.py index 8315be970a..adcbf29fd4 100644 --- a/core/src/apps/monero/live_refresh.py +++ b/core/src/apps/monero/live_refresh.py @@ -37,7 +37,7 @@ async def live_refresh(ctx, msg: MoneroLiveRefreshStartRequest, keychain): class LiveRefreshState: def __init__(self): - self.current_output = -1 + self.current_output = 0 self.creds = None diff --git a/core/src/apps/monero/signing/state.py b/core/src/apps/monero/signing/state.py index d608d226a6..22c240d871 100644 --- a/core/src/apps/monero/signing/state.py +++ b/core/src/apps/monero/signing/state.py @@ -68,6 +68,9 @@ class State: self.input_count = 0 self.output_count = 0 + self.progress_total = 0 + self.progress_cur = 0 + self.output_change = None self.fee = 0 diff --git a/core/src/apps/monero/signing/step_01_init_transaction.py b/core/src/apps/monero/signing/step_01_init_transaction.py index 9778e53c27..989d16f188 100644 --- a/core/src/apps/monero/signing/step_01_init_transaction.py +++ b/core/src/apps/monero/signing/step_01_init_transaction.py @@ -36,16 +36,19 @@ async def init_transaction( state.mem_trace(1) + state.input_count = tsx_data.num_inputs + state.output_count = len(tsx_data.outputs) + state.progress_total = 4 + 3 * state.input_count + state.output_count + state.progress_cur = 0 + # Ask for confirmation await confirms.require_confirm_transaction( - state.ctx, tsx_data, state.creds.network_type + state.ctx, state, tsx_data, state.creds.network_type ) gc.collect() state.mem_trace(3) # Basic transaction parameters - state.input_count = tsx_data.num_inputs - state.output_count = len(tsx_data.outputs) state.output_change = tsx_data.change_dts state.mixin = tsx_data.mixin state.fee = tsx_data.fee diff --git a/core/src/apps/monero/signing/step_02_set_input.py b/core/src/apps/monero/signing/step_02_set_input.py index af62823264..ce408e83c1 100644 --- a/core/src/apps/monero/signing/step_02_set_input.py +++ b/core/src/apps/monero/signing/step_02_set_input.py @@ -32,9 +32,7 @@ async def set_input(state: State, src_entr: MoneroTransactionSourceEntry): state.current_input_index += 1 - await confirms.transaction_step( - state.ctx, state.STEP_INP, state.current_input_index, state.input_count - ) + await confirms.transaction_step(state, state.STEP_INP, state.current_input_index) if state.current_input_index >= state.input_count: raise ValueError("Too many inputs") diff --git a/core/src/apps/monero/signing/step_03_inputs_permutation.py b/core/src/apps/monero/signing/step_03_inputs_permutation.py index cbd19c36b0..14f538fb7e 100644 --- a/core/src/apps/monero/signing/step_03_inputs_permutation.py +++ b/core/src/apps/monero/signing/step_03_inputs_permutation.py @@ -21,7 +21,7 @@ async def tsx_inputs_permutation(state: State, permutation: list): MoneroTransactionInputsPermutationAck, ) - await transaction_step(state.ctx, state.STEP_PERM) + await transaction_step(state, state.STEP_PERM) """ Set permutation on the inputs - sorted by key image on host. diff --git a/core/src/apps/monero/signing/step_04_input_vini.py b/core/src/apps/monero/signing/step_04_input_vini.py index 874bc09e73..2c88f7965c 100644 --- a/core/src/apps/monero/signing/step_04_input_vini.py +++ b/core/src/apps/monero/signing/step_04_input_vini.py @@ -33,7 +33,7 @@ async def input_vini( ) await confirms.transaction_step( - state.ctx, state.STEP_VINI, state.current_input_index + 1, state.input_count + state, state.STEP_VINI, state.current_input_index + 1 ) if state.current_input_index >= state.input_count: raise ValueError("Too many inputs") diff --git a/core/src/apps/monero/signing/step_05_all_inputs_set.py b/core/src/apps/monero/signing/step_05_all_inputs_set.py index 86b55271c0..dac0d28ad7 100644 --- a/core/src/apps/monero/signing/step_05_all_inputs_set.py +++ b/core/src/apps/monero/signing/step_05_all_inputs_set.py @@ -12,7 +12,7 @@ from apps.monero.xmr import crypto async def all_inputs_set(state: State): state.mem_trace(0) - await confirms.transaction_step(state.ctx, state.STEP_ALL_IN) + await confirms.transaction_step(state, state.STEP_ALL_IN) from trezor.messages.MoneroTransactionAllInputsSetAck import ( MoneroTransactionAllInputsSetAck, diff --git a/core/src/apps/monero/signing/step_06_set_output.py b/core/src/apps/monero/signing/step_06_set_output.py index d0ecc55cf1..6f0e30f167 100644 --- a/core/src/apps/monero/signing/step_06_set_output.py +++ b/core/src/apps/monero/signing/step_06_set_output.py @@ -23,10 +23,7 @@ async def set_output( # Progress update only for master message (skip for offloaded BP msg) if not is_offloaded_bp: await confirms.transaction_step( - state.ctx, - state.STEP_OUT, - state.current_output_index + 1, - state.output_count, + state, state.STEP_OUT, state.current_output_index + 1 ) state.mem_trace(1, True) diff --git a/core/src/apps/monero/signing/step_07_all_outputs_set.py b/core/src/apps/monero/signing/step_07_all_outputs_set.py index e5708a7d9c..e836aaae01 100644 --- a/core/src/apps/monero/signing/step_07_all_outputs_set.py +++ b/core/src/apps/monero/signing/step_07_all_outputs_set.py @@ -18,7 +18,7 @@ from apps.monero.xmr import crypto async def all_outputs_set(state: State): state.mem_trace(0) - await confirms.transaction_step(state.ctx, state.STEP_ALL_OUT) + await confirms.transaction_step(state, state.STEP_ALL_OUT) state.mem_trace(1) _validate(state) diff --git a/core/src/apps/monero/signing/step_09_sign_input.py b/core/src/apps/monero/signing/step_09_sign_input.py index 0a7639e4e0..8b71e146cc 100644 --- a/core/src/apps/monero/signing/step_09_sign_input.py +++ b/core/src/apps/monero/signing/step_09_sign_input.py @@ -48,7 +48,7 @@ async def sign_input( :return: Generated signature MGs[i] """ await confirms.transaction_step( - state.ctx, state.STEP_SIGN, state.current_input_index + 1, state.input_count + state, state.STEP_SIGN, state.current_input_index + 1 ) state.current_input_index += 1 diff --git a/core/src/apps/tezos/helpers.py b/core/src/apps/tezos/helpers.py index 4bba52008d..73bd02d1f8 100644 --- a/core/src/apps/tezos/helpers.py +++ b/core/src/apps/tezos/helpers.py @@ -3,6 +3,7 @@ from micropython import const from trezor.crypto import base58 from apps.common import HARDENED +from apps.common.writers import write_uint8 TEZOS_AMOUNT_DIVISIBILITY = const(6) TEZOS_ED25519_ADDRESS_PREFIX = "tz1" @@ -21,6 +22,8 @@ TEZOS_PREFIX_BYTES = { "edsig": [9, 245, 205, 134, 18], # operation hash "o": [5, 116], + # protocol hash + "P": [2, 170], } @@ -42,13 +45,29 @@ def validate_full_path(path: list) -> bool: """ Validates derivation path to equal 44'/1729'/a', where `a` is an account index from 0 to 1 000 000. + Additional component added to allow ledger migration + 44'/1729'/0'/b' where `b` is an account index from 0 to 1 000 000 """ - if len(path) != 3: + length = len(path) + if length < 3 or length > 4: return False if path[0] != 44 | HARDENED: return False if path[1] != 1729 | HARDENED: return False - if path[2] < HARDENED or path[2] > 1000000 | HARDENED: - return False + if length == 3: + if path[2] < HARDENED or path[2] > 1000000 | HARDENED: + return False + if length == 4: + if path[2] != 0 | HARDENED: + return False + if path[3] < HARDENED or path[3] > 1000000 | HARDENED: + return False return True + + +def write_bool(w: bytearray, boolean: bool): + if boolean: + write_uint8(w, 255) + else: + write_uint8(w, 0) diff --git a/core/src/apps/tezos/layout.py b/core/src/apps/tezos/layout.py index 4b1528efe2..325d73b26d 100644 --- a/core/src/apps/tezos/layout.py +++ b/core/src/apps/tezos/layout.py @@ -1,5 +1,10 @@ -from trezor import ui -from trezor.messages import ButtonRequestType +from micropython import const + +from trezor import ui, wire +from trezor.messages import ButtonRequestType, MessageType +from trezor.messages.ButtonRequest import ButtonRequest +from trezor.ui.confirm import CANCELLED, ConfirmDialog +from trezor.ui.scroll import Scrollpage, animate_swipe, paginate from trezor.ui.text import Text from trezor.utils import chunks, format_amount @@ -66,6 +71,45 @@ def split_address(address): return chunks(address, 18) +def split_proposal(proposal): + return chunks(proposal, 17) + + def format_tezos_amount(value): formatted_value = format_amount(value, TEZOS_AMOUNT_DIVISIBILITY) return formatted_value + " XTZ" + + +async def require_confirm_ballot(ctx, proposal, ballot): + text = Text("Submit ballot", ui.ICON_SEND, icon_color=ui.PURPLE) + text.bold("Ballot: {}".format(ballot)) + text.bold("Proposal:") + text.mono(*split_proposal(proposal)) + await require_confirm(ctx, text, ButtonRequestType.SignTx) + + +# use, when there are more then one proposals in one operation +async def require_confirm_proposals(ctx, proposals): + await ctx.call(ButtonRequest(code=ButtonRequestType.SignTx), MessageType.ButtonAck) + first_page = const(0) + pages = proposals + title = "Submit proposals" if len(proposals) > 1 else "Submit proposal" + + paginator = paginate(show_proposal_page, len(pages), first_page, pages, title) + return await ctx.wait(paginator) + + +@ui.layout +async def show_proposal_page(page: int, page_count: int, pages: list, title: str): + text = Text(title, ui.ICON_SEND, icon_color=ui.PURPLE) + text.bold("Proposal {}: ".format(page + 1)) + text.mono(*split_proposal(pages[page])) + content = Scrollpage(text, page, page_count) + + if page + 1 >= page_count: + confirm = await ConfirmDialog(content) + if confirm == CANCELLED: + raise wire.ActionCancelled("Cancelled") + else: + content.render() + await animate_swipe() diff --git a/core/src/apps/tezos/sign_tx.py b/core/src/apps/tezos/sign_tx.py index 10227e2bd2..a15f96853b 100644 --- a/core/src/apps/tezos/sign_tx.py +++ b/core/src/apps/tezos/sign_tx.py @@ -1,13 +1,17 @@ +from micropython import const + from trezor import wire from trezor.crypto import hashlib from trezor.crypto.curve import ed25519 -from trezor.messages import TezosContractType +from trezor.messages import TezosBallotType, TezosContractType from trezor.messages.TezosSignedTx import TezosSignedTx from apps.common import paths -from apps.common.writers import write_bytes, write_uint8 +from apps.common.writers import write_bytes, write_uint8, write_uint32_be from apps.tezos import CURVE, helpers, layout +PROPOSAL_LENGTH = const(32) + async def sign_tx(ctx, msg, keychain): await paths.validate_path( @@ -52,6 +56,15 @@ async def sign_tx(ctx, msg, keychain): ctx, source, msg.delegation.fee ) + elif msg.proposal is not None: + proposed_protocols = [_get_protocol_hash(p) for p in msg.proposal.proposals] + await layout.require_confirm_proposals(ctx, proposed_protocols) + + elif msg.ballot is not None: + proposed_protocol = _get_protocol_hash(msg.ballot.proposal) + submitted_ballot = _get_ballot(msg.ballot.ballot) + await layout.require_confirm_ballot(ctx, proposed_protocol, submitted_ballot) + else: raise wire.DataError("Invalid operation") @@ -101,11 +114,24 @@ def _get_address_from_contract(address): raise wire.DataError("Invalid contract type") +def _get_protocol_hash(proposal): + return helpers.base58_encode_check(proposal, prefix="P") + + +def _get_ballot(ballot): + if ballot == TezosBallotType.Yay: + return "yay" + elif ballot == TezosBallotType.Nay: + return "nay" + elif ballot == TezosBallotType.Pass: + return "pass" + + def _get_operation_bytes(w: bytearray, msg): write_bytes(w, msg.branch) # when the account sends first operation in lifetime, - # we need to reveal its publickey + # we need to reveal its public key if msg.reveal is not None: _encode_common(w, msg.reveal, "reveal") write_bytes(w, msg.reveal.public_key) @@ -121,14 +147,18 @@ def _get_operation_bytes(w: bytearray, msg): _encode_common(w, msg.origination, "origination") write_bytes(w, msg.origination.manager_pubkey) _encode_zarith(w, msg.origination.balance) - _encode_bool(w, msg.origination.spendable) - _encode_bool(w, msg.origination.delegatable) + helpers.write_bool(w, msg.origination.spendable) + helpers.write_bool(w, msg.origination.delegatable) _encode_data_with_bool_prefix(w, msg.origination.delegate) _encode_data_with_bool_prefix(w, msg.origination.script) # delegation operation elif msg.delegation is not None: _encode_common(w, msg.delegation, "delegation") _encode_data_with_bool_prefix(w, msg.delegation.delegate) + elif msg.proposal is not None: + _encode_proposal(w, msg.proposal) + elif msg.ballot is not None: + _encode_ballot(w, msg.ballot) def _encode_common(w: bytearray, operation, str_operation): @@ -146,19 +176,12 @@ def _encode_contract_id(w: bytearray, contract_id): write_bytes(w, contract_id.hash) -def _encode_bool(w: bytearray, boolean): - if boolean: - write_uint8(w, 255) - else: - write_uint8(w, 0) - - def _encode_data_with_bool_prefix(w: bytearray, data): if data: - _encode_bool(w, True) + helpers.write_bool(w, True) write_bytes(w, data) else: - _encode_bool(w, False) + helpers.write_bool(w, False) def _encode_zarith(w: bytearray, num): @@ -171,3 +194,24 @@ def _encode_zarith(w: bytearray, num): break write_uint8(w, 128 | byte) + + +def _encode_proposal(w: bytearray, proposal): + proposal_tag = 5 + + write_uint8(w, proposal_tag) + write_bytes(w, proposal.source) + write_uint32_be(w, proposal.period) + write_uint32_be(w, len(proposal.proposals) * PROPOSAL_LENGTH) + for proposal_hash in proposal.proposals: + write_bytes(w, proposal_hash) + + +def _encode_ballot(w: bytearray, ballot): + ballot_tag = 6 + + write_uint8(w, ballot_tag) + write_bytes(w, ballot.source) + write_uint32_be(w, ballot.period) + write_bytes(w, ballot.proposal) + write_uint8(w, ballot.ballot) diff --git a/core/src/apps/wallet/sign_tx/addresses.py b/core/src/apps/wallet/sign_tx/addresses.py index ff6e93c3ac..d9d44d3f01 100644 --- a/core/src/apps/wallet/sign_tx/addresses.py +++ b/core/src/apps/wallet/sign_tx/addresses.py @@ -204,7 +204,7 @@ def validate_full_path( See docs/coins for what paths are allowed. Please note that this is not a comprehensive check, some nuances are omitted for simplification. """ - if len(path) != 5: + if len(path) not in (4, 5, 6): return False if not validate_purpose(path[0], coin): @@ -214,21 +214,29 @@ def validate_full_path( ): return False - if path[1] != coin.slip44 | HARDENED: + if path[1] > 20 and path[1] != coin.slip44 | HARDENED: return False - if path[2] < HARDENED or path[2] > 20 | HARDENED: + if (path[2] > 20 and path[2] < HARDENED) or path[2] > 20 | HARDENED: return False - if path[3] not in [0, 1]: + if path[3] not in (0, 1, 0 | HARDENED, 1 | HARDENED, 2 | HARDENED): return False - if path[4] > 1000000: + if len(path) > 4 and path[4] > 1000000: + return False + if len(path) > 5 and path[5] > 1000000: return False return True def validate_purpose(purpose: int, coin: CoinInfo) -> bool: - if purpose not in (44 | HARDENED, 48 | HARDENED, 49 | HARDENED, 84 | HARDENED): + if purpose not in ( + 44 | HARDENED, + 45 | HARDENED, + 48 | HARDENED, + 49 | HARDENED, + 84 | HARDENED, + ): return False - if not coin.segwit and purpose not in (44 | HARDENED, 48 | HARDENED): + if not coin.segwit and purpose not in (44 | HARDENED, 45 | HARDENED, 48 | HARDENED): return False return True @@ -239,21 +247,23 @@ def validate_purpose_against_script_type( """ Validates purpose against provided input's script type: - 44 for spending address (script_type == SPENDADDRESS) - - 48 for multisig (script_type == SPENDMULTISIG) - - 49 for p2sh-segwit spend (script_type == SPENDP2SHWITNESS) - - 84 for native segwit spend (script_type == SPENDWITNESS) + - 45, 48 for multisig (script_type == SPENDMULTISIG) + - 49 for p2wsh-nested-in-p2sh spend (script_type == SPENDP2SHWITNESS) + - 84 for p2wsh native segwit spend (script_type == SPENDWITNESS) """ if purpose == 44 | HARDENED and script_type != InputScriptType.SPENDADDRESS: return False - if purpose == 48 | HARDENED and script_type != InputScriptType.SPENDMULTISIG: + if purpose == 45 | HARDENED and script_type != InputScriptType.SPENDMULTISIG: return False - if ( # p2wsh-nested-in-p2sh - purpose == 49 | HARDENED and script_type != InputScriptType.SPENDP2SHWITNESS + if purpose == 48 | HARDENED and script_type not in ( + InputScriptType.SPENDMULTISIG, + InputScriptType.SPENDP2SHWITNESS, + InputScriptType.SPENDWITNESS, ): return False - if ( # p2wsh - purpose == 84 | HARDENED and script_type != InputScriptType.SPENDWITNESS - ): + if purpose == 49 | HARDENED and script_type != InputScriptType.SPENDP2SHWITNESS: + return False + if purpose == 84 | HARDENED and script_type != InputScriptType.SPENDWITNESS: return False return True diff --git a/core/src/apps/wallet/sign_tx/progress.py b/core/src/apps/wallet/sign_tx/progress.py index 80918217fe..00c281660b 100644 --- a/core/src/apps/wallet/sign_tx/progress.py +++ b/core/src/apps/wallet/sign_tx/progress.py @@ -24,5 +24,5 @@ def report_init(): def report(): - p = int(1000 * _progress / _steps) - ui.display.loader(p, 18, ui.WHITE, ui.BG) + p = 1000 * _progress // _steps + ui.display.loader(p, False, 18, ui.WHITE, ui.BG) diff --git a/core/src/trezor/messages/MoneroTransactionData.py b/core/src/trezor/messages/MoneroTransactionData.py index a05cb49da7..a2b3918b6b 100644 --- a/core/src/trezor/messages/MoneroTransactionData.py +++ b/core/src/trezor/messages/MoneroTransactionData.py @@ -29,6 +29,8 @@ class MoneroTransactionData(p.MessageType): rsig_data: MoneroTransactionRsigData = None, integrated_indices: List[int] = None, client_version: int = None, + hard_fork: int = None, + monero_version: bytes = None, ) -> None: self.version = version self.payment_id = payment_id @@ -43,6 +45,8 @@ class MoneroTransactionData(p.MessageType): self.rsig_data = rsig_data self.integrated_indices = integrated_indices if integrated_indices is not None else [] self.client_version = client_version + self.hard_fork = hard_fork + self.monero_version = monero_version @classmethod def get_fields(cls): @@ -60,4 +64,6 @@ class MoneroTransactionData(p.MessageType): 11: ('rsig_data', MoneroTransactionRsigData, 0), 12: ('integrated_indices', p.UVarintType, p.FLAG_REPEATED), 13: ('client_version', p.UVarintType, 0), + 14: ('hard_fork', p.UVarintType, 0), + 15: ('monero_version', p.BytesType, 0), } diff --git a/core/src/trezor/messages/TezosBallotOp.py b/core/src/trezor/messages/TezosBallotOp.py new file mode 100644 index 0000000000..cf6b969cd9 --- /dev/null +++ b/core/src/trezor/messages/TezosBallotOp.py @@ -0,0 +1,27 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + + +class TezosBallotOp(p.MessageType): + + def __init__( + self, + source: bytes = None, + period: int = None, + proposal: bytes = None, + ballot: int = None, + ) -> None: + self.source = source + self.period = period + self.proposal = proposal + self.ballot = ballot + + @classmethod + def get_fields(cls): + return { + 1: ('source', p.BytesType, 0), + 2: ('period', p.UVarintType, 0), + 3: ('proposal', p.BytesType, 0), + 4: ('ballot', p.UVarintType, 0), + } diff --git a/core/src/trezor/messages/TezosBallotType.py b/core/src/trezor/messages/TezosBallotType.py new file mode 100644 index 0000000000..1d955862f5 --- /dev/null +++ b/core/src/trezor/messages/TezosBallotType.py @@ -0,0 +1,5 @@ +# Automatically generated by pb2py +# fmt: off +Yay = 0 +Nay = 1 +Pass = 2 diff --git a/core/src/trezor/messages/TezosProposalOp.py b/core/src/trezor/messages/TezosProposalOp.py new file mode 100644 index 0000000000..d533ce6c94 --- /dev/null +++ b/core/src/trezor/messages/TezosProposalOp.py @@ -0,0 +1,30 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import List + except ImportError: + List = None # type: ignore + + +class TezosProposalOp(p.MessageType): + + def __init__( + self, + source: bytes = None, + period: int = None, + proposals: List[bytes] = None, + ) -> None: + self.source = source + self.period = period + self.proposals = proposals if proposals is not None else [] + + @classmethod + def get_fields(cls): + return { + 1: ('source', p.BytesType, 0), + 2: ('period', p.UVarintType, 0), + 4: ('proposals', p.BytesType, p.FLAG_REPEATED), + } diff --git a/core/src/trezor/messages/TezosSignTx.py b/core/src/trezor/messages/TezosSignTx.py index 8127603780..cbba27e04f 100644 --- a/core/src/trezor/messages/TezosSignTx.py +++ b/core/src/trezor/messages/TezosSignTx.py @@ -2,8 +2,10 @@ # fmt: off import protobuf as p +from .TezosBallotOp import TezosBallotOp from .TezosDelegationOp import TezosDelegationOp from .TezosOriginationOp import TezosOriginationOp +from .TezosProposalOp import TezosProposalOp from .TezosRevealOp import TezosRevealOp from .TezosTransactionOp import TezosTransactionOp @@ -25,6 +27,8 @@ class TezosSignTx(p.MessageType): transaction: TezosTransactionOp = None, origination: TezosOriginationOp = None, delegation: TezosDelegationOp = None, + proposal: TezosProposalOp = None, + ballot: TezosBallotOp = None, ) -> None: self.address_n = address_n if address_n is not None else [] self.branch = branch @@ -32,6 +36,8 @@ class TezosSignTx(p.MessageType): self.transaction = transaction self.origination = origination self.delegation = delegation + self.proposal = proposal + self.ballot = ballot @classmethod def get_fields(cls): @@ -42,4 +48,6 @@ class TezosSignTx(p.MessageType): 4: ('transaction', TezosTransactionOp, 0), 5: ('origination', TezosOriginationOp, 0), 6: ('delegation', TezosDelegationOp, 0), + 7: ('proposal', TezosProposalOp, 0), + 8: ('ballot', TezosBallotOp, 0), } diff --git a/core/src/trezor/messages/__init__.py b/core/src/trezor/messages/__init__.py index 9e4ecb13ab..95c9b6fa6e 100644 --- a/core/src/trezor/messages/__init__.py +++ b/core/src/trezor/messages/__init__.py @@ -31,6 +31,6 @@ def get_type(wire_type): for msg_name in dir(MessageType): # Modules contain internal variables that may cause exception here. # No Message begins with underscore so it's safe to skip those. - if (msg_name[0] == '_'): + if msg_name[0] == "_": continue type_to_name[getattr(MessageType, msg_name)] = msg_name diff --git a/core/src/trezor/pin.py b/core/src/trezor/pin.py index e633605750..10d78cf650 100644 --- a/core/src/trezor/pin.py +++ b/core/src/trezor/pin.py @@ -18,7 +18,7 @@ def show_pin_timeout(seconds: int, progress: int, message: str) -> bool: ui.display.text_center( ui.WIDTH // 2, 37, message, ui.BOLD, ui.FG, ui.BG, ui.WIDTH ) - ui.display.loader(progress, 0, ui.FG, ui.BG) + ui.display.loader(progress, False, 0, ui.FG, ui.BG) if seconds == 0: ui.display.text_center( ui.WIDTH // 2, ui.HEIGHT - 22, "Done", ui.BOLD, ui.FG, ui.BG, ui.WIDTH diff --git a/core/src/trezor/ui/loader.py b/core/src/trezor/ui/loader.py index 6e19c8c999..9e81749fda 100644 --- a/core/src/trezor/ui/loader.py +++ b/core/src/trezor/ui/loader.py @@ -47,12 +47,15 @@ class Loader(ui.Widget): else: s = self.normal_style if s["icon"] is None: - ui.display.loader(r, -24, s["fg-color"], s["bg-color"]) + ui.display.loader(r, False, -24, s["fg-color"], s["bg-color"]) elif s["icon-fg-color"] is None: - ui.display.loader(r, -24, s["fg-color"], s["bg-color"], res.load(s["icon"])) + ui.display.loader( + r, False, -24, s["fg-color"], s["bg-color"], res.load(s["icon"]) + ) else: ui.display.loader( r, + False, -24, s["fg-color"], s["bg-color"], diff --git a/core/src/trezor/ui/scroll.py b/core/src/trezor/ui/scroll.py index 89d2fad39b..d144f76d7c 100644 --- a/core/src/trezor/ui/scroll.py +++ b/core/src/trezor/ui/scroll.py @@ -21,9 +21,9 @@ async def change_page(page, page_count): else: s = await swipe if s == SWIPE_UP: - return page + 1 # scroll down + return min(page + 1, page_count - 1) # scroll down elif s == SWIPE_DOWN: - return page - 1 # scroll up + return max(page - 1, 0) # scroll up async def paginate(render_page, page_count, page=0, *args): diff --git a/core/src/usb.py b/core/src/usb.py index a878b5fca9..06132785ea 100644 --- a/core/src/usb.py +++ b/core/src/usb.py @@ -62,7 +62,6 @@ bus = io.USB( product="TREZOR", interface="TREZOR Interface", serial_number=get_device_id(), - usb21_landing=False, ) bus.add(iface_wire) if __debug__: diff --git a/core/tests/test_apps.tezos.address.py b/core/tests/test_apps.tezos.address.py index 9b093f9e0b..c3870fd313 100644 --- a/core/tests/test_apps.tezos.address.py +++ b/core/tests/test_apps.tezos.address.py @@ -46,10 +46,11 @@ class TestTezosAddress(unittest.TestCase): [44 | HARDENED], [44 | HARDENED, 1729 | HARDENED], [44 | HARDENED, 1729 | HARDENED, 0], - [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 0 | HARDENED], + [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 0], [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0 | HARDENED], [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 1, 0], [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 0, 0], + [44 | HARDENED, 1729 | HARDENED, 1 | HARDENED, 1 | HARDENED], [44 | HARDENED, 1729 | HARDENED, 9999000 | HARDENED], [44 | HARDENED, 60 | HARDENED, 0 | HARDENED, 0, 0], [1 | HARDENED, 1 | HARDENED, 1 | HARDENED], @@ -58,6 +59,9 @@ class TestTezosAddress(unittest.TestCase): [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED], [44 | HARDENED, 1729 | HARDENED, 3 | HARDENED], [44 | HARDENED, 1729 | HARDENED, 9 | HARDENED], + [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 0 | HARDENED], + [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 3 | HARDENED], + [44 | HARDENED, 1729 | HARDENED, 0 | HARDENED, 9 | HARDENED], ] for path in incorrect_paths: diff --git a/core/tests/test_apps.tezos.encode.py b/core/tests/test_apps.tezos.encode.py index ee875d3757..16f1142f4d 100644 --- a/core/tests/test_apps.tezos.encode.py +++ b/core/tests/test_apps.tezos.encode.py @@ -4,9 +4,8 @@ from common import * from trezor.messages import TezosContractType from trezor.messages.TezosContractID import TezosContractID -from apps.tezos.helpers import base58_decode_check, base58_encode_check +from apps.tezos.helpers import base58_decode_check, base58_encode_check, write_bool from apps.tezos.sign_tx import ( - _encode_bool, _encode_contract_id, _encode_data_with_bool_prefix, _encode_zarith, @@ -35,11 +34,11 @@ class TestTezosEncoding(unittest.TestCase): def test_tezos_encode_bool(self): w = bytearray() - _encode_bool(w, True) + write_bool(w, True) self.assertEqual(bytes(w), bytes([255])) w = bytearray() - _encode_bool(w, False) + write_bool(w, False) self.assertEqual(bytes(w), bytes([0])) def test_tezos_encode_contract_id(self): diff --git a/core/tests/test_apps.wallet.address.py b/core/tests/test_apps.wallet.address.py index 2df72c70ea..11b5e83dcf 100644 --- a/core/tests/test_apps.wallet.address.py +++ b/core/tests/test_apps.wallet.address.py @@ -130,7 +130,7 @@ class TestAddress(unittest.TestCase): ([49 | HARDENED], InputScriptType.SPENDP2SHWITNESS), # invalid length ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0 | HARDENED], InputScriptType.SPENDP2SHWITNESS), # too many HARDENED ([49 | HARDENED, 0 | HARDENED], InputScriptType.SPENDP2SHWITNESS), # invalid length - ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0, 0], InputScriptType.SPENDP2SHWITNESS), # invalid length + ([49 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0, 0, 0, 0], InputScriptType.SPENDP2SHWITNESS), # invalid length ([49 | HARDENED, 123 | HARDENED, 0 | HARDENED, 0, 0, 0], InputScriptType.SPENDP2SHWITNESS), # invalid slip44 ([49 | HARDENED, 0 | HARDENED, 1000 | HARDENED, 0, 0], InputScriptType.SPENDP2SHWITNESS), # account too high ([49 | HARDENED, 0 | HARDENED, 1 | HARDENED, 2, 0], InputScriptType.SPENDP2SHWITNESS), # invalid y @@ -172,7 +172,7 @@ class TestAddress(unittest.TestCase): ([49 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0], InputScriptType.SPENDP2SHWITNESS), # bch is not segwit coin so 49' is not allowed ([84 | HARDENED, 145 | HARDENED, 1 | HARDENED, 0, 1], InputScriptType.SPENDWITNESS), # and neither is 84' ([44 | HARDENED, 145 | HARDENED], InputScriptType.SPENDADDRESS), # invalid length - ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0, 0], InputScriptType.SPENDADDRESS), # invalid length + ([44 | HARDENED, 145 | HARDENED, 0 | HARDENED, 0, 0, 0, 0], InputScriptType.SPENDADDRESS), # invalid length ([44 | HARDENED, 123 | HARDENED, 0 | HARDENED, 0, 0, 0], InputScriptType.SPENDADDRESS), # invalid slip44 ([44 | HARDENED, 145 | HARDENED, 1000 | HARDENED, 0, 0], InputScriptType.SPENDADDRESS), # account too high ([44 | HARDENED, 145 | HARDENED, 1 | HARDENED, 2, 0], InputScriptType.SPENDADDRESS), # invalid y diff --git a/core/tests/test_trezor.ui.display.py b/core/tests/test_trezor.ui.display.py index 87c1c27dac..d326d735fe 100644 --- a/core/tests/test_trezor.ui.display.py +++ b/core/tests/test_trezor.ui.display.py @@ -39,7 +39,7 @@ class TestDisplay(unittest.TestCase): display.qrcode(0, 0, 'Test', 4) def test_loader(self): - display.loader(333, 0, 0xFFFF, 0x0000) + display.loader(333, False, 0, 0xFFFF, 0x0000) def test_orientation(self): for o in [0, 90, 180, 270]: diff --git a/core/tools/build_protobuf b/core/tools/build_protobuf index 921fc95a1d..0df512e436 100755 --- a/core/tools/build_protobuf +++ b/core/tools/build_protobuf @@ -1,5 +1,6 @@ #!/bin/bash set -e +cd $(dirname $0) rm -f ../src/trezor/messages/[A-Z]*.py ../vendor/trezor-common/protob/pb2py \ diff --git a/core/travis-install-libsodium.sh b/core/travis-install-libsodium.sh deleted file mode 100755 index 3e73215fd3..0000000000 --- a/core/travis-install-libsodium.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# libsodium-dev replacement -# -# The purpose of this file is to install libsodium in -# the Travis CI environment. Outside this environment, -# you would probably not want to install it like this. - -set -e -export LIBSODIUM_VER="1.0.16" - -# check if libsodium is already installed -if [ ! -d "$HOME/libsodium/lib" ]; then - wget "https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VER}/libsodium-${LIBSODIUM_VER}.tar.gz" - tar xvfz "libsodium-${LIBSODIUM_VER}.tar.gz" - cd "libsodium-${LIBSODIUM_VER}" - ./configure --prefix=$HOME/libsodium - make - make install -else - echo 'Using cached directory.' -fi diff --git a/core/vendor/flamegraph/flamegraph.pl b/core/vendor/flamegraph/flamegraph.pl deleted file mode 100755 index 5286d9f859..0000000000 --- a/core/vendor/flamegraph/flamegraph.pl +++ /dev/null @@ -1,1077 +0,0 @@ -#!/usr/bin/perl -w -# -# flamegraph.pl flame stack grapher. -# -# This takes stack samples and renders a call graph, allowing hot functions -# and codepaths to be quickly identified. Stack samples can be generated using -# tools such as DTrace, perf, SystemTap, and Instruments. -# -# USAGE: ./flamegraph.pl [options] input.txt > graph.svg -# -# grep funcA input.txt | ./flamegraph.pl [options] > graph.svg -# -# Then open the resulting .svg in a web browser, for interactivity: mouse-over -# frames for info, click to zoom, and ctrl-F to search. -# -# Options are listed in the usage message (--help). -# -# The input is stack frames and sample counts formatted as single lines. Each -# frame in the stack is semicolon separated, with a space and count at the end -# of the line. These can be generated using DTrace with stackcollapse.pl, -# and other tools using the stackcollapse variants. -# -# An optional extra column of counts can be provided to generate a differential -# flame graph of the counts, colored red for more, and blue for less. This -# can be useful when using flame graphs for non-regression testing. -# See the header comment in the difffolded.pl program for instructions. -# -# The output graph shows relative presence of functions in stack samples. The -# ordering on the x-axis has no meaning; since the data is samples, time order -# of events is not known. The order used sorts function names alphabetically. -# -# While intended to process stack samples, this can also process stack traces. -# For example, tracing stacks for memory allocation, or resource usage. You -# can use --title to set the title to reflect the content, and --countname -# to change "samples" to "bytes" etc. -# -# There are a few different palettes, selectable using --color. By default, -# the colors are selected at random (except for differentials). Functions -# called "-" will be printed gray, which can be used for stack separators (eg, -# between user and kernel stacks). -# -# HISTORY -# -# This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb -# program, which visualized function entry and return trace events. As Neel -# wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which -# was in turn inspired by the work on vftrace by Jan Boerhout". See: -# https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and -# -# Copyright 2016 Netflix, Inc. -# Copyright 2011 Joyent, Inc. All rights reserved. -# Copyright 2011 Brendan Gregg. All rights reserved. -# -# CDDL HEADER START -# -# The contents of this file are subject to the terms of the -# Common Development and Distribution License (the "License"). -# You may not use this file except in compliance with the License. -# -# You can obtain a copy of the license at docs/cddl1.txt or -# http://opensource.org/licenses/CDDL-1.0. -# See the License for the specific language governing permissions -# and limitations under the License. -# -# When distributing Covered Code, include this CDDL HEADER in each -# file and include the License file at docs/cddl1.txt. -# If applicable, add the following below this CDDL HEADER, with the -# fields enclosed by brackets "[]" replaced with your own identifying -# information: Portions Copyright [yyyy] [name of copyright owner] -# -# CDDL HEADER END -# -# 11-Oct-2014 Adrien Mahieux Added zoom. -# 21-Nov-2013 Shawn Sterling Added consistent palette file option -# 17-Mar-2013 Tim Bunce Added options and more tunables. -# 15-Dec-2011 Dave Pacheco Support for frames with whitespace. -# 10-Sep-2011 Brendan Gregg Created this. - -use strict; - -use Getopt::Long; - -# tunables -my $encoding; -my $fonttype = "Verdana"; -my $imagewidth = 1200; # max width, pixels -my $frameheight = 16; # max height is dynamic -my $fontsize = 12; # base text size -my $fontwidth = 0.59; # avg width relative to fontsize -my $minwidth = 0.1; # min function width, pixels -my $nametype = "Function:"; # what are the names in the data? -my $countname = "samples"; # what are the counts in the data? -my $colors = "hot"; # color theme -my $bgcolor1 = "#eeeeee"; # background color gradient start -my $bgcolor2 = "#eeeeb0"; # background color gradient stop -my $nameattrfile; # file holding function attributes -my $timemax; # (override the) sum of the counts -my $factor = 1; # factor to scale counts by -my $hash = 0; # color by function name -my $palette = 0; # if we use consistent palettes (default off) -my %palette_map; # palette map hash -my $pal_file = "palette.map"; # palette map file name -my $stackreverse = 0; # reverse stack order, switching merge end -my $inverted = 0; # icicle graph -my $negate = 0; # switch differential hues -my $titletext = ""; # centered heading -my $titledefault = "Flame Graph"; # overwritten by --title -my $titleinverted = "Icicle Graph"; # " " -my $searchcolor = "rgb(230,0,230)"; # color for search highlighting -my $help = 0; - -sub usage { - die < outfile.svg\n - --title # change title text - --width # width of image (default 1200) - --height # height of each frame (default 16) - --minwidth # omit smaller functions (default 0.1 pixels) - --fonttype # font type (default "Verdana") - --fontsize # font size (default 12) - --countname # count type label (default "samples") - --nametype # name type label (default "Function:") - --colors # set color palette. choices are: hot (default), mem, io, - # wakeup, chain, java, js, perl, red, green, blue, aqua, - # yellow, purple, orange - --hash # colors are keyed by function name hash - --cp # use consistent palette (palette.map) - --reverse # generate stack-reversed flame graph - --inverted # icicle graph - --negate # switch differential hues (blue<->red) - --help # this message - - eg, - $0 --title="Flame Graph: malloc()" trace.txt > graph.svg -USAGE_END -} - -GetOptions( - 'fonttype=s' => \$fonttype, - 'width=i' => \$imagewidth, - 'height=i' => \$frameheight, - 'encoding=s' => \$encoding, - 'fontsize=f' => \$fontsize, - 'fontwidth=f' => \$fontwidth, - 'minwidth=f' => \$minwidth, - 'title=s' => \$titletext, - 'nametype=s' => \$nametype, - 'countname=s' => \$countname, - 'nameattr=s' => \$nameattrfile, - 'total=s' => \$timemax, - 'factor=f' => \$factor, - 'colors=s' => \$colors, - 'hash' => \$hash, - 'cp' => \$palette, - 'reverse' => \$stackreverse, - 'inverted' => \$inverted, - 'negate' => \$negate, - 'help' => \$help, -) or usage(); -$help && usage(); - -# internals -my $ypad1 = $fontsize * 4; # pad top, include title -my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels -my $xpad = 10; # pad lefm and right -my $framepad = 1; # vertical padding for frames -my $depthmax = 0; -my %Events; -my %nameattr; - -if ($titletext eq "") { - unless ($inverted) { - $titletext = $titledefault; - } else { - $titletext = $titleinverted; - } -} - -if ($nameattrfile) { - # The name-attribute file format is a function name followed by a tab then - # a sequence of tab separated name=value pairs. - open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n"; - while (<$attrfh>) { - chomp; - my ($funcname, $attrstr) = split /\t/, $_, 2; - die "Invalid format in $nameattrfile" unless defined $attrstr; - $nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr }; - } -} - -# background colors: -# - yellow gradient: default (hot, java, js, perl) -# - blue gradient: mem, chain -# - gray gradient: io, wakeup, flat colors (red, green, blue, ...) -if ($colors eq "mem" or $colors eq "chain") { - $bgcolor1 = "#eeeeee"; $bgcolor2 = "#e0e0ff"; -} -if ($colors =~ /^(io|wakeup|red|green|blue|aqua|yellow|purple|orange)$/) { - $bgcolor1 = "#f8f8f8"; $bgcolor2 = "#e8e8e8"; -} - -# SVG functions -{ package SVG; - sub new { - my $class = shift; - my $self = {}; - bless ($self, $class); - return $self; - } - - sub header { - my ($self, $w, $h) = @_; - my $enc_attr = ''; - if (defined $encoding) { - $enc_attr = qq{ encoding="$encoding"}; - } - $self->{svg} .= < - - - -SVG - } - - sub include { - my ($self, $content) = @_; - $self->{svg} .= $content; - } - - sub colorAllocate { - my ($self, $r, $g, $b) = @_; - return "rgb($r,$g,$b)"; - } - - sub group_start { - my ($self, $attr) = @_; - - my @g_attr = map { - exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : () - } qw(class style onmouseover onmouseout onclick); - push @g_attr, $attr->{g_extra} if $attr->{g_extra}; - $self->{svg} .= sprintf qq/\n/, join(' ', @g_attr); - - $self->{svg} .= sprintf qq/%s<\/title>/, $attr->{title} - if $attr->{title}; # should be first element within g container - - if ($attr->{href}) { - my @a_attr; - push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href}; - # default target=_top else links will open within SVG - push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top"; - push @a_attr, $attr->{a_extra} if $attr->{a_extra}; - $self->{svg} .= sprintf qq//, join(' ', @a_attr); - } - } - - sub group_end { - my ($self, $attr) = @_; - $self->{svg} .= qq/<\/a>\n/ if $attr->{href}; - $self->{svg} .= qq/<\/g>\n/; - } - - sub filledRectangle { - my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_; - $x1 = sprintf "%0.1f", $x1; - $x2 = sprintf "%0.1f", $x2; - my $w = sprintf "%0.1f", $x2 - $x1; - my $h = sprintf "%0.1f", $y2 - $y1; - $extra = defined $extra ? $extra : ""; - $self->{svg} .= qq/\n/; - } - - sub stringTTF { - my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_; - $x = sprintf "%0.2f", $x; - $loc = defined $loc ? $loc : "left"; - $extra = defined $extra ? $extra : ""; - $self->{svg} .= qq/$str<\/text>\n/; - } - - sub svg { - my $self = shift; - return "$self->{svg}\n"; - } - 1; -} - -sub namehash { - # Generate a vector hash for the name string, weighting early over - # later characters. We want to pick the same colors for function - # names across different flame graphs. - my $name = shift; - my $vector = 0; - my $weight = 1; - my $max = 1; - my $mod = 10; - # if module name present, trunc to 1st char - $name =~ s/.(.*?)`//; - foreach my $c (split //, $name) { - my $i = (ord $c) % $mod; - $vector += ($i / ($mod++ - 1)) * $weight; - $max += 1 * $weight; - $weight *= 0.70; - last if $mod > 12; - } - return (1 - $vector / $max) -} - -sub color { - my ($type, $hash, $name) = @_; - my ($v1, $v2, $v3); - - if ($hash) { - $v1 = namehash($name); - $v2 = $v3 = namehash(scalar reverse $name); - } else { - $v1 = rand(1); - $v2 = rand(1); - $v3 = rand(1); - } - - # theme palettes - if (defined $type and $type eq "hot") { - my $r = 205 + int(50 * $v3); - my $g = 0 + int(230 * $v1); - my $b = 0 + int(55 * $v2); - return "rgb($r,$g,$b)"; - } - if (defined $type and $type eq "mem") { - my $r = 0; - my $g = 190 + int(50 * $v2); - my $b = 0 + int(210 * $v1); - return "rgb($r,$g,$b)"; - } - if (defined $type and $type eq "io") { - my $r = 80 + int(60 * $v1); - my $g = $r; - my $b = 190 + int(55 * $v2); - return "rgb($r,$g,$b)"; - } - - # multi palettes - if (defined $type and $type eq "java") { - if ($name =~ m:(/|\.):) { # Java (match "/" in path) - $type = "green"; - $type = "aqua" if $name =~ m/_\[i\]/; #inline - } elsif ($name =~ /::/) { # C++ - $type = "yellow"; - } elsif ($name =~ m:_\[k\]:) { # kernel - $type = "orange" - } else { # system - $type = "red"; - } - # fall-through to color palettes - } - if (defined $type and $type eq "perl") { - if ($name =~ /::/) { # C++ - $type = "yellow"; - } elsif ($name =~ m:Perl: or $name =~ m:\.pl:) { # Perl - $type = "green"; - } elsif ($name =~ m:_\[k\]:) { # kernel - $type = "orange" - } else { # system - $type = "red"; - } - # fall-through to color palettes - } - if (defined $type and $type eq "js") { - if ($name =~ /::/) { # C++ - $type = "yellow"; - } elsif ($name =~ m:/:) { # JavaScript (match "/" in path) - $type = "green" - } elsif ($name =~ m/:/) { # JavaScript (match ":" in builtin) - $type = "aqua" - } elsif ($name =~ m/^ $/) { # Missing symbol - $type = "green" - } elsif ($name =~ m:_\[k\]:) { # kernel - $type = "orange" - } else { # system - $type = "red"; - } - # fall-through to color palettes - } - if (defined $type and $type eq "wakeup") { - $type = "aqua"; - # fall-through to color palettes - } - if (defined $type and $type eq "chain") { - if ($name =~ m:_\[w\]:) { # waker - $type = "aqua" - } else { # off-CPU - $type = "blue"; - } - # fall-through to color palettes - } - - # color palettes - if (defined $type and $type eq "red") { - my $r = 200 + int(55 * $v1); - my $x = 50 + int(80 * $v1); - return "rgb($r,$x,$x)"; - } - if (defined $type and $type eq "green") { - my $g = 200 + int(55 * $v1); - my $x = 50 + int(60 * $v1); - return "rgb($x,$g,$x)"; - } - if (defined $type and $type eq "blue") { - my $b = 205 + int(50 * $v1); - my $x = 80 + int(60 * $v1); - return "rgb($x,$x,$b)"; - } - if (defined $type and $type eq "yellow") { - my $x = 175 + int(55 * $v1); - my $b = 50 + int(20 * $v1); - return "rgb($x,$x,$b)"; - } - if (defined $type and $type eq "purple") { - my $x = 190 + int(65 * $v1); - my $g = 80 + int(60 * $v1); - return "rgb($x,$g,$x)"; - } - if (defined $type and $type eq "aqua") { - my $r = 50 + int(60 * $v1); - my $g = 165 + int(55 * $v1); - my $b = 165 + int(55 * $v1); - return "rgb($r,$g,$b)"; - } - if (defined $type and $type eq "orange") { - my $r = 190 + int(65 * $v1); - my $g = 90 + int(65 * $v1); - return "rgb($r,$g,0)"; - } - - return "rgb(0,0,0)"; -} - -sub color_scale { - my ($value, $max) = @_; - my ($r, $g, $b) = (255, 255, 255); - $value = -$value if $negate; - if ($value > 0) { - $g = $b = int(210 * ($max - $value) / $max); - } elsif ($value < 0) { - $r = $g = int(210 * ($max + $value) / $max); - } - return "rgb($r,$g,$b)"; -} - -sub color_map { - my ($colors, $func) = @_; - if (exists $palette_map{$func}) { - return $palette_map{$func}; - } else { - $palette_map{$func} = color($colors, $hash, $func); - return $palette_map{$func}; - } -} - -sub write_palette { - open(FILE, ">$pal_file"); - foreach my $key (sort keys %palette_map) { - print FILE $key."->".$palette_map{$key}."\n"; - } - close(FILE); -} - -sub read_palette { - if (-e $pal_file) { - open(FILE, $pal_file) or die "can't open file $pal_file: $!"; - while ( my $line = ) { - chomp($line); - (my $key, my $value) = split("->",$line); - $palette_map{$key}=$value; - } - close(FILE) - } -} - -my %Node; # Hash of merged frame data -my %Tmp; - -# flow() merges two stacks, storing the merged frames and value data in %Node. -sub flow { - my ($last, $this, $v, $d) = @_; - - my $len_a = @$last - 1; - my $len_b = @$this - 1; - - my $i = 0; - my $len_same; - for (; $i <= $len_a; $i++) { - last if $i > $len_b; - last if $last->[$i] ne $this->[$i]; - } - $len_same = $i; - - for ($i = $len_a; $i >= $len_same; $i--) { - my $k = "$last->[$i];$i"; - # a unique ID is constructed from "func;depth;etime"; - # func-depth isn't unique, it may be repeated later. - $Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime}; - if (defined $Tmp{$k}->{delta}) { - $Node{"$k;$v"}->{delta} = delete $Tmp{$k}->{delta}; - } - delete $Tmp{$k}; - } - - for ($i = $len_same; $i <= $len_b; $i++) { - my $k = "$this->[$i];$i"; - $Tmp{$k}->{stime} = $v; - if (defined $d) { - $Tmp{$k}->{delta} += $i == $len_b ? $d : 0; - } - } - - return $this; -} - -# parse input -my @Data; -my $last = []; -my $time = 0; -my $delta = undef; -my $ignored = 0; -my $line; -my $maxdelta = 1; - -# reverse if needed -foreach (<>) { - chomp; - $line = $_; - if ($stackreverse) { - # there may be an extra samples column for differentials - # XXX todo: redo these REs as one. It's repeated below. - my($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); - my $samples2 = undef; - if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { - $samples2 = $samples; - ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); - unshift @Data, join(";", reverse split(";", $stack)) . " $samples $samples2"; - } else { - unshift @Data, join(";", reverse split(";", $stack)) . " $samples"; - } - } else { - unshift @Data, $line; - } -} - -# process and merge frames -foreach (sort @Data) { - chomp; - # process: folded_stack count - # eg: func_a;func_b;func_c 31 - my ($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); - unless (defined $samples and defined $stack) { - ++$ignored; - next; - } - - # there may be an extra samples column for differentials: - my $samples2 = undef; - if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { - $samples2 = $samples; - ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); - } - $delta = undef; - if (defined $samples2) { - $delta = $samples2 - $samples; - $maxdelta = abs($delta) if abs($delta) > $maxdelta; - } - - # clean up SVG breaking characters: - $stack =~ tr/<>/()/; - - # for chain graphs, annotate waker frames with "_[w]", for later - # coloring. This is a hack, but has a precedent ("_[k]" from perf). - if ($colors eq "chain") { - my @parts = split ";-;", $stack; - my @newparts = (); - $stack = shift @parts; - $stack .= ";-;"; - foreach my $part (@parts) { - $part =~ s/;/_[w];/g; - $part .= "_[w]"; - push @newparts, $part; - } - $stack .= join ";-;", @parts; - } - - # merge frames and populate %Node: - $last = flow($last, [ '', split ";", $stack ], $time, $delta); - - if (defined $samples2) { - $time += $samples2; - } else { - $time += $samples; - } -} -flow($last, [], $time, $delta); - -warn "Ignored $ignored lines with invalid format\n" if $ignored; -unless ($time) { - warn "ERROR: No stack counts found\n"; - my $im = SVG->new(); - # emit an error message SVG, for tools automating flamegraph use - my $imageheight = $fontsize * 5; - $im->header($imagewidth, $imageheight); - $im->stringTTF($im->colorAllocate(0, 0, 0), $fonttype, $fontsize + 2, - 0.0, int($imagewidth / 2), $fontsize * 2, - "ERROR: No valid input provided to flamegraph.pl.", "middle"); - print $im->svg; - exit 2; -} -if ($timemax and $timemax < $time) { - warn "Specified --total $timemax is less than actual total $time, so ignored\n" - if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc) - undef $timemax; -} -$timemax ||= $time; - -my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax; -my $minwidth_time = $minwidth / $widthpertime; - -# prune blocks that are too narrow and determine max depth -while (my ($id, $node) = each %Node) { - my ($func, $depth, $etime) = split ";", $id; - my $stime = $node->{stime}; - die "missing start for $id" if not defined $stime; - - if (($etime-$stime) < $minwidth_time) { - delete $Node{$id}; - next; - } - $depthmax = $depth if $depth > $depthmax; -} - -# draw canvas, and embed interactive JavaScript program -my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2; -my $im = SVG->new(); -$im->header($imagewidth, $imageheight); -my $inc = < - - - - - - - -INC -$im->include($inc); -$im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)'); -my ($white, $black, $vvdgrey, $vdgrey) = ( - $im->colorAllocate(255, 255, 255), - $im->colorAllocate(0, 0, 0), - $im->colorAllocate(40, 40, 40), - $im->colorAllocate(160, 160, 160), - ); -$im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, $titletext, "middle"); -$im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), " ", "", 'id="details"'); -$im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $fontsize * 2, - "Reset Zoom", "", 'id="unzoom" onclick="unzoom()" style="opacity:0.0;cursor:pointer"'); -$im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100, - $fontsize * 2, "Search", "", 'id="search" onmouseover="searchover()" onmouseout="searchout()" onclick="search_prompt()" style="opacity:0.1;cursor:pointer"'); -$im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100, $imageheight - ($ypad2 / 2), " ", "", 'id="matched"'); - -if ($palette) { - read_palette(); -} - -# draw frames -while (my ($id, $node) = each %Node) { - my ($func, $depth, $etime) = split ";", $id; - my $stime = $node->{stime}; - my $delta = $node->{delta}; - - $etime = $timemax if $func eq "" and $depth == 0; - - my $x1 = $xpad + $stime * $widthpertime; - my $x2 = $xpad + $etime * $widthpertime; - my ($y1, $y2); - unless ($inverted) { - $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad; - $y2 = $imageheight - $ypad2 - $depth * $frameheight; - } else { - $y1 = $ypad1 + $depth * $frameheight; - $y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad; - } - - my $samples = sprintf "%.0f", ($etime - $stime) * $factor; - (my $samples_txt = $samples) # add commas per perlfaq5 - =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; - - my $info; - if ($func eq "" and $depth == 0) { - $info = "all ($samples_txt $countname, 100%)"; - } else { - my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor)); - my $escaped_func = $func; - $escaped_func =~ s/&/&/g; - $escaped_func =~ s//>/g; - $escaped_func =~ s/"/"/g; - $escaped_func =~ s/_\[[kwi]\]$//; # strip any annotation - unless (defined $delta) { - $info = "$escaped_func ($samples_txt $countname, $pct%)"; - } else { - my $d = $negate ? -$delta : $delta; - my $deltapct = sprintf "%.2f", ((100 * $d) / ($timemax * $factor)); - $deltapct = $d > 0 ? "+$deltapct" : $deltapct; - $info = "$escaped_func ($samples_txt $countname, $pct%; $deltapct%)"; - } - } - - my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone - $nameattr->{class} ||= "func_g"; - $nameattr->{onmouseover} ||= "s(this)"; - $nameattr->{onmouseout} ||= "c()"; - $nameattr->{onclick} ||= "zoom(this)"; - $nameattr->{title} ||= $info; - $im->group_start($nameattr); - - my $color; - if ($func eq "-") { - $color = $vdgrey; - } elsif (defined $delta) { - $color = color_scale($delta, $maxdelta); - } elsif ($palette) { - $color = color_map($colors, $func); - } else { - $color = color($colors, $hash, $func); - } - $im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"'); - - my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth)); - my $text = ""; - if ($chars >= 3) { # room for one char plus two dots - $func =~ s/_\[[kwi]\]$//; # strip any annotation - $text = substr $func, 0, $chars; - substr($text, -2, 2) = ".." if $chars < length $func; - $text =~ s/&/&/g; - $text =~ s//>/g; - } - $im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, ""); - - $im->group_end($nameattr); -} - -print $im->svg; - -if ($palette) { - write_palette(); -} - -# vim: ts=8 sts=8 sw=8 noexpandtab diff --git a/core/vendor/flamegraph/stackcollapse-perf.pl b/core/vendor/flamegraph/stackcollapse-perf.pl deleted file mode 100755 index dfd6da47bc..0000000000 --- a/core/vendor/flamegraph/stackcollapse-perf.pl +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/perl -w -# -# stackcolllapse-perf.pl collapse perf samples into single lines. -# -# Parses a list of multiline stacks generated by "perf script", and -# outputs a semicolon separated stack followed by a space and a count. -# If memory addresses (+0xd) are present, they are stripped, and resulting -# identical stacks are colased with their counts summed. -# -# USAGE: ./stackcollapse-perf.pl [options] infile > outfile -# -# Run "./stackcollapse-perf.pl -h" to list options. -# -# Example input: -# -# swapper 0 [000] 158665.570607: cpu-clock: -# ffffffff8103ce3b native_safe_halt ([kernel.kallsyms]) -# ffffffff8101c6a3 default_idle ([kernel.kallsyms]) -# ffffffff81013236 cpu_idle ([kernel.kallsyms]) -# ffffffff815bf03e rest_init ([kernel.kallsyms]) -# ffffffff81aebbfe start_kernel ([kernel.kallsyms].init.text) -# [...] -# -# Example output: -# -# swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1 -# -# Input may be created and processed using: -# -# perf record -a -g -F 997 sleep 60 -# perf script | ./stackcollapse-perf.pl > out.stacks-folded -# -# The output of "perf script" should include stack traces. If these are missing -# for you, try manually selecting the perf script output; eg: -# -# perf script -f comm,pid,tid,cpu,time,event,ip,sym,dso,trace | ... -# -# This is also required for the --pid or --tid options, so that the output has -# both the PID and TID. -# -# Copyright 2012 Joyent, Inc. All rights reserved. -# Copyright 2012 Brendan Gregg. All rights reserved. -# -# CDDL HEADER START -# -# The contents of this file are subject to the terms of the -# Common Development and Distribution License (the "License"). -# You may not use this file except in compliance with the License. -# -# You can obtain a copy of the license at docs/cddl1.txt or -# http://opensource.org/licenses/CDDL-1.0. -# See the License for the specific language governing permissions -# and limitations under the License. -# -# When distributing Covered Code, include this CDDL HEADER in each -# file and include the License file at docs/cddl1.txt. -# If applicable, add the following below this CDDL HEADER, with the -# fields enclosed by brackets "[]" replaced with your own identifying -# information: Portions Copyright [yyyy] [name of copyright owner] -# -# CDDL HEADER END -# -# 02-Mar-2012 Brendan Gregg Created this. -# 02-Jul-2014 " " Added process name to stacks. - -use strict; -use Getopt::Long; - -my %collapsed; - -sub remember_stack { - my ($stack, $count) = @_; - $collapsed{$stack} += $count; -} -my $annotate_kernel = 0; # put an annotation on kernel function -my $include_pname = 1; # include process names in stacks -my $include_pid = 0; # include process ID with process name -my $include_tid = 0; # include process & thread ID with process name -my $tidy_java = 1; # condense Java signatures -my $tidy_generic = 1; # clean up function names a little -my $target_pname; # target process name from perf invocation - -my $show_inline = 0; -my $show_context = 0; -GetOptions('inline' => \$show_inline, - 'context' => \$show_context, - 'pid' => \$include_pid, - 'kernel' => \$annotate_kernel, - 'tid' => \$include_tid) -or die < outfile\n - --pid # include PID with process names [1] - --tid # include TID and PID with process names [1] - --inline # un-inline using addr2line - --kernel # annotate kernel functions with a _[k] - --context # include source context from addr2line\n -[1] perf script must emit both PID and TIDs for these to work; eg: - perf script -f comm,pid,tid,cpu,time,event,ip,sym,dso,trace -USAGE_END - -# for the --inline option -sub inline { - my ($pc, $mod) = @_; - - # capture addr2line output - my $a2l_output = `addr2line -a $pc -e $mod -i -f -s -C`; - - # remove first line - $a2l_output =~ s/^(.*\n){1}//; - - my @fullfunc; - my $one_item = ""; - for (split /^/, $a2l_output) { - chomp $_; - - # remove discriminator info if exists - $_ =~ s/ \(discriminator \S+\)//; - - if ($one_item eq "") { - $one_item = $_; - } else { - if ($show_context == 1) { - unshift @fullfunc, $one_item . ":$_"; - } else { - unshift @fullfunc, $one_item; - } - $one_item = ""; - } - } - - return join(";", @fullfunc); -} - -my @stack; -my $pname; - -# -# Main loop -# -while (defined($_ = <>)) { - - # find the name of the process launched by perf, by stepping backwards - # over the args to find the first non-option (no dash): - if (/^# cmdline/) { - my @args = split ' ', $_; - foreach my $arg (reverse @args) { - if ($arg !~ /^-/) { - $target_pname = $arg; - $target_pname =~ s:.*/::; # strip pathname - last; - } - } - } - - # skip remaining comments - next if m/^#/; - chomp; - - # end of stack. save cached data. - if (m/^$/) { - if ($include_pname) { - if (defined $pname) { - unshift @stack, $pname; - } else { - unshift @stack, ""; - } - } - remember_stack(join(";", @stack), 1) if @stack; - undef @stack; - undef $pname; - next; - } - - # event record start - if (/^(\S+\s*?\S*?)\s+(\d+)\s/) { - # default "perf script" output has TID but not PID - # eg, "java 25607 4794564.109216: cycles:" - # eg, "java 12688 [002] 6544038.708352: cpu-clock:" - # eg, "V8 WorkerThread 25607 4794564.109216: cycles:" - # other combinations possible - if ($include_tid) { - $pname = "$1-?/$2"; - } elsif ($include_pid) { - $pname = "$1-?"; - } else { - $pname = $1; - } - $pname =~ tr/ /_/; - } elsif (/^(\S+\s*?\S*?)\s+(\d+)\/(\d+)/) { - # eg, "java 24636/25607 [000] 4794564.109216: cycles:" - # eg, "java 12688/12764 6544038.708352: cpu-clock:" - # eg, "V8 WorkerThread 24636/25607 [000] 94564.109216: cycles:" - # other combinations possible - if ($include_tid) { - $pname = "$1-$2/$3"; - } elsif ($include_pid) { - $pname = "$1-$2"; - } else { - $pname = $1; - } - $pname =~ tr/ /_/; - - # stack line - } elsif (/^\s*(\w+)\s*(.+) \((\S*)\)/) { - my ($pc, $rawfunc, $mod) = ($1, $2, $3); - $rawfunc.="_[k]" if ($annotate_kernel == 1 && $mod =~ m/kernel\./); - if ($show_inline == 1 && $mod !~ m/(perf-\d+.map|kernel\.|\[[^\]]+\])/) { - unshift @stack, inline($pc, $mod); - next; - } - - next if $rawfunc =~ /^\(/; # skip process names - - my @inline; - for (split /\->/, $rawfunc) { - my $func = $_; - - if ($tidy_generic) { - $func =~ s/;/:/g; - $func =~ tr/<>//d; - if ($func !~ m/\.\(.*\)\./) { - # This doesn't look like a Go method name (such as - # "net/http.(*Client).Do"), so everything after the first open - # paren is just noise. - $func =~ s/\(.*//; - } - # now tidy this horrible thing: - # 13a80b608e0a RegExp:[&<>\"\'] (/tmp/perf-7539.map) - $func =~ tr/"\'//d; - # fall through to $tidy_java - } - - if ($tidy_java and $pname eq "java") { - # along with $tidy_generic, converts the following: - # Lorg/mozilla/javascript/ContextFactory;.call(Lorg/mozilla/javascript/ContextAction;)Ljava/lang/Object; - # Lorg/mozilla/javascript/ContextFactory;.call(Lorg/mozilla/javascript/C - # Lorg/mozilla/javascript/MemberBox;.(Ljava/lang/reflect/Method;)V - # into: - # org/mozilla/javascript/ContextFactory:.call - # org/mozilla/javascript/ContextFactory:.call - # org/mozilla/javascript/MemberBox:.init - $func =~ s/^L// if $func =~ m:/:; - } - - $func .= "_[i]" if scalar(@inline) > 0; #inlined - push @inline, $func; - } - - unshift @stack, @inline; - } else { - warn "Unrecognized line: $_"; - } -} - -foreach my $k (sort { $a cmp $b } keys %collapsed) { - print "$k $collapsed{$k}\n"; -} diff --git a/crypto/.clang-format b/crypto/.clang-format deleted file mode 100644 index 58d4b3b682..0000000000 --- a/crypto/.clang-format +++ /dev/null @@ -1,2 +0,0 @@ ---- -BasedOnStyle: Google diff --git a/crypto/.travis.yml b/crypto/.travis.yml deleted file mode 100644 index 090e8af7e2..0000000000 --- a/crypto/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -sudo: false -dist: trusty -language: c - -compiler: - - clang - - gcc - -addons: - apt: - packages: - - check - - libssl-dev - - python3-pip - - valgrind - -env: - global: - - PYTHON=python3 - -install: - - $PYTHON -m pip install --user pytest ecdsa curve25519-donna pyasn1 - -script: - - make - - ./tests/aestst - - ./tests/test_check - - CK_TIMEOUT_MULTIPLIER=20 valgrind -q --error-exitcode=1 ./tests/test_check - - ./tests/test_openssl 1000 - - ITERS=10 $PYTHON -m pytest tests/ - -notifications: - webhooks: - urls: - - http://ci-bot.satoshilabs.com:5000/travis - on_success: always - on_failure: always - on_start: always diff --git a/crypto/Makefile b/crypto/Makefile index cb3ebe80f3..5739d1737b 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -63,6 +63,7 @@ SRCS += rc4.c SRCS += nem.c SRCS += segwit_addr.c cash_addr.c SRCS += memzero.c +SRCS += schnorr.c SRCS += shamir.c OBJS = $(SRCS:.c=.o) diff --git a/crypto/schnorr.c b/crypto/schnorr.c new file mode 100644 index 0000000000..9be43eef2c --- /dev/null +++ b/crypto/schnorr.c @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2019 Anatolii Kurotych + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "schnorr.h" +#include "memzero.h" + +// r = H(Q, kpub, m) +static void calc_r(const curve_point *Q, const uint8_t pub_key[33], + const uint8_t *msg, const uint32_t msg_len, bignum256 *r) { + uint8_t Q_compress[33]; + compress_coords(Q, Q_compress); + + SHA256_CTX ctx; + uint8_t digest[SHA256_DIGEST_LENGTH]; + sha256_Init(&ctx); + sha256_Update(&ctx, Q_compress, 33); + sha256_Update(&ctx, pub_key, 33); + sha256_Update(&ctx, msg, msg_len); + sha256_Final(&ctx, digest); + bn_read_be(digest, r); +} + +// returns 0 if signing succeeded +int schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, + const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, + schnorr_sign_pair *result) { + bignum256 private_key_scalar; + bn_read_be(priv_key, &private_key_scalar); + uint8_t pub_key[33]; + ecdsa_get_public_key33(curve, priv_key, pub_key); + + /* Q = kG */ + curve_point Q; + scalar_multiply(curve, k, &Q); + + /* r = H(Q, kpub, m) */ + calc_r(&Q, pub_key, msg, msg_len, &result->r); + + /* s = k - r*kpriv mod(order) */ + bignum256 s_temp; + bn_copy(&result->r, &s_temp); + bn_multiply(&private_key_scalar, &s_temp, &curve->order); + bn_subtractmod(k, &s_temp, &result->s, &curve->order); + memzero(&private_key_scalar, sizeof(private_key_scalar)); + + while (bn_is_less(&curve->order, &result->s)) { + bn_mod(&result->s, &curve->order); + } + + if (bn_is_zero(&result->s) || bn_is_zero(&result->r)) { + return 1; + } + + return 0; +} + +// returns 0 if verification succeeded +int schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, + const uint8_t *msg, const uint32_t msg_len, + const schnorr_sign_pair *sign) { + if (msg_len == 0) return 1; + if (bn_is_zero(&sign->r)) return 2; + if (bn_is_zero(&sign->s)) return 3; + if (bn_is_less(&curve->order, &sign->r)) return 4; + if (bn_is_less(&curve->order, &sign->s)) return 5; + + curve_point pub_key_point; + if (!ecdsa_read_pubkey(curve, pub_key, &pub_key_point)) { + return 6; + } + + // Compute Q = sG + r*kpub + curve_point sG, Q; + scalar_multiply(curve, &sign->s, &sG); + point_multiply(curve, &sign->r, &pub_key_point, &Q); + point_add(curve, &sG, &Q); + + /* r = H(Q, kpub, m) */ + bignum256 r; + calc_r(&Q, pub_key, msg, msg_len, &r); + + if (bn_is_equal(&r, &sign->r)) return 0; // success + + return 10; +} diff --git a/crypto/schnorr.h b/crypto/schnorr.h new file mode 100644 index 0000000000..57bfc46d76 --- /dev/null +++ b/crypto/schnorr.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2019 Anatolii Kurotych + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __SCHNORR_H__ +#define __SCHNORR_H__ + +#include "ecdsa.h" + +// result of sign operation +typedef struct { + bignum256 r, s; +} schnorr_sign_pair; + +// sign/verify returns 0 if operation succeeded + +// k is a random from [1, ..., order-1] +int schnorr_sign(const ecdsa_curve *curve, const uint8_t *priv_key, + const bignum256 *k, const uint8_t *msg, const uint32_t msg_len, + schnorr_sign_pair *result); +int schnorr_verify(const ecdsa_curve *curve, const uint8_t *pub_key, + const uint8_t *msg, const uint32_t msg_len, + const schnorr_sign_pair *sign); +#endif diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c index 3310cdc06d..a530be6618 100644 --- a/crypto/tests/test_check.c +++ b/crypto/tests/test_check.c @@ -61,6 +61,7 @@ #include "rand.h" #include "rc4.h" #include "rfc6979.h" +#include "schnorr.h" #include "script.h" #include "secp256k1.h" #include "sha2.h" @@ -8448,6 +8449,195 @@ START_TEST(test_compress_coords) { } END_TEST +// vectors generated using Schnorr::Sign function defined in +// https://github.com/Zilliqa/Zilliqa/blob/master/src/libCrypto/Schnorr.cpp#L88 +START_TEST(test_schnorr_sign_verify) { + static struct { + const char *message; + const char *priv_key; + const char *k_hex; + const char *s_hex; + const char *r_hex; + } test_cases[] = { + { + "123", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", + "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", + "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", + }, + { + "1234", + "51a2758eed776c40b367364909c8a9c98cc969104f69ff316f7a287495c37c9b", + "A0A1A9B3570AAE963535B8D4376C58A61646C18182C9FDDA5FB13703F88D4D1E", + "99A0CB942C81571B77C682F79CD3CB663CE9E1C55BB425BA24B9F11A0DE84FE2", + "C3C10363E38158BBA20556A36DE9358DFD81A31C180ABC9E7617C1CC1CAF03B3", + }, + { + "12345", + "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", + "38DE7B3315F201433D271E91FBE62966576CA05CBFEC1770B77D7EC9D6A01D6D", + "28982FA6C2B620CBC550F7EF9EAB605F409C584FBE5A765678877B79AB517086", + "9A0788E5B0947DEDEDE386DF57A006CF3FE43919A74D9CA630F8A1A9D97B4650", + }, + { + "fun", + "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", + "E005ABD242C7C602AB5EED080C5083C7C5F8DAEC6D046A54F384A8B8CDECF740", + "51070ABCA039DAC294F6BA3BFC8C36CFC66020EDF66D1ACF1A9B545B0BF09F52", + "330A924525EF722FA20E8E25CB6E8BD7DF4394886FA4414E4A0B6812AA25BBC0", + }, + { + "funny", + "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", + "0CF28B5C40A8830F3195BB99A9F0E2808F576105F41D16ABCF596AC5A8CFE88A", + "3D60FB4664C994AD956378B9402BC68F7B4799D74F4783A6199C0D74865EA2B6", + "5ED5EDEE0314DFFBEE39EE4E9C76DE8BC3EB8CB891AEC32B83957514284B205B", + }, + { + "What is great in man is that he is a bridge and not a goal", + "52c395a6d304de1a959e73e4604e32c5ad3f2bf01c8f730af426b38d7d5dd908", + "000000000000000000000000000000000000000000000000000000000000007B", + "546F70AA1FEE3718C95508240CDC073B9FEFED05959C5319DD8E2BF07A1DD028", + "B8667BE5E10B113608BFE5327C44E9F0462BE26F789177E10DCE53019AA33DAA", + }, + { + "123456789147258369qwertyuiopasdfghjklzxcvbnm,", + "2685adffdbb4b2c515054cffc25cfcbfe2e462df65bbe82fb50f71e1e68dd285", + "1D0CB70310C4D793A4561FE592B7C156771E3E26283B28AB588E968243B52DD0", + "54D7A435E5E3F2811AA542F8895C20CCB760F2713DBDDB7291DAB6DA4E4F927E", + "20A3BDABFFF2C1BF8E2AF709F6CDCAFE70DA9A1DBC22305B6332E36844092984", + }, + { + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "111111111111111111", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "A669F372B3C2EEA351210082CAEC3B96767A7B222D19FF2EE3D814860F0D703A", + "4890F9AC3A8D102EE3A2A473930C01CAD29DCE3860ACB7A5DADAEF16FE808991", + "979F088E58F1814D5E462CB9F935D2924ABD8D32211D8F02DD7E0991726DF573", + }, + { + "qwertyuiop[]asdfghjkl;'zxcvbnm,./1234567890-=", + "7457dc574d927e5dae84b05264a5b637b5a68e34a85b3965084ed6fed5b7f12d", + "000000000000000000000000000000000000000000000000000000000000007C", + "0AA595A649E517133D3448CA657424DD07BBED289030F0C0AA6738D26AB9A910", + "83812632F1443A70B198D112D075D886BE7BBC6EC6275AE52661E52B7358BB8B", + }, + }; + + const ecdsa_curve *curve = &secp256k1; + bignum256 k; + uint8_t priv_key[32], buf_raw[32]; + schnorr_sign_pair result; + for (size_t i = 0; i < sizeof(test_cases) / sizeof(*test_cases); i++) { + memcpy(priv_key, fromhex(test_cases[i].priv_key), 32); + memcpy(&buf_raw, fromhex(test_cases[i].k_hex), 32); + bn_read_be(buf_raw, &k); + schnorr_sign(curve, priv_key, &k, (const uint8_t *)test_cases[i].message, + strlen(test_cases[i].message), &result); + + schnorr_sign_pair expected; + + memcpy(&buf_raw, fromhex(test_cases[i].s_hex), 32); + bn_read_be(buf_raw, &expected.s); + memcpy(&buf_raw, fromhex(test_cases[i].r_hex), 32); + bn_read_be(buf_raw, &expected.r); + + ck_assert_mem_eq(&expected.r, &result.r, 32); + ck_assert_mem_eq(&expected.s, &result.s, 32); + + uint8_t pub_key[33]; + ecdsa_get_public_key33(curve, priv_key, pub_key); + int res = + schnorr_verify(curve, pub_key, (const uint8_t *)test_cases[i].message, + strlen(test_cases[i].message), &result); + ck_assert_int_eq(res, 0); + } +} +END_TEST + +// vectors generated using Schnorr::Sign function defined in +// https://github.com/Zilliqa/Zilliqa/blob/master/src/libCrypto/Schnorr.cpp#L88 +START_TEST(test_schnorr_fail_verify) { + static struct { + const char *message; + const char *priv_key; + const char *k_hex; + const char *s_hex; + const char *r_hex; + } test_case = { + "123", + "3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6", + "669301F724C555D7BB1185C04909E9CACA3EC7A292B3A1C92DDCCD5A5A7DDDD3", + "FFD72C290B98C93A4BCEDC0EDCDF040C35579BE962FE83E6821D4F3CB4B795D2", + "74AAE9C3E069E2806E1B0D890970BE387AEBED8040F37991AACAD70B27895E39", + }; + + const ecdsa_curve *curve = &secp256k1; + bignum256 k; + uint8_t priv_key[32], buf_raw[32]; + memcpy(priv_key, fromhex(test_case.priv_key), 32); + memcpy(&buf_raw, fromhex(test_case.k_hex), 32); + bn_read_be(buf_raw, &k); + + schnorr_sign_pair result; + schnorr_sign(curve, priv_key, &k, (const uint8_t *)test_case.message, + strlen(test_case.message), &result); + uint8_t pub_key[33]; + ecdsa_get_public_key33(curve, priv_key, pub_key); + + // OK + int res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &result); + ck_assert_int_eq(res, 0); + + res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, 0, + &result); + ck_assert_int_eq(res, 1); + + schnorr_sign_pair bad_result; + + bn_copy(&result.s, &bad_result.s); + bn_zero(&bad_result.r); + // r == 0 + res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 2); + + bn_copy(&result.r, &bad_result.r); + bn_zero(&bad_result.s); + // s == 0 + res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 3); + + bn_copy(&result.s, &bad_result.s); + bn_copy(&curve->order, &bad_result.r); + bn_addi(&bad_result.r, 1); + // r == curve->order + 1 + res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 4); + + bn_copy(&result.r, &bad_result.r); + bn_copy(&curve->order, &bad_result.s); + bn_addi(&bad_result.s, 1); + // s == curve->order + 1 + res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 5); + + bn_copy(&result.r, &bad_result.r); + bn_copy(&result.s, &bad_result.s); + // change message + test_case.message = "12"; + res = schnorr_verify(curve, pub_key, (const uint8_t *)test_case.message, + strlen(test_case.message), &bad_result); + ck_assert_int_eq(res, 10); +} +END_TEST + static int my_strncasecmp(const char *s1, const char *s2, size_t n) { size_t i = 0; while (i < n) { @@ -8729,6 +8919,11 @@ Suite *test_suite(void) { tcase_add_test(tc, test_compress_coords); suite_add_tcase(s, tc); + tc = tcase_create("schnorr"); + tcase_add_test(tc, test_schnorr_sign_verify); + tcase_add_test(tc, test_schnorr_fail_verify); + suite_add_tcase(s, tc); + #if USE_CARDANO tc = tcase_create("bip32-cardano"); diff --git a/crypto/tests/test_check_cardano.h b/crypto/tests/test_check_cardano.h index 434ea96b9a..5fd830f24a 100644 --- a/crypto/tests/test_check_cardano.h +++ b/crypto/tests/test_check_cardano.h @@ -5,82 +5,80 @@ START_TEST(test_ed25519_cardano_sign_vectors) { ed25519_secret_key secret_key_extension; ed25519_signature signature; - static const char - *vectors[] = - { - "6065a956b1b34145c4416fdc3ba3276801850e91a77a31a7be782463288aea5" - "3", // private key - "60ba6e25b1a02157fb69c5d1d7b96c4619736e545447069a6a6f0ba90844bc8" - "e", // private key extension - "64b20fa082b3143d6b5eed42c6ef63f99599d0888afe060620abc1b319935fe" - "1", // public key - "45b1a75fe3119e13c6f60ab9ba674b42f946fdc558e07c83dfa0751c2eba69c7" - "9331bd8a4a975662b23628a438a0eba76367e44c12ca91b39ec59063f860f10" - "d", // signature + static const char *vectors[] = { + "6065a956b1b34145c4416fdc3ba3276801850e91a77a31a7be782463288aea5" + "3", // private key + "60ba6e25b1a02157fb69c5d1d7b96c4619736e545447069a6a6f0ba90844bc8" + "e", // private key extension + "64b20fa082b3143d6b5eed42c6ef63f99599d0888afe060620abc1b319935fe" + "1", // public key + "45b1a75fe3119e13c6f60ab9ba674b42f946fdc558e07c83dfa0751c2eba69c7" + "9331bd8a4a975662b23628a438a0eba76367e44c12ca91b39ec59063f860f10" + "d", // signature - "e7d27516538403a53a8b041656a3f570909df641a0ab811fe7d87c9ba02a830" - "c", // private key - "794a2c54ad8b525b781773c87d38cbf4197636bc427a9d551368286fe4c294a" - "4", // private key extension - "95bb82ffd5707716bc65170ab4e8dafeed90fbe0ce9258713b7751e962d931d" - "f", // public key - "f2c9171782e7df7665126ac545ae53b05964b0160536efdb545e2460dbbec2b1" - "9ec6b338b8f1bf4dfee94360ed024b115e37b1d7e6f3f9ae4beb79539428560" - "f", // signature + "e7d27516538403a53a8b041656a3f570909df641a0ab811fe7d87c9ba02a830" + "c", // private key + "794a2c54ad8b525b781773c87d38cbf4197636bc427a9d551368286fe4c294a" + "4", // private key extension + "95bb82ffd5707716bc65170ab4e8dafeed90fbe0ce9258713b7751e962d931d" + "f", // public key + "f2c9171782e7df7665126ac545ae53b05964b0160536efdb545e2460dbbec2b1" + "9ec6b338b8f1bf4dfee94360ed024b115e37b1d7e6f3f9ae4beb79539428560" + "f", // signature - "9b5a3d9a4c60bcd49bb64b72c082b164314d0f61d842f2575fd1d4fb30a28a0" - "c", // private key - "b093e376f41eb7bf80abcd0073a52455d25b5d21815bc758e5f6f81536aedeb" - "b", // private key extension - "79fc8154554b97e4c56ef2f9dbb4c1421ff19509688931a1e964bda5dec0f19" - "f", // public key - "2ba1439ae648a7e8da7c9ab1ee6da94fd4ebe37abd0978306e8fba2afa8f111a" - "88a993dbf008bedae9167f4f68409e4c9ddaf02cba12418447b1848907ad800" - "f", // signature + "9b5a3d9a4c60bcd49bb64b72c082b164314d0f61d842f2575fd1d4fb30a28a0" + "c", // private key + "b093e376f41eb7bf80abcd0073a52455d25b5d21815bc758e5f6f81536aedeb" + "b", // private key extension + "79fc8154554b97e4c56ef2f9dbb4c1421ff19509688931a1e964bda5dec0f19" + "f", // public key + "2ba1439ae648a7e8da7c9ab1ee6da94fd4ebe37abd0978306e8fba2afa8f111a" + "88a993dbf008bedae9167f4f68409e4c9ddaf02cba12418447b1848907ad800" + "f", // signature - "52e0c98aa600cfdcd1ff28fcda5227ed87063f4a98547a78b771052cf102b40" - "c", // private key - "6c18d9f8075b1a6a1833540607479bd58b7beb8a83d2bb01ca7ae02452a2580" - "3", // private key extension - "dc907c7c06e6314eedd9e18c9f6c6f9cc4e205fb1c70da608234c319f1f7b0d" - "6", // public key - "0cd34f84e0d2fcb1800bdb0e869b9041349955ced66aedbe6bda187ebe8d36a6" - "2a05b39647e92fcc42aa7a7368174240afba08b8c81f981a22f942d6bd78160" - "2", // signature + "52e0c98aa600cfdcd1ff28fcda5227ed87063f4a98547a78b771052cf102b40" + "c", // private key + "6c18d9f8075b1a6a1833540607479bd58b7beb8a83d2bb01ca7ae02452a2580" + "3", // private key extension + "dc907c7c06e6314eedd9e18c9f6c6f9cc4e205fb1c70da608234c319f1f7b0d" + "6", // public key + "0cd34f84e0d2fcb1800bdb0e869b9041349955ced66aedbe6bda187ebe8d36a6" + "2a05b39647e92fcc42aa7a7368174240afba08b8c81f981a22f942d6bd78160" + "2", // signature - "11fd6462a3a92b35c22703f6f1c124ddcf36b7c2b09cc2784f320e1cfa12ec0" - "4", // private key - "c2785803c61c46aeca192a1bb1b7b20a8c4cc7fa01db57fc5d1d8a547340235" - "2", // private key extension - "839775a41876e328986aa26168958bba1176e67819b357eea84afceab8b1db7" - "8", // public key - "e41f73db2f8d2896a687802b2be76b7cabb73dfbb4891494883a0cbd9bbb9e5f" - "9d3e14d2d0b06c6674333508496db660936737c0efd9511514147dac79fa490" - "5", // signature + "11fd6462a3a92b35c22703f6f1c124ddcf36b7c2b09cc2784f320e1cfa12ec0" + "4", // private key + "c2785803c61c46aeca192a1bb1b7b20a8c4cc7fa01db57fc5d1d8a547340235" + "2", // private key extension + "839775a41876e328986aa26168958bba1176e67819b357eea84afceab8b1db7" + "8", // public key + "e41f73db2f8d2896a687802b2be76b7cabb73dfbb4891494883a0cbd9bbb9e5f" + "9d3e14d2d0b06c6674333508496db660936737c0efd9511514147dac79fa490" + "5", // signature - "5b1e5cad02274ba461f4708d8598d3497faf8fe3e894a379573aa6ac3a03e50" - "5", // private key - "ba179d2e3c67aabb486c48d16002b51ad32eab434c738a1550962313b07098c" - "d", // private key extension - "75eb8d197ec8627c85af88e66aa1e49065dd8ac98ed8991db52ece01635dfb7" - "6", // public key - "631015357cee3051116b4c2ff4d1c5beb13b6e5023635aa1eeb0563cadf0d4fb" - "c10bd5e31b4a4220c67875558c41b5cc0328104ae39cc7ff20ff0c2bda59890" - "6", // signature + "5b1e5cad02274ba461f4708d8598d3497faf8fe3e894a379573aa6ac3a03e50" + "5", // private key + "ba179d2e3c67aabb486c48d16002b51ad32eab434c738a1550962313b07098c" + "d", // private key extension + "75eb8d197ec8627c85af88e66aa1e49065dd8ac98ed8991db52ece01635dfb7" + "6", // public key + "631015357cee3051116b4c2ff4d1c5beb13b6e5023635aa1eeb0563cadf0d4fb" + "c10bd5e31b4a4220c67875558c41b5cc0328104ae39cc7ff20ff0c2bda59890" + "6", // signature - "624b47150f58dfa44284fbc63c9f99b9b79f808c4955a461f0e2be44eb0be50" - "d", // private key - "097aa006d694b165ef37cf23562e5967c96e49255d2f20faae478dee83aa5b0" - "2", // private key extension - "0588589cd9b51dfc028cf225674069cbe52e0e70deb02dc45b79b26ee3548b0" - "0", // public key - "1de1d275428ba9491a433cd473cd076c027f61e7a8b5391df9dea5cb4bc88d8a" - "57b095906a30b13e68259851a8dd3f57b6f0ffa37a5d3ffc171240f2d404f90" - "1", // signature + "624b47150f58dfa44284fbc63c9f99b9b79f808c4955a461f0e2be44eb0be50" + "d", // private key + "097aa006d694b165ef37cf23562e5967c96e49255d2f20faae478dee83aa5b0" + "2", // private key extension + "0588589cd9b51dfc028cf225674069cbe52e0e70deb02dc45b79b26ee3548b0" + "0", // public key + "1de1d275428ba9491a433cd473cd076c027f61e7a8b5391df9dea5cb4bc88d8a" + "57b095906a30b13e68259851a8dd3f57b6f0ffa37a5d3ffc171240f2d404f90" + "1", // signature - 0, - 0, - }; + 0, + 0, + }; const char **test_data; test_data = vectors; diff --git a/crypto/tests/test_curves.py b/crypto/tests/test_curves.py index 7056cb6363..2a15bf7790 100755 --- a/crypto/tests/test_curves.py +++ b/crypto/tests/test_curves.py @@ -30,48 +30,48 @@ class Point: points = [ Point( "secp256k1", - 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, - 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, + 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, + 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, ), Point( "secp256k1", 0x1, - 0x4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee, + 0x4218F20AE6C646B363DB68605822FB14264CA8D2587FDD6FBC750D587E76A7EE, ), Point( "secp256k1", 0x2, - 0x66fbe727b2ba09e09f5a98d70a5efce8424c5fa425bbda1c511f860657b8535e, + 0x66FBE727B2BA09E09F5A98D70A5EFCE8424C5FA425BBDA1C511F860657B8535E, ), Point( "secp256k1", - 0x1b, - 0x1adcea1cf831b0ad1653e769d1a229091d0cc68d4b0328691b9caacc76e37c90, + 0x1B, + 0x1ADCEA1CF831B0AD1653E769D1A229091D0CC68D4B0328691B9CAACC76E37C90, ), Point( "nist256p1", - 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, - 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5, + 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296, + 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5, ), Point( "nist256p1", 0x0, - 0x66485c780e2f83d72433bd5d84a06bb6541c2af31dae871728bf856a174f93f4, + 0x66485C780E2F83D72433BD5D84A06BB6541C2AF31DAE871728BF856A174F93F4, ), Point( "nist256p1", 0x0, - 0x99b7a386f1d07c29dbcc42a27b5f9449abe3d50de25178e8d7407a95e8b06c0b, + 0x99B7A386F1D07C29DBCC42A27B5F9449ABE3D50DE25178E8D7407A95E8B06C0B, ), Point( "nist256p1", - 0xaf8bbdfe8cdd5577acbf345b543d28cf402f4e94d3865b97ea0787f2d3aa5d22, - 0x35802b8b376b995265918b078bc109c21a535176585c40f519aca52d6afc147c, + 0xAF8BBDFE8CDD5577ACBF345B543D28CF402F4E94D3865B97EA0787F2D3AA5D22, + 0x35802B8B376B995265918B078BC109C21A535176585C40F519ACA52D6AFC147C, ), Point( "nist256p1", 0x80000, - 0x580610071f440f0dcc14a22e2d5d5afc1224c0cd11a3b4b51b8ecd2224ee1ce2, + 0x580610071F440F0DCC14A22E2D5D5AFC1224C0CD11A3B4B51B8ECD2224EE1CE2, ), ] diff --git a/crypto/tests/test_wycheproof.py b/crypto/tests/test_wycheproof.py index 3bdedbfac4..c449b29c8a 100755 --- a/crypto/tests/test_wycheproof.py +++ b/crypto/tests/test_wycheproof.py @@ -102,7 +102,7 @@ def is_valid_der(data): try: structure, _ = der_decode(data) return data == der_encode(structure) - except: + except Exception: return False @@ -140,7 +140,7 @@ def parse_ec_pubkey(public_key): try: public_key = bytes(public_key["public_key"].asOctets()) - except: + except Exception: raise ParseError("Not a BER encoded named elliptic curve public key") return curve_name, public_key @@ -152,13 +152,13 @@ def parse_ecdsa256_signature(signature): raise ParseError("Not a valid DER") try: signature, _ = der_decode(signature, asn1Spec=EcSignature()) - except: + except Exception: raise ParseError("Not a valid DER encoded ECDSA signature") try: r = int(signature["r"]).to_bytes(32, byteorder="big") s = int(signature["s"]).to_bytes(32, byteorder="big") signature = r + s - except: + except Exception: raise ParseError("Not a valid DER encoded 256 bit ECDSA signature") return signature @@ -281,7 +281,7 @@ def aes_cbc_decrypt(key, iv, ciphertext): def load_json_testvectors(filename): try: result = json.loads(open(os.path.join(testvectors_directory, filename)).read()) - except: + except Exception: raise DataError() return result @@ -310,7 +310,7 @@ def generate_aes(filename): plaintext = unhexlify(test["msg"]) ciphertext = unhexlify(test["ct"]) result = parse_result(test["result"]) - except: + except Exception: raise DataError() if len(key) not in [128 / 8, 192 / 8, 256 / 8]: @@ -359,7 +359,7 @@ def generate_chacha_poly(filename): ciphertext = unhexlify(test["ct"]) tag = unhexlify(test["tag"]) result = parse_result(test["result"]) - except: + except Exception: raise DataError() if result is None: @@ -406,7 +406,7 @@ def generate_curve25519_dh(filename): private_key = unhexlify(test["private"]) shared = unhexlify(test["shared"]) result = parse_result(test["result"]) - except: + except Exception: raise DataError() if curve_name != "curve25519": @@ -448,7 +448,7 @@ def generate_ecdh(filename): private_key = parse_signed_hex(test["private"]) shared = unhexlify(test["shared"]) result = parse_result(test["result"]) - except: + except Exception: raise DataError() try: @@ -498,7 +498,7 @@ def generate_ecdsa(filename): try: public_key = unhexlify(test_group["keyDer"]) - except: + except Exception: raise DataError() try: @@ -521,7 +521,7 @@ def generate_ecdsa(filename): signature = unhexlify(test["sig"]) message = unhexlify(test["msg"]) result = parse_result(test["result"]) - except: + except Exception: raise DataError() if result is None: @@ -563,7 +563,7 @@ def generate_eddsa(filename): try: public_key = unhexlify(test_group["keyDer"]) - except: + except Exception: raise DataError() try: @@ -579,7 +579,7 @@ def generate_eddsa(filename): signature = unhexlify(test["sig"]) message = unhexlify(test["msg"]) result = parse_result(test["result"]) - except: + except Exception: raise DataError() if result is None: diff --git a/legacy/.clang-format b/legacy/.clang-format deleted file mode 100644 index 58d4b3b682..0000000000 --- a/legacy/.clang-format +++ /dev/null @@ -1,2 +0,0 @@ ---- -BasedOnStyle: Google diff --git a/legacy/.travis.yml b/legacy/.travis.yml deleted file mode 100644 index 39adb5429c..0000000000 --- a/legacy/.travis.yml +++ /dev/null @@ -1,63 +0,0 @@ -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/Makefile.include b/legacy/Makefile.include index 029dddae7d..c2a96e254a 100644 --- a/legacy/Makefile.include +++ b/legacy/Makefile.include @@ -132,6 +132,14 @@ LDLIBS += -lopencm3_stm32f2 LIBDEPS += $(TOOLCHAIN_DIR)/lib/libopencm3_stm32f2.a endif +ifeq ($(BITCOIN_ONLY), 1) +CFLAGS += -DBITCOIN_ONLY=1 +CFLAGS += -DU2F_ENABLED=0 +else +CFLAGS += -DBITCOIN_ONLY=0 +CFLAGS += -DU2F_ENABLED=1 +endif + ifeq ($(MEMORY_PROTECT), 0) CFLAGS += -DMEMORY_PROTECT=0 $(info MEMORY_PROTECT=0) diff --git a/legacy/Pipfile b/legacy/Pipfile deleted file mode 100644 index ba4ee323c8..0000000000 --- a/legacy/Pipfile +++ /dev/null @@ -1,14 +0,0 @@ -[[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 deleted file mode 100644 index cd379ed76a..0000000000 --- a/legacy/Pipfile.lock +++ /dev/null @@ -1,214 +0,0 @@ -{ - "_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/bootloader/ChangeLog b/legacy/bootloader/ChangeLog index a348378c01..2c27f6ab70 100644 --- a/legacy/bootloader/ChangeLog +++ b/legacy/bootloader/ChangeLog @@ -1,27 +1,30 @@ -Version 1.6.1 +Version 1.8.0 [Feb 2019] +* Make the update process more similar to Model T process + +Version 1.6.1 [Dec 2018] * Fix USB issue on some Windows 10 installations -Version 1.6.0 +Version 1.6.0 [Sep 2018] * Switch from HID to WebUSB -Version 1.5.1 +Version 1.5.1 [Aug 2018] * Improve MPU configuration -Version 1.5.0 +Version 1.5.0 [Jun 2018] * Make unofficial firmwares work again -Version 1.4.0 +Version 1.4.0 [Mar 2018] * 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 +Version 1.3.3 [Aug 2017] * Add self-test * Erase metadata backup after usage * Erase SRAM on application start -Version 1.3.2 +Version 1.3.2 [Jul 2017] * 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 @@ -29,22 +32,25 @@ Version 1.3.2 * Fix usage of RNG before setup * Fix stack protector fault -Version 1.3.1 +Version 1.3.1 [Feb 2017] * Fix button testing so it does not break USB communication -Version 1.3.0 +Version 1.3.0 [Oct 2016] * 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 +Version 1.2.8 [Sep 2016] +* Don't halt on broken firmware + +Version 1.2.7 [May 2016] * Optimize speed of firmware update -Version 1.2.6 +Version 1.2.6 [Feb 2016] * Show hash of unofficial firmware * Use stack protector * Clean USB descriptor -Version 1.2.5 +Version 1.2.5 [Oct 2014] * Initial import of code diff --git a/legacy/bootloader/bootloader.h b/legacy/bootloader/bootloader.h index b967c7f0b9..98267c9849 100644 --- a/legacy/bootloader/bootloader.h +++ b/legacy/bootloader/bootloader.h @@ -22,7 +22,7 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 8 -#define VERSION_PATCH 0 +#define VERSION_PATCH 1 #define STR(X) #X #define VERSTR(X) STR(X) diff --git a/legacy/bootloader/combine/prepare.py b/legacy/bootloader/combine/prepare.py index eb073e9820..7918e063e1 100755 --- a/legacy/bootloader/combine/prepare.py +++ b/legacy/bootloader/combine/prepare.py @@ -1,12 +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:] +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) +open("combined.bin", "w").write(combined) -print('bootloader : %d bytes' % len(bl)) -print('firmware : %d bytes' % len(fw)) -print('combined : %d bytes' % len(combined)) +print("bootloader : %d bytes" % len(bl)) +print("firmware : %d bytes" % len(fw)) +print("combined : %d bytes" % len(combined)) diff --git a/legacy/bootloader/firmware_align.py b/legacy/bootloader/firmware_align.py index 3c1434987e..0866593f56 100755 --- a/legacy/bootloader/firmware_align.py +++ b/legacy/bootloader/firmware_align.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import sys import os +import sys TOTALSIZE = 32768 MAXSIZE = TOTALSIZE - 32 @@ -8,7 +8,10 @@ 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() + 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 index a7b8e13cf6..948a08f993 100755 --- a/legacy/bootloader/firmware_sign.py +++ b/legacy/bootloader/firmware_sign.py @@ -7,7 +7,6 @@ import struct import ecdsa - SLOTS = 3 pubkeys = { @@ -94,7 +93,7 @@ def update_hashes_in_header(data): data = bytearray(data) o = 0 for h in prepare_hashes(data[FWHEADER_SIZE:]): - data[0x20 + o:0x20 + o + 32] = h + data[0x20 + o : 0x20 + o + 32] = h o += 32 return bytes(data) @@ -112,6 +111,7 @@ def check_size(data): size = struct.unpack(" 32768: - raise Exception('bootloader has to be smaller than 32768 bytes') + raise Exception("bootloader has to be smaller than 32768 bytes") -data += b'\x00' * (32768 - len(data)) +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)) +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) +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/bootloader.dat b/legacy/firmware/bootloader.dat new file mode 100644 index 0000000000..334e31461f Binary files /dev/null and b/legacy/firmware/bootloader.dat differ diff --git a/legacy/firmware/fsm.c b/legacy/firmware/fsm.c index 59f93774f3..59b98a3797 100644 --- a/legacy/firmware/fsm.c +++ b/legacy/firmware/fsm.c @@ -30,18 +30,14 @@ #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" @@ -51,13 +47,20 @@ #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" +#if !BITCOIN_ONLY +#include "ethereum.h" +#include "lisk.h" +#include "nem.h" +#include "nem2.h" +#include "stellar.h" +#endif + // message methods static uint8_t msg_resp[MSG_OUT_SIZE] __attribute__((aligned)); @@ -254,7 +257,12 @@ static bool fsm_layoutAddress(const char *address, const char *desc, #include "fsm_msg_common.h" #include "fsm_msg_crypto.h" #include "fsm_msg_debug.h" + +#if !BITCOIN_ONLY + #include "fsm_msg_ethereum.h" #include "fsm_msg_lisk.h" #include "fsm_msg_nem.h" #include "fsm_msg_stellar.h" + +#endif diff --git a/legacy/firmware/fsm_msg_coin.h b/legacy/firmware/fsm_msg_coin.h index 30b5e1747e..dc3a94e526 100644 --- a/legacy/firmware/fsm_msg_coin.h +++ b/legacy/firmware/fsm_msg_coin.h @@ -147,12 +147,15 @@ static bool path_mismatched(const CoinInfo *coin, const GetAddress *msg) { // m/48' - BIP48 Copay Multisig P2SH // m / purpose' / coin_type' / account' / change / address_index + // Electrum: + // m / purpose' / coin_type' / account' / type' / change / address_index if (msg->address_n[0] == (0x80000000 + 48)) { - mismatch |= (msg->script_type != InputScriptType_SPENDMULTISIG); - mismatch |= (msg->address_n_count != 5); + mismatch |= (msg->script_type != InputScriptType_SPENDMULTISIG) && + (msg->script_type != InputScriptType_SPENDP2SHWITNESS) && + (msg->script_type != InputScriptType_SPENDWITNESS); + mismatch |= (msg->address_n_count != 5) && (msg->address_n_count != 6); 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; } diff --git a/legacy/firmware/fsm_msg_common.h b/legacy/firmware/fsm_msg_common.h index 265b629d40..44dece9919 100644 --- a/legacy/firmware/fsm_msg_common.h +++ b/legacy/firmware/fsm_msg_common.h @@ -256,12 +256,12 @@ void fsm_msgEntropyAck(const EntropyAck *msg) { } void fsm_msgBackupDevice(const BackupDevice *msg) { + (void)msg; + CHECK_INITIALIZED CHECK_PIN_UNCACHED - (void) - msg; char mnemonic[MAX_MNEMONIC_LEN + 1]; if (config_getMnemonic(mnemonic, sizeof(mnemonic))) { reset_backup(true, mnemonic); @@ -273,7 +273,9 @@ void fsm_msgCancel(const Cancel *msg) { (void)msg; recovery_abort(); signing_abort(); +#if !BITCOIN_ONLY ethereum_signing_abort(); +#endif fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); } diff --git a/legacy/firmware/layout2.c b/legacy/firmware/layout2.c index 61510249e8..9b7d961b18 100644 --- a/legacy/firmware/layout2.c +++ b/legacy/firmware/layout2.c @@ -745,11 +745,17 @@ void layoutDecryptIdentity(const IdentityType *identity) { row_user[0] ? row_user : NULL, NULL, NULL, NULL); } +#if U2F_ENABLED + void layoutU2FDialog(const char *verb, const char *appname) { layoutDialog(&bmp_webauthn, NULL, verb, NULL, verb, _("U2F security key?"), NULL, appname, NULL, NULL); } +#endif + +#if !BITCOIN_ONLY + 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]; @@ -908,6 +914,8 @@ void layoutNEMLevy(const NEMMosaicDefinition *definition, uint8_t network) { } } +#endif + 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) && diff --git a/legacy/firmware/protob/Makefile b/legacy/firmware/protob/Makefile index e456572d5f..e1f3daced0 100644 --- a/legacy/firmware/protob/Makefile +++ b/legacy/firmware/protob/Makefile @@ -2,6 +2,12 @@ ifneq ($(V),1) Q := @ endif +ifeq ($(BITCOIN_ONLY), 1) +SKIPPED_MESSAGES := Cardano Tezos Ripple Monero DebugMonero Ontology Tron Eos Binance Ethereum Lisk NEM Stellar +else +SKIPPED_MESSAGES := Cardano Tezos Ripple Monero DebugMonero Ontology Tron Eos Binance +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 @@ -26,7 +32,7 @@ messages_%_pb2.py: messages-%.proto $(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 + $(Q)$(PYTHON) $< ${SKIPPED_MESSAGES} 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_map.py b/legacy/firmware/protob/messages_map.py index c8c763047f..3bb9d4a5e8 100755 --- a/legacy/firmware/protob/messages_map.py +++ b/legacy/firmware/protob/messages_map.py @@ -1,11 +1,16 @@ #!/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 + +from messages_pb2 import ( + MessageType, + wire_bootloader, + wire_debug_in, + wire_debug_out, + wire_in, + wire_no_fsm, + wire_out, +) fh = open("messages_map.h", "wt") fl = open("messages_map_limits.h", "wt") @@ -24,7 +29,7 @@ LABELS = { def handle_message(fh, fl, skipped, message, extension): name = message.name short_name = name.split("MessageType_", 1).pop() - assert(short_name != name) + assert short_name != name for s in skipped: if short_name.startswith(s): @@ -37,14 +42,14 @@ def handle_message(fh, fl, skipped, message, extension): 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) + 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) + 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) + fh.write("\t// Message %s is not used in FSM\n" % short_name) return if direction == "i": @@ -52,13 +57,15 @@ def handle_message(fh, fl, skipped, message, extension): 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, - )) + 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 @@ -69,13 +76,20 @@ def handle_message(fh, fl, skipped, message, extension): 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)) + 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") +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) diff --git a/legacy/firmware/u2f/u2f.h b/legacy/firmware/u2f/u2f.h index b067713963..865778d33c 100644 --- a/legacy/firmware/u2f/u2f.h +++ b/legacy/firmware/u2f/u2f.h @@ -1,3 +1,4 @@ +// clang-format off /** * Copyright FIDO Alliance, 2017 * diff --git a/legacy/firmware/u2f/u2f_hid.h b/legacy/firmware/u2f/u2f_hid.h index f29059da21..39586cdd67 100644 --- a/legacy/firmware/u2f/u2f_hid.h +++ b/legacy/firmware/u2f/u2f_hid.h @@ -1,3 +1,4 @@ +// clang-format off /** * Copyright FIDO Alliance, 2017 * diff --git a/legacy/firmware/usb.c b/legacy/firmware/usb.c index 8d80dbed92..45ecb9ac21 100644 --- a/legacy/firmware/usb.c +++ b/legacy/firmware/usb.c @@ -25,7 +25,9 @@ #include "messages.h" #include "timer.h" #include "trezor.h" +#if U2F_ENABLED #include "u2f.h" +#endif #include "usb.h" #include "util.h" @@ -36,11 +38,19 @@ #define USB_INTERFACE_INDEX_MAIN 0 #if DEBUG_LINK #define USB_INTERFACE_INDEX_DEBUG 1 +#if U2F_ENABLED #define USB_INTERFACE_INDEX_U2F 2 #define USB_INTERFACE_COUNT 3 #else +#define USB_INTERFACE_COUNT 2 +#endif +#else +#if U2F_ENABLED #define USB_INTERFACE_INDEX_U2F 1 #define USB_INTERFACE_COUNT 2 +#else +#define USB_INTERFACE_COUNT 1 +#endif #endif #define ENDPOINT_ADDRESS_MAIN_IN (0x81) @@ -49,8 +59,10 @@ #define ENDPOINT_ADDRESS_DEBUG_IN (0x82) #define ENDPOINT_ADDRESS_DEBUG_OUT (0x02) #endif +#if U2F_ENABLED #define ENDPOINT_ADDRESS_U2F_IN (0x83) #define ENDPOINT_ADDRESS_U2F_OUT (0x03) +#endif #define USB_STRINGS \ X(MANUFACTURER, "SatoshiLabs") \ @@ -88,6 +100,8 @@ static const struct usb_device_descriptor dev_descr = { .bNumConfigurations = 1, }; +#if U2F_ENABLED + static const uint8_t hid_report_descriptor_u2f[] = { 0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance) 0x09, 0x01, // USAGE (U2F HID Authenticator Device) @@ -160,7 +174,10 @@ static const struct usb_interface_descriptor hid_iface_u2f[] = {{ .extralen = sizeof(hid_function_u2f), }}; +#endif + #if DEBUG_LINK + static const struct usb_endpoint_descriptor webusb_endpoints_debug[2] = { { .bLength = USB_DT_ENDPOINT_SIZE, @@ -241,10 +258,12 @@ static const struct usb_interface ifaces[] = { .num_altsetting = 1, .altsetting = webusb_iface_debug, #endif +#if U2F_ENABLED }, { .num_altsetting = 1, .altsetting = hid_iface_u2f, +#endif }}; static const struct usb_config_descriptor config = { @@ -259,6 +278,10 @@ static const struct usb_config_descriptor config = { .interface = ifaces, }; +static volatile char tiny = 0; + +#if U2F_ENABLED + 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) { @@ -277,7 +300,16 @@ static enum usbd_request_return_codes hid_control_request( return 1; } -static volatile char tiny = 0; +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); +} + +#endif static void main_rx_callback(usbd_device *dev, uint8_t ep) { (void)ep; @@ -292,16 +324,8 @@ static void main_rx_callback(usbd_device *dev, uint8_t ep) { } } -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))); @@ -314,6 +338,7 @@ static void debug_rx_callback(usbd_device *dev, uint8_t ep) { msg_read_tiny(buf, 64); } } + #endif static void set_config(usbd_device *dev, uint16_t wValue) { @@ -323,20 +348,23 @@ static void set_config(usbd_device *dev, uint16_t wValue) { 0); usbd_ep_setup(dev, ENDPOINT_ADDRESS_MAIN_OUT, USB_ENDPOINT_ATTR_INTERRUPT, 64, main_rx_callback); +#if U2F_ENABLED 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); +#endif #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 - +#if U2F_ENABLED usbd_register_control_callback( dev, USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, hid_control_request); +#endif } static usbd_device *usbd_dev = NULL; @@ -344,7 +372,7 @@ 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, + *)&webusb_platform_capability_descriptor, }; static const struct usb_bos_descriptor bos_descriptor = { @@ -381,12 +409,14 @@ void usbPoll(void) { 64) { } } +#if U2F_ENABLED data = u2f_out_data(); if (data) { while (usbd_ep_write_packet(usbd_dev, ENDPOINT_ADDRESS_U2F_IN, data, 64) != 64) { } } +#endif #if DEBUG_LINK // write pending debug data data = msg_debug_out_data(); diff --git a/legacy/firmware/version.h b/legacy/firmware/version.h index 1202e1b44f..a0ce369d9f 100644 --- a/legacy/firmware/version.h +++ b/legacy/firmware/version.h @@ -1,6 +1,6 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 8 -#define VERSION_PATCH 0 +#define VERSION_PATCH 1 #define FIX_VERSION_MAJOR 1 #define FIX_VERSION_MINOR 8 diff --git a/legacy/gen/bitmaps.c b/legacy/gen/bitmaps.c index 5d60730d1a..c47c1a0bdd 100644 --- a/legacy/gen/bitmaps.c +++ b/legacy/gen/bitmaps.c @@ -1,3 +1,4 @@ +// clang-format off #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, }; diff --git a/legacy/gen/bitmaps.h b/legacy/gen/bitmaps.h index c06351db07..41bc1507dc 100644 --- a/legacy/gen/bitmaps.h +++ b/legacy/gen/bitmaps.h @@ -4,8 +4,8 @@ #include typedef struct { - uint8_t width, height; - const uint8_t *data; + uint8_t width, height; + const uint8_t *data; } BITMAP; extern const BITMAP bmp_digit0; diff --git a/legacy/gen/bitmaps/generate.py b/legacy/gen/bitmaps/generate.py index d0005427ce..0e23ae435a 100755 --- a/legacy/gen/bitmaps/generate.py +++ b/legacy/gen/bitmaps/generate.py @@ -1,58 +1,64 @@ #!/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 + 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 +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.c", "wt") as f: + f.write("// clang-format off\n") + 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__ +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; + uint8_t width, height; + const uint8_t *data; } BITMAP; -''') +""" + ) - for i in range(cnt): - f.write(hdrs[i]) + for i in range(cnt): + f.write(hdrs[i]) - f.write('\n#endif\n') - f.close() + f.write("\n#endif\n") + f.close() diff --git a/legacy/gen/fonts.c b/legacy/gen/fonts.c index 68deb549bc..5bcf305a4c 100644 --- a/legacy/gen/fonts.c +++ b/legacy/gen/fonts.c @@ -1,18 +1,16 @@ #include "fonts.h" -const uint8_t * const font_data[2][128] = { - { -#include"font.inc" - }, - { -#include"fontfixed.inc" - }, +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]; -} +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; + return font_data[font][c & 0x7f] + 1; } diff --git a/legacy/gen/fonts.h b/legacy/gen/fonts.h index dbbbef1303..2c1a9b2a52 100644 --- a/legacy/gen/fonts.h +++ b/legacy/gen/fonts.h @@ -5,10 +5,10 @@ #define FONT_HEIGHT 8 #define FONT_STANDARD 0 -#define FONT_FIXED 1 -#define FONT_DOUBLE 0x80 +#define FONT_FIXED 1 +#define FONT_DOUBLE 0x80 -extern const uint8_t * const font_data[2][128]; +extern const uint8_t *const font_data[2][128]; int fontCharWidth(int font, char c); const uint8_t *fontCharData(int font, char c); diff --git a/legacy/gen/fonts/generate.py b/legacy/gen/fonts/generate.py index 3299138c71..463d73e63a 100755 --- a/legacy/gen/fonts/generate.py +++ b/legacy/gen/fonts/generate.py @@ -1,39 +1,40 @@ #!/usr/bin/env python3 from PIL import Image -class Img(object): +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 ] + p = self.data[r + c * self.w] if p == (255, 255, 255): - return '0' + return "0" if p == (0, 0, 0): - return '1' + return "1" if p == (255, 0, 255): return None - raise Exception('Unknown color', p) + raise Exception("Unknown color", p) def convert(imgfile, outfile): img = Img(imgfile) - cur = '' - with open(outfile, 'w') as f: + 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)) + cur = "" + while img.pixel(x, y) is not 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)) + 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') + +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 index 5e79610675..7d5802af06 100755 --- a/legacy/gen/handlers/handlers.py +++ b/legacy/gen/handlers/handlers.py @@ -2,99 +2,102 @@ 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', + "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') +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 index 8ba3f1c367..c7c0125941 100644 --- a/legacy/gen/strwidth.c +++ b/legacy/gen/strwidth.c @@ -1,35 +1,35 @@ +#include +#include #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'; - } + 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); - } + 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; - } + 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); - } + printf("%zu\n", width); + free(line); + } } diff --git a/legacy/script/bootstrap b/legacy/script/bootstrap index 500e9a0612..617aa69118 100755 --- a/legacy/script/bootstrap +++ b/legacy/script/bootstrap @@ -5,6 +5,6 @@ set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." git submodule update --init --recursive diff --git a/legacy/script/fullbuild b/legacy/script/fullbuild index f8ddb87424..30fce963ff 100755 --- a/legacy/script/fullbuild +++ b/legacy/script/fullbuild @@ -9,22 +9,26 @@ export LANG=C.UTF-8 set -eu -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." + +EMULATOR=${EMULATOR:-0} + +readonly MCU_ROOT=legacy 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_DIR="$BUILD_DIR/legacy/bootloader" +readonly BOOTLOADER_FILENAME="$MCU_ROOT/bootloader/bootloader.bin" readonly BOOTLOADER_PATH="$BOOTLOADER_DIR/$BOOTLOADER_FILENAME" -readonly FIRMWARE_DIR="$BUILD_DIR/firmware" -readonly FIRMWARE_FILENAME="firmware/trezor.bin" +readonly FIRMWARE_DIR="$BUILD_DIR/legacy/firmware" +readonly FIRMWARE_FILENAME="$MCU_ROOT/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" +readonly EMULATOR_FILENAME="$MCU_ROOT/firmware/trezor-emulator.elf" +readonly EMULATOR_PATH="$EMULATOR_DIR/$MCU_ROOT/firmware/trezor.elf" worktree_setup() { local path="$1" @@ -36,27 +40,22 @@ worktree_setup() { # 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 ) + ( cd "$path/$MCU_ROOT" && 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 ) + if [ ! -e "$path/Pipfile" ]; then + echo "Can't handle pre-monorepo tags properly. You will have to check out manually" + exit 1 fi + + pushd $path + export HOME=/tmp + pipenv install + pipenv run $MCU_ROOT/script/cibuild + popd } worktree_copy() { @@ -64,18 +63,23 @@ worktree_copy() { local filename="$2" local pattern="$3" - local describe="$(git -C "$path" describe --tags --match "$pattern")" + local describe="$(git -C "$path" describe --tags --match "legacy/$pattern")" + describe="${describe##legacy/}" 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 + if [ "$EMULATOR" = 1 ]; then + install -Dm0644 "${src%.*}.elf" "${dest%.*}.elf" + else + for extension in "${ARTIFACT_EXTENSIONS[@]}"; do + install -Dm0644 \ + "${src%.*}.$extension" \ + "${dest%.*}.$extension" + done + fi printf "%s" "$dest" } @@ -84,7 +88,6 @@ main() { local bootloader_commit="$1" local firmware_commit="$2" - script/bootstrap worktree_setup "$FIRMWARE_DIR" "$firmware_commit" if [ "$EMULATOR" != 1 ]; then @@ -116,13 +119,13 @@ main() { "$BOOTLOADER_FILENAME" \ "bl*")" - printf "\n\n"; $PYTHON script/fingerprint \ + printf "\n\n"; $PYTHON $MCU_ROOT/script/fingerprint \ "$bootloader_path" \ --max-size 32768 \ --double fi - printf "\n\n"; $PYTHON script/fingerprint \ + printf "\n\n"; $PYTHON $MCU_ROOT/script/fingerprint \ "$firmware_path" \ --offset 256 \ --max-size 983296 # 256 + 64*1024 + 3*128*1024 + 4*128*1024 diff --git a/legacy/shell.nix b/legacy/shell.nix deleted file mode 100644 index 87f5ce3442..0000000000 --- a/legacy/shell.nix +++ /dev/null @@ -1,9 +0,0 @@ -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/webusb.c b/legacy/webusb.c index 24f28a4ca5..b4f2677e2d 100644 --- a/legacy/webusb.c +++ b/legacy/webusb.c @@ -32,17 +32,6 @@ const struct webusb_platform_descriptor webusb_platform_capability_descriptor = .bVendorCode = WEBUSB_VENDOR_CODE, .iLandingPage = 1}; -const struct webusb_platform_descriptor - webusb_platform_capability_descriptor_no_landing_page = { - .bLength = WEBUSB_PLATFORM_DESCRIPTOR_SIZE, - .bDescriptorType = USB_DT_DEVICE_CAPABILITY, - .bDevCapabilityType = USB_DC_PLATFORM, - .bReserved = 0, - .platformCapabilityUUID = WEBUSB_UUID, - .bcdVersion = 0x0100, - .bVendorCode = WEBUSB_VENDOR_CODE, - .iLandingPage = 0}; - static const char* webusb_https_url; static enum usbd_request_return_codes webusb_control_vendor_request( diff --git a/legacy/webusb.h b/legacy/webusb.h index 01feda4e5b..5835e3ca4e 100644 --- a/legacy/webusb.h +++ b/legacy/webusb.h @@ -27,8 +27,6 @@ extern const struct webusb_platform_descriptor webusb_platform_capability_descriptor; -extern const struct webusb_platform_descriptor - webusb_platform_capability_descriptor_no_landing_page; extern void webusb_setup(usbd_device* usbd_dev, const char* https_url); diff --git a/python/.travis.yml b/python/.travis.yml deleted file mode 100644 index c7c009af12..0000000000 --- a/python/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -language: python - -# Runs jobs on container based infrastructure -sudo: false - -# Saves pip downloads/wheels between builds -cache: - directories: - - $HOME/.cache/pip - -addons: - apt: - packages: - - libudev-dev - - libusb-1.0-0-dev - -env: - global: - PROTOBUF_VERSION=3.4.0 - -python: - - "3.5" - - "3.6" - -# workaround for https://github.com/travis-ci/travis-ci/issues/9815 -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true - -install: - # Optimisation: build requirements as wheels, which get cached by Travis - - pip install "pip>=9.0" wheel # pip 9.0 understands `python_requires` constraints - - pip install "setuptools>=38" # setuptools >= 38 are capable of using prebuilt wheels - - pip install tox-travis - - pip install -r requirements-dev.txt - # protobuf-related dependencies - - curl -LO "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" - -before_script: - - ./trigger-travis.sh - -script: - - python setup.py install - - if [ $TRAVIS_PYTHON_VERSION != 3.5 ]; then make style_check; fi - - tox - -notifications: - webhooks: - urls: - - http://ci-bot.satoshilabs.com:5000/travis - on_success: always - on_failure: always - on_start: always diff --git a/python/trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py b/python/trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py index d0478b5763..ecd6101a76 100644 --- a/python/trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py +++ b/python/trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py @@ -14,6 +14,8 @@ # You should have received a copy of the License along with this library. # If not, see . +import time + import pytest from trezorlib import messages, tezos @@ -23,6 +25,7 @@ from trezorlib.tools import parse_path from .common import TrezorTest TEZOS_PATH = parse_path("m/44'/1729'/0'") +TEZOS_PATH_10 = parse_path("m/44'/1729'/10'") @pytest.mark.tezos @@ -194,3 +197,152 @@ class TestMsgTezosSignTx(TrezorTest): assert ( resp.operation_hash == "oocgc3hyKsGHPsw6WFWJpWT8jBwQLtebQAXF27KNisThkzoj635" ) + + def input_flow(self, num_pages): + yield + time.sleep(1) + for _ in range(num_pages - 1): + self.client.debug.swipe_down() + time.sleep(1) + self.client.debug.press_yes() + + def test_tezos_sign_tx_proposal(self): + self.setup_mnemonic_allallall() + + self.client.set_input_flow(self.input_flow(num_pages=1)) + resp = tezos.sign_tx( + self.client, + TEZOS_PATH_10, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "dee04042c0832d68a43699b2001c0a38065436eb05e578071a763e1972d0bc81", + "proposal": { + "source": "005f450441f41ee11eee78a31d1e1e55627c783bd6", + "period": 17, + "proposals": [ + "dfa974df171c2dad9a9b8f25d99af41fd9702ce5d04521d2f9943c84d88aa572" + ], + }, + }, + ), + ) + assert ( + resp.signature + == "edsigtfY16R32k2WVMYfFr7ymnro4ib5zMckk28vsuViYNN77DJAvCJLRNArd9L531pUCxT4YdcvCvBym5dhcZ1rknEVm6yZ8bB" + ) + assert ( + resp.sig_op_contents.hex() + == "dee04042c0832d68a43699b2001c0a38065436eb05e578071a763e1972d0bc8105005f450441f41ee11eee78a31d1e1e55627c783bd60000001100000020dfa974df171c2dad9a9b8f25d99af41fd9702ce5d04521d2f9943c84d88aa5723b12621296a679b3a74ea790df5347995a76e20a09e76590baaacf4e09341965a04123f5cbbba8427f045b5f7d59157a3098e44839babe7c247d19b58bbb2405" + ) + assert ( + resp.operation_hash == "opLqntFUu984M7LnGsFvfGW6kWe9QjAz4AfPDqQvwJ1wPM4Si4c" + ) + + def test_tezos_sign_tx_multiple_proposals(self): + self.setup_mnemonic_allallall() + + self.client.set_input_flow(self.input_flow(num_pages=2)) + resp = tezos.sign_tx( + self.client, + TEZOS_PATH_10, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "7e0be36a90c663c73c60da3889ffefff1383fb65cc29f0639f173d8f95a52df7", + "proposal": { + "source": "005f450441f41ee11eee78a31d1e1e55627c783bd6", + "period": 17, + "proposals": [ + "2a6ff28ab4d0ccb18f7129aaaf9a4b8027d794f2562849665fdb6999db2a4e57", + "47cd60c09ab8437cc9fe19add494dce1b9844100f660f02ce77510a0c66d2762", + ], + }, + }, + ), + ) + assert ( + resp.signature + == "edsigu6GAjhiWAQ64ctWTGEDYAZ16tYzLgzWzqc4CUyixK4FGRE8YUBVzFaVJ2fUCexZjZLMLdiNZGcUdzeL1bQhZ2h5oLrh7pA" + ) + assert ( + resp.sig_op_contents.hex() + == "7e0be36a90c663c73c60da3889ffefff1383fb65cc29f0639f173d8f95a52df705005f450441f41ee11eee78a31d1e1e55627c783bd600000011000000402a6ff28ab4d0ccb18f7129aaaf9a4b8027d794f2562849665fdb6999db2a4e5747cd60c09ab8437cc9fe19add494dce1b9844100f660f02ce77510a0c66d2762f813361ac00ada7e3256f23973ae25b112229476a3cb3e506fe929ea1e9358299fed22178d1be689cddeedd1f303abfef859b664f159a528576a1c807079f005" + ) + assert ( + resp.operation_hash == "onobSyNgiitGXxSVFJN6949MhUomkkxvH4ZJ2owgWwNeDdntF9Y" + ) + + def test_tezos_sing_tx_ballot_yay(self): + self.setup_mnemonic_allallall() + + resp = tezos.sign_tx( + self.client, + TEZOS_PATH_10, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "3a8f60c4cd394cee5b50136c7fc8cb157e8aaa476a9e5c68709be6fc1cdb5395", + "ballot": { + "source": "0002298c03ed7d454a101eb7022bc95f7e5f41ac78", + "period": 2, + "proposal": "def7ed9c84af23ab37ebb60dd83cd103d1272ad6c63d4c05931567e65ed027e3", + "ballot": 0, + }, + }, + ), + ) + + assert ( + resp.signature + == "edsigtkxNm6YXwtV24DqeuimeZFTeFCn2jDYheSsXT4rHMcEjNvzsiSo55nVyVsQxtEe8M7U4PWJWT4rGYYGckQCgtkNJkd2roX" + ) + + def test_tezos_sing_tx_ballot_nay(self): + self.setup_mnemonic_allallall() + + resp = tezos.sign_tx( + self.client, + TEZOS_PATH_10, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "3a8f60c4cd394cee5b50136c7fc8cb157e8aaa476a9e5c68709be6fc1cdb5395", + "ballot": { + "source": "0002298c03ed7d454a101eb7022bc95f7e5f41ac78", + "period": 2, + "proposal": "def7ed9c84af23ab37ebb60dd83cd103d1272ad6c63d4c05931567e65ed027e3", + "ballot": 1, + }, + }, + ), + ) + assert ( + resp.signature + == "edsigtqLaizfF6Cfc2JQL7TrsyniGhpZEojZAKMFW6AeudaUoU8KGXEHJH69Q4Lf27qFyUSTfbeHNnnCt69SGEPWkmpkgkgqMbL" + ) + + def test_tezos_sing_tx_ballot_pass(self): + self.setup_mnemonic_allallall() + + resp = tezos.sign_tx( + self.client, + TEZOS_PATH_10, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "3a8f60c4cd394cee5b50136c7fc8cb157e8aaa476a9e5c68709be6fc1cdb5395", + "ballot": { + "source": "0002298c03ed7d454a101eb7022bc95f7e5f41ac78", + "period": 2, + "proposal": "def7ed9c84af23ab37ebb60dd83cd103d1272ad6c63d4c05931567e65ed027e3", + "ballot": 2, + }, + }, + ), + ) + + assert ( + resp.signature + == "edsigu6YX7EegPwrpcEbdNQsNhrRiEagBNGJBmFamP4mixZZw1UynhahGQ8RNiZLSUVLERUZwygrsSVenBqXGt9VnknTxtzjKzv" + ) diff --git a/python/trezorlib/tests/device_tests/test_multisig_change.py b/python/trezorlib/tests/device_tests/test_multisig_change.py index 957e09c679..d207771aed 100644 --- a/python/trezorlib/tests/device_tests/test_multisig_change.py +++ b/python/trezorlib/tests/device_tests/test_multisig_change.py @@ -127,15 +127,7 @@ class TestMultisigChange(TrezorTest): proto.TxRequest( request_type=proto.RequestType.TXINPUT, details=proto.TxRequestDetailsType(request_index=0), - ) - ] - if TREZOR_VERSION != 1: - # trezor 1 does not have UnknownDerivationPath implemented - resp.append( - proto.ButtonRequest(code=proto.ButtonRequestType.UnknownDerivationPath) - ) - - resp += [ + ), proto.TxRequest( request_type=proto.RequestType.TXMETA, details=proto.TxRequestDetailsType(tx_hash=inp1.prev_hash), @@ -162,14 +154,6 @@ class TestMultisigChange(TrezorTest): request_type=proto.RequestType.TXINPUT, details=proto.TxRequestDetailsType(request_index=1), ), - ] - if TREZOR_VERSION != 1: - # trezor 1 does not have UnknownDerivationPath implemented - resp.append( - proto.ButtonRequest(code=proto.ButtonRequestType.UnknownDerivationPath) - ) - - resp += [ proto.TxRequest( request_type=proto.RequestType.TXMETA, details=proto.TxRequestDetailsType(tx_hash=inp2.prev_hash), diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..c7f5cdead8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,36 @@ +[flake8] +ignore = + # E203 whitespace before ':' + E203, + # E221: multiple spaces before operator + E221, + # E241: multiple spaces after ':' + E241, + # E402: module level import not at top of file + E402, + # E501: line too long + E501, + # E741 ambiguous variable name + E741, + # F403: star import used, unable to detect undefined names + F403, + # F405: name may be undefined, or defined from star imports + F405, + # W503: line break before binary operator + W503 + +[isort] +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 0 +combine_as_imports = True +line_length = 88 +not_skip=__init__.py +forced_separate = apps +known_standard_library = micropython,ubinascii,ustruct,uctypes,utime,utimeq,trezorio,trezorui,trezorutils,trezorconfig + +# TODO: ask matejcik if we can omit this completely +# [tool:pytest] +# addopts = --pyargs trezorlib.tests.device_tests +# xfail_strict = true +# run_xfail = diff --git a/storage/README.md b/storage/README.md index f44352a283..5e1bacc556 100644 --- a/storage/README.md +++ b/storage/README.md @@ -2,7 +2,7 @@ This repository contains the implementation of Trezor's internal storage, which is common for both trezor-mcu (Trezor One) and trezor-core (Trezor T). This README also contains a detailed description of the cryptographic design. -All tests are located in the [trezor-storage-test](https://github.com/trezor/trezor-storage-test) repository, which also includes a Python implementation to run tests against this C production version and the Python one. +All tests are located in the `tests` subdirectory, which also includes a Python implementation to run tests against this C production version and the Python one. ## Summary diff --git a/storage/norcow.c b/storage/norcow.c index 3ef5d6f9cc..d0a845c522 100644 --- a/storage/norcow.c +++ b/storage/norcow.c @@ -19,33 +19,35 @@ #include -#include "norcow.h" -#include "flash.h" #include "common.h" +#include "flash.h" +#include "norcow.h" // NRC2 = 4e524332 -#define NORCOW_MAGIC ((uint32_t)0x3243524e) +#define NORCOW_MAGIC ((uint32_t)0x3243524e) // NRCW = 4e524357 -#define NORCOW_MAGIC_V0 ((uint32_t)0x5743524e) +#define NORCOW_MAGIC_V0 ((uint32_t)0x5743524e) -#define NORCOW_WORD_SIZE (sizeof(uint32_t)) +#define NORCOW_WORD_SIZE (sizeof(uint32_t)) #define NORCOW_PREFIX_LEN NORCOW_WORD_SIZE -#define NORCOW_MAGIC_LEN NORCOW_WORD_SIZE +#define NORCOW_MAGIC_LEN NORCOW_WORD_SIZE #define NORCOW_VERSION_LEN NORCOW_WORD_SIZE // The key value which is used to indicate that the entry is not set. -#define NORCOW_KEY_FREE (0xFFFF) +#define NORCOW_KEY_FREE (0xFFFF) // The key value which is used to indicate that the entry has been deleted. #define NORCOW_KEY_DELETED (0x0000) // The offset from the beginning of the sector where stored items start. -#define NORCOW_STORAGE_START (NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN + NORCOW_VERSION_LEN) +#define NORCOW_STORAGE_START \ + (NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN + NORCOW_VERSION_LEN) // Map from sector index to sector number. static const uint8_t norcow_sectors[NORCOW_SECTOR_COUNT] = NORCOW_SECTORS; -// The index of the active reading sector and writing sector. These should be equal except when storage version upgrade or compaction is in progress. +// The index of the active reading sector and writing sector. These should be +// equal except when storage version upgrade or compaction is in progress. static uint8_t norcow_active_sector = 0; static uint8_t norcow_write_sector = 0; @@ -59,76 +61,79 @@ static uint32_t norcow_free_offset = 0; * Returns pointer to sector, starting with offset * Fails when there is not enough space for data of given size */ -static const void *norcow_ptr(uint8_t sector, uint32_t offset, uint32_t size) -{ - ensure(sectrue * (sector <= NORCOW_SECTOR_COUNT), "invalid sector"); - return flash_get_address(norcow_sectors[sector], offset, size); +static const void *norcow_ptr(uint8_t sector, uint32_t offset, uint32_t size) { + ensure(sectrue * (sector <= NORCOW_SECTOR_COUNT), "invalid sector"); + return flash_get_address(norcow_sectors[sector], offset, size); } /* * Writes data to given sector, starting from offset */ -static secbool norcow_write(uint8_t sector, uint32_t offset, uint32_t prefix, const uint8_t *data, uint16_t len) -{ - if (sector >= NORCOW_SECTOR_COUNT) { - return secfalse; +static secbool norcow_write(uint8_t sector, uint32_t offset, uint32_t prefix, + const uint8_t *data, uint16_t len) { + if (sector >= NORCOW_SECTOR_COUNT) { + return secfalse; + } + + if (offset + NORCOW_PREFIX_LEN + len > NORCOW_SECTOR_SIZE) { + return secfalse; + } + + ensure(flash_unlock_write(), NULL); + + // write prefix + ensure(flash_write_word(norcow_sectors[sector], offset, prefix), NULL); + offset += NORCOW_PREFIX_LEN; + + if (data != NULL) { + // write data + for (uint16_t i = 0; i < len; i++, offset++) { + ensure(flash_write_byte(norcow_sectors[sector], offset, data[i]), NULL); } + } else { + offset += len; + } - if (offset + NORCOW_PREFIX_LEN + len > NORCOW_SECTOR_SIZE) { - return secfalse; - } + // pad with zeroes + for (; offset % NORCOW_WORD_SIZE; offset++) { + ensure(flash_write_byte(norcow_sectors[sector], offset, 0x00), NULL); + } - ensure(flash_unlock_write(), NULL); - - // write prefix - ensure(flash_write_word(norcow_sectors[sector], offset, prefix), NULL); - offset += NORCOW_PREFIX_LEN; - - if (data != NULL) { - // write data - for (uint16_t i = 0; i < len; i++, offset++) { - ensure(flash_write_byte(norcow_sectors[sector], offset, data[i]), NULL); - } - } else { - offset += len; - } - - // pad with zeroes - for (; offset % NORCOW_WORD_SIZE; offset++) { - ensure(flash_write_byte(norcow_sectors[sector], offset, 0x00), NULL); - } - - ensure(flash_lock_write(), NULL); - return sectrue; + ensure(flash_lock_write(), NULL); + return sectrue; } /* * Erases sector (and sets a magic) */ -static void erase_sector(uint8_t sector, secbool set_magic) -{ +static void erase_sector(uint8_t sector, secbool set_magic) { #if NORCOW_HEADER_LEN > 0 - // Backup the sector header. - uint32_t header_backup[NORCOW_HEADER_LEN/sizeof(uint32_t)]; - const void *sector_start = norcow_ptr(sector, 0, NORCOW_HEADER_LEN); - memcpy(header_backup, sector_start, sizeof(header_backup)); + // Backup the sector header. + uint32_t header_backup[NORCOW_HEADER_LEN / sizeof(uint32_t)]; + const void *sector_start = norcow_ptr(sector, 0, NORCOW_HEADER_LEN); + memcpy(header_backup, sector_start, sizeof(header_backup)); #endif - ensure(flash_erase(norcow_sectors[sector]), "erase failed"); + ensure(flash_erase(norcow_sectors[sector]), "erase failed"); #if NORCOW_HEADER_LEN > 0 - // Copy the sector header back. - ensure(flash_unlock_write(), NULL); - for (uint32_t i = 0; i < NORCOW_HEADER_LEN/sizeof(uint32_t); ++i) { - ensure(flash_write_word(norcow_sectors[sector], i*sizeof(uint32_t), header_backup[i]), NULL); - } - ensure(flash_lock_write(), NULL); + // Copy the sector header back. + ensure(flash_unlock_write(), NULL); + for (uint32_t i = 0; i < NORCOW_HEADER_LEN / sizeof(uint32_t); ++i) { + ensure(flash_write_word(norcow_sectors[sector], i * sizeof(uint32_t), + header_backup[i]), + NULL); + } + ensure(flash_lock_write(), NULL); #endif - if (sectrue == set_magic) { - ensure(norcow_write(sector, NORCOW_HEADER_LEN, NORCOW_MAGIC, NULL, 0), "set magic failed"); - ensure(norcow_write(sector, NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN, ~NORCOW_VERSION, NULL, 0), "set version failed"); - } + if (sectrue == set_magic) { + ensure(norcow_write(sector, NORCOW_HEADER_LEN, NORCOW_MAGIC, NULL, 0), + "set magic failed"); + ensure(norcow_write(sector, NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN, + ~NORCOW_VERSION, NULL, 0), + "set version failed"); + } } #define ALIGN4(X) (X) = ((X) + 3) & ~3 @@ -136,263 +141,262 @@ static void erase_sector(uint8_t sector, secbool set_magic) /* * Reads one item starting from offset */ -static secbool read_item(uint8_t sector, uint32_t offset, uint16_t *key, const void **val, uint16_t *len, uint32_t *pos) -{ - *pos = offset; +static secbool read_item(uint8_t sector, uint32_t offset, uint16_t *key, + const void **val, uint16_t *len, uint32_t *pos) { + *pos = offset; - const void *k = norcow_ptr(sector, *pos, 2); - if (k == NULL) return secfalse; - *pos += 2; - memcpy(key, k, sizeof(uint16_t)); - if (*key == NORCOW_KEY_FREE) { - return secfalse; - } + const void *k = norcow_ptr(sector, *pos, 2); + if (k == NULL) return secfalse; + *pos += 2; + memcpy(key, k, sizeof(uint16_t)); + if (*key == NORCOW_KEY_FREE) { + return secfalse; + } - const void *l = norcow_ptr(sector, *pos, 2); - if (l == NULL) return secfalse; - *pos += 2; - memcpy(len, l, sizeof(uint16_t)); + const void *l = norcow_ptr(sector, *pos, 2); + if (l == NULL) return secfalse; + *pos += 2; + memcpy(len, l, sizeof(uint16_t)); - *val = norcow_ptr(sector, *pos, *len); - if (*val == NULL) return secfalse; - *pos += *len; - ALIGN4(*pos); - return sectrue; + *val = norcow_ptr(sector, *pos, *len); + if (*val == NULL) return secfalse; + *pos += *len; + ALIGN4(*pos); + return sectrue; } /* * Writes one item starting from offset */ -static secbool write_item(uint8_t sector, uint32_t offset, uint16_t key, const void *val, uint16_t len, uint32_t *pos) -{ - uint32_t prefix = ((uint32_t)len << 16) | key; - *pos = offset + NORCOW_PREFIX_LEN + len; - ALIGN4(*pos); - return norcow_write(sector, offset, prefix, val, len); +static secbool write_item(uint8_t sector, uint32_t offset, uint16_t key, + const void *val, uint16_t len, uint32_t *pos) { + uint32_t prefix = ((uint32_t)len << 16) | key; + *pos = offset + NORCOW_PREFIX_LEN + len; + ALIGN4(*pos); + return norcow_write(sector, offset, prefix, val, len); } /* * Finds the offset from the beginning of the sector where stored items start. */ -static secbool find_start_offset(uint8_t sector, uint32_t *offset, uint32_t *version) -{ - const uint32_t *magic = norcow_ptr(sector, NORCOW_HEADER_LEN, NORCOW_MAGIC_LEN + NORCOW_VERSION_LEN); - if (magic == NULL) { - return secfalse; - } +static secbool find_start_offset(uint8_t sector, uint32_t *offset, + uint32_t *version) { + const uint32_t *magic = norcow_ptr(sector, NORCOW_HEADER_LEN, + NORCOW_MAGIC_LEN + NORCOW_VERSION_LEN); + if (magic == NULL) { + return secfalse; + } - if (*magic == NORCOW_MAGIC) { - *offset = NORCOW_STORAGE_START; - *version = ~(magic[1]); - } else if (*magic == NORCOW_MAGIC_V0) { - *offset = NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN; - *version = 0; - } else { - return secfalse; - } + if (*magic == NORCOW_MAGIC) { + *offset = NORCOW_STORAGE_START; + *version = ~(magic[1]); + } else if (*magic == NORCOW_MAGIC_V0) { + *offset = NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN; + *version = 0; + } else { + return secfalse; + } - return sectrue; + return sectrue; } /* * Finds item in given sector */ -static secbool find_item(uint8_t sector, uint16_t key, const void **val, uint16_t *len) -{ - *val = NULL; - *len = 0; +static secbool find_item(uint8_t sector, uint16_t key, const void **val, + uint16_t *len) { + *val = NULL; + *len = 0; - uint32_t offset; - uint32_t version; - if (sectrue != find_start_offset(sector, &offset, &version)) { - return secfalse; - } + uint32_t offset; + uint32_t version; + if (sectrue != find_start_offset(sector, &offset, &version)) { + return secfalse; + } - for (;;) { - uint16_t k, l; - const void *v; - uint32_t pos; - if (sectrue != read_item(sector, offset, &k, &v, &l, &pos)) { - break; - } - if (key == k) { - *val = v; - *len = l; - } - offset = pos; + for (;;) { + uint16_t k, l; + const void *v; + uint32_t pos; + if (sectrue != read_item(sector, offset, &k, &v, &l, &pos)) { + break; } - return sectrue * (*val != NULL); + if (key == k) { + *val = v; + *len = l; + } + offset = pos; + } + return sectrue * (*val != NULL); } /* * Finds first unused offset in given sector */ -static uint32_t find_free_offset(uint8_t sector) -{ - uint32_t offset; - uint32_t version; - if (sectrue != find_start_offset(sector, &offset, &version)) { - return secfalse; - } +static uint32_t find_free_offset(uint8_t sector) { + uint32_t offset; + uint32_t version; + if (sectrue != find_start_offset(sector, &offset, &version)) { + return secfalse; + } - for (;;) { - uint16_t key, len; - const void *val; - uint32_t pos; - if (sectrue != read_item(sector, offset, &key, &val, &len, &pos)) { - break; - } - offset = pos; + for (;;) { + uint16_t key, len; + const void *val; + uint32_t pos; + if (sectrue != read_item(sector, offset, &key, &val, &len, &pos)) { + break; } - return offset; + offset = pos; + } + return offset; } /* * Compacts active sector and sets new active sector */ -static void compact(void) -{ - uint32_t offsetr; - uint32_t version; - if (sectrue != find_start_offset(norcow_active_sector, &offsetr, &version)) { - return; +static void compact(void) { + uint32_t offsetr; + uint32_t version; + if (sectrue != find_start_offset(norcow_active_sector, &offsetr, &version)) { + return; + } + + norcow_write_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT; + erase_sector(norcow_write_sector, sectrue); + uint32_t offsetw = NORCOW_STORAGE_START; + + for (;;) { + // read item + uint16_t k, l; + const void *v; + uint32_t posr; + secbool r = read_item(norcow_active_sector, offsetr, &k, &v, &l, &posr); + if (sectrue != r) { + break; + } + offsetr = posr; + + // skip deleted items + if (k == NORCOW_KEY_DELETED) { + continue; } - norcow_write_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT; - erase_sector(norcow_write_sector, sectrue); - uint32_t offsetw = NORCOW_STORAGE_START; + // copy the item + uint32_t posw; + ensure(write_item(norcow_write_sector, offsetw, k, v, l, &posw), + "compaction write failed"); + offsetw = posw; + } - for (;;) { - // read item - uint16_t k, l; - const void *v; - uint32_t posr; - secbool r = read_item(norcow_active_sector, offsetr, &k, &v, &l, &posr); - if (sectrue != r) { - break; - } - offsetr = posr; - - // skip deleted items - if (k == NORCOW_KEY_DELETED) { - continue; - } - - // copy the item - uint32_t posw; - ensure(write_item(norcow_write_sector, offsetw, k, v, l, &posw), "compaction write failed"); - offsetw = posw; - } - - erase_sector(norcow_active_sector, secfalse); - norcow_active_sector = norcow_write_sector; - norcow_active_version = NORCOW_VERSION; - norcow_free_offset = find_free_offset(norcow_write_sector); + erase_sector(norcow_active_sector, secfalse); + norcow_active_sector = norcow_write_sector; + norcow_active_version = NORCOW_VERSION; + norcow_free_offset = find_free_offset(norcow_write_sector); } /* * Initializes storage */ -void norcow_init(uint32_t *norcow_version) -{ - flash_init(); - secbool found = secfalse; - *norcow_version = 0; - // detect active sector - starts with magic and has highest version - for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) { - uint32_t offset; - if (sectrue == find_start_offset(i, &offset, &norcow_active_version) && norcow_active_version >= *norcow_version) { - found = sectrue; - norcow_active_sector = i; - *norcow_version = norcow_active_version; - } +void norcow_init(uint32_t *norcow_version) { + flash_init(); + secbool found = secfalse; + *norcow_version = 0; + // detect active sector - starts with magic and has highest version + for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) { + uint32_t offset; + if (sectrue == find_start_offset(i, &offset, &norcow_active_version) && + norcow_active_version >= *norcow_version) { + found = sectrue; + norcow_active_sector = i; + *norcow_version = norcow_active_version; } + } - // If no active sectors found or version downgrade, then erase. - if (sectrue != found || *norcow_version > NORCOW_VERSION) { - norcow_wipe(); - *norcow_version = NORCOW_VERSION; - } else if (*norcow_version < NORCOW_VERSION) { - // Prepare write sector for storage upgrade. - norcow_write_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT; - erase_sector(norcow_write_sector, sectrue); - norcow_free_offset = find_free_offset(norcow_write_sector); - } else { - norcow_write_sector = norcow_active_sector; - norcow_free_offset = find_free_offset(norcow_write_sector); - } + // If no active sectors found or version downgrade, then erase. + if (sectrue != found || *norcow_version > NORCOW_VERSION) { + norcow_wipe(); + *norcow_version = NORCOW_VERSION; + } else if (*norcow_version < NORCOW_VERSION) { + // Prepare write sector for storage upgrade. + norcow_write_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT; + erase_sector(norcow_write_sector, sectrue); + norcow_free_offset = find_free_offset(norcow_write_sector); + } else { + norcow_write_sector = norcow_active_sector; + norcow_free_offset = find_free_offset(norcow_write_sector); + } } /* * Wipe the storage */ -void norcow_wipe(void) -{ - erase_sector(0, sectrue); - for (uint8_t i = 1; i < NORCOW_SECTOR_COUNT; i++) { - erase_sector(i, secfalse); - } - norcow_active_sector = 0; - norcow_active_version = NORCOW_VERSION; - norcow_write_sector = 0; - norcow_free_offset = NORCOW_STORAGE_START; +void norcow_wipe(void) { + erase_sector(0, sectrue); + for (uint8_t i = 1; i < NORCOW_SECTOR_COUNT; i++) { + erase_sector(i, secfalse); + } + norcow_active_sector = 0; + norcow_active_version = NORCOW_VERSION; + norcow_write_sector = 0; + norcow_free_offset = NORCOW_STORAGE_START; } /* * Looks for the given key, returns status of the operation */ -secbool norcow_get(uint16_t key, const void **val, uint16_t *len) -{ - return find_item(norcow_active_sector, key, val, len); +secbool norcow_get(uint16_t key, const void **val, uint16_t *len) { + return find_item(norcow_active_sector, key, val, len); } /* - * Reads the next entry in the storage starting at offset. Returns secfalse if there is none. + * Reads the next entry in the storage starting at offset. Returns secfalse if + * there is none. */ -secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val, uint16_t *len) -{ - if (*offset == 0) { - uint32_t version; - if (sectrue != find_start_offset(norcow_active_sector, offset, &version)) { - return secfalse; - } +secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val, + uint16_t *len) { + if (*offset == 0) { + uint32_t version; + if (sectrue != find_start_offset(norcow_active_sector, offset, &version)) { + return secfalse; + } + } + + for (;;) { + uint32_t pos = 0; + secbool ret = read_item(norcow_active_sector, *offset, key, val, len, &pos); + if (sectrue != ret) { + break; + } + *offset = pos; + + // Skip deleted items. + if (*key == NORCOW_KEY_DELETED) { + continue; } - for (;;) { - uint32_t pos = 0; - secbool ret = read_item(norcow_active_sector, *offset, key, val, len, &pos); + if (norcow_active_version == 0) { + // Check whether the item is the latest instance. + uint32_t offsetr = *offset; + for (;;) { + uint16_t k; + uint16_t l; + const void *v; + ret = read_item(norcow_active_sector, offsetr, &k, &v, &l, &offsetr); if (sectrue != ret) { - break; + // There is no newer instance of the item. + return sectrue; } - *offset = pos; - - // Skip deleted items. - if (*key == NORCOW_KEY_DELETED) { - continue; - } - - if (norcow_active_version == 0) { - // Check whether the item is the latest instance. - uint32_t offsetr = *offset; - for (;;) { - uint16_t k; - uint16_t l; - const void *v; - ret = read_item(norcow_active_sector, offsetr, &k, &v, &l, &offsetr); - if (sectrue != ret) { - // There is no newer instance of the item. - return sectrue; - } - if (*key == k) { - // There exists a newer instance of the item. - break; - } - } - } else { - return sectrue; + if (*key == k) { + // There exists a newer instance of the item. + break; } + } + } else { + return sectrue; } - return secfalse; + } + return secfalse; } /* @@ -400,163 +404,175 @@ secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val, uint1 * as val, then norcow_set allocates a new key of size len. The value should * then be written using norcow_update_bytes(). */ -secbool norcow_set(uint16_t key, const void *val, uint16_t len) -{ - secbool found; - return norcow_set_ex(key, val, len, &found); +secbool norcow_set(uint16_t key, const void *val, uint16_t len) { + secbool found; + return norcow_set_ex(key, val, len, &found); } -secbool norcow_set_ex(uint16_t key, const void *val, uint16_t len, secbool *found) -{ - // Key 0xffff is used as a marker to indicate that the entry is not set. - if (key == NORCOW_KEY_FREE) { - return secfalse; +secbool norcow_set_ex(uint16_t key, const void *val, uint16_t len, + secbool *found) { + // Key 0xffff is used as a marker to indicate that the entry is not set. + if (key == NORCOW_KEY_FREE) { + return secfalse; + } + + const uint8_t sector_num = norcow_sectors[norcow_write_sector]; + secbool ret = secfalse; + const void *ptr = NULL; + uint16_t len_old = 0; + *found = find_item(norcow_write_sector, key, &ptr, &len_old); + + // Try to update the entry if it already exists. + uint32_t offset = 0; + if (sectrue == *found) { + offset = + (const uint8_t *)ptr - + (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE); + if (val != NULL && len_old == len) { + ret = sectrue; + ensure(flash_unlock_write(), NULL); + for (uint16_t i = 0; i < len; i++) { + if (sectrue != flash_write_byte(sector_num, offset + i, + ((const uint8_t *)val)[i])) { + ret = secfalse; + break; + } + } + ensure(flash_lock_write(), NULL); } + } - const uint8_t sector_num = norcow_sectors[norcow_write_sector]; - secbool ret = secfalse; - const void *ptr = NULL; - uint16_t len_old = 0; - *found = find_item(norcow_write_sector, key, &ptr, &len_old); - - // Try to update the entry if it already exists. - uint32_t offset = 0; + // If the update was not possible then write the entry as a new item. + if (secfalse == ret) { + // Delete the old item. if (sectrue == *found) { - offset = (const uint8_t*) ptr - (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE); - if (val != NULL && len_old == len) { - ret = sectrue; - ensure(flash_unlock_write(), NULL); - for (uint16_t i = 0; i < len; i++) { - if (sectrue != flash_write_byte(sector_num, offset + i, ((const uint8_t*)val)[i])) { - ret = secfalse; - break; - } - } - ensure(flash_lock_write(), NULL); - } + ensure(flash_unlock_write(), NULL); + + // Update the prefix to indicate that the old item has been deleted. + uint32_t prefix = (uint32_t)len_old << 16; + ensure(flash_write_word(sector_num, offset - NORCOW_PREFIX_LEN, prefix), + NULL); + + // Delete the old item data. + uint32_t end = offset + len_old; + while (offset < end) { + ensure(flash_write_word(sector_num, offset, 0x00000000), NULL); + offset += NORCOW_WORD_SIZE; + } + + ensure(flash_lock_write(), NULL); } - - // If the update was not possible then write the entry as a new item. - if (secfalse == ret) { - // Delete the old item. - if (sectrue == *found) { - ensure(flash_unlock_write(), NULL); - - // Update the prefix to indicate that the old item has been deleted. - uint32_t prefix = (uint32_t)len_old << 16; - ensure(flash_write_word(sector_num, offset - NORCOW_PREFIX_LEN, prefix), NULL); - - // Delete the old item data. - uint32_t end = offset + len_old; - while (offset < end) { - ensure(flash_write_word(sector_num, offset, 0x00000000), NULL); - offset += NORCOW_WORD_SIZE; - } - - ensure(flash_lock_write(), NULL); - } - // Check whether there is enough free space and compact if full. - if (norcow_free_offset + NORCOW_PREFIX_LEN + len > NORCOW_SECTOR_SIZE) { - compact(); - } - // Write new item. - uint32_t pos; - ret = write_item(norcow_write_sector, norcow_free_offset, key, val, len, &pos); - if (sectrue == ret) { - norcow_free_offset = pos; - } + // Check whether there is enough free space and compact if full. + if (norcow_free_offset + NORCOW_PREFIX_LEN + len > NORCOW_SECTOR_SIZE) { + compact(); } - return ret; + // Write new item. + uint32_t pos; + ret = write_item(norcow_write_sector, norcow_free_offset, key, val, len, + &pos); + if (sectrue == ret) { + norcow_free_offset = pos; + } + } + return ret; } /* * Deletes the given key, returns status of the operation. */ -secbool norcow_delete(uint16_t key) -{ - // Key 0xffff is used as a marker to indicate that the entry is not set. - if (key == NORCOW_KEY_FREE) { - return secfalse; - } +secbool norcow_delete(uint16_t key) { + // Key 0xffff is used as a marker to indicate that the entry is not set. + if (key == NORCOW_KEY_FREE) { + return secfalse; + } - const uint8_t sector_num = norcow_sectors[norcow_write_sector]; - const void *ptr = NULL; - uint16_t len = 0; - if (sectrue != find_item(norcow_write_sector, key, &ptr, &len)) { - return secfalse; - } + const uint8_t sector_num = norcow_sectors[norcow_write_sector]; + const void *ptr = NULL; + uint16_t len = 0; + if (sectrue != find_item(norcow_write_sector, key, &ptr, &len)) { + return secfalse; + } - uint32_t offset = (const uint8_t*) ptr - (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE); + uint32_t offset = + (const uint8_t *)ptr - + (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE); - ensure(flash_unlock_write(), NULL); + ensure(flash_unlock_write(), NULL); - // Update the prefix to indicate that the item has been deleted. - uint32_t prefix = (uint32_t)len << 16; - ensure(flash_write_word(sector_num, offset - NORCOW_PREFIX_LEN, prefix), NULL); + // Update the prefix to indicate that the item has been deleted. + uint32_t prefix = (uint32_t)len << 16; + ensure(flash_write_word(sector_num, offset - NORCOW_PREFIX_LEN, prefix), + NULL); - // Delete the item data. - uint32_t end = offset + len; - while (offset < end) { - ensure(flash_write_word(sector_num, offset, 0x00000000), NULL); - offset += NORCOW_WORD_SIZE; - } + // Delete the item data. + uint32_t end = offset + len; + while (offset < end) { + ensure(flash_write_word(sector_num, offset, 0x00000000), NULL); + offset += NORCOW_WORD_SIZE; + } - ensure(flash_lock_write(), NULL); + ensure(flash_lock_write(), NULL); - return sectrue; + return sectrue; } /* * Update a word in flash at the given pointer. The pointer must point * into the NORCOW area. */ -secbool norcow_update_word(uint16_t key, uint16_t offset, uint32_t value) -{ - const void *ptr; - uint16_t len; - if (sectrue != find_item(norcow_write_sector, key, &ptr, &len)) { - return secfalse; - } - if ((offset & 3) != 0 || offset >= len) { - return secfalse; - } - uint32_t sector_offset = (const uint8_t*) ptr - (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE) + offset; - ensure(flash_unlock_write(), NULL); - ensure(flash_write_word(norcow_sectors[norcow_write_sector], sector_offset, value), NULL); - ensure(flash_lock_write(), NULL); - return sectrue; +secbool norcow_update_word(uint16_t key, uint16_t offset, uint32_t value) { + const void *ptr; + uint16_t len; + if (sectrue != find_item(norcow_write_sector, key, &ptr, &len)) { + return secfalse; + } + if ((offset & 3) != 0 || offset >= len) { + return secfalse; + } + uint32_t sector_offset = + (const uint8_t *)ptr - + (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE) + + offset; + ensure(flash_unlock_write(), NULL); + ensure(flash_write_word(norcow_sectors[norcow_write_sector], sector_offset, + value), + NULL); + ensure(flash_lock_write(), NULL); + return sectrue; } /* * Update the value of the given key starting at the given offset. */ -secbool norcow_update_bytes(const uint16_t key, const uint16_t offset, const uint8_t *data, const uint16_t len) -{ - const void *ptr; - uint16_t allocated_len; - if (sectrue != find_item(norcow_write_sector, key, &ptr, &allocated_len)) { - return secfalse; - } - if (offset + len > allocated_len) { - return secfalse; - } - uint32_t sector_offset = (const uint8_t*) ptr - (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE) + offset; - uint8_t sector = norcow_sectors[norcow_write_sector]; - ensure(flash_unlock_write(), NULL); - for (uint16_t i = 0; i < len; i++, sector_offset++) { - ensure(flash_write_byte(sector, sector_offset, data[i]), NULL); - } - ensure(flash_lock_write(), NULL); - return sectrue; +secbool norcow_update_bytes(const uint16_t key, const uint16_t offset, + const uint8_t *data, const uint16_t len) { + const void *ptr; + uint16_t allocated_len; + if (sectrue != find_item(norcow_write_sector, key, &ptr, &allocated_len)) { + return secfalse; + } + if (offset + len > allocated_len) { + return secfalse; + } + uint32_t sector_offset = + (const uint8_t *)ptr - + (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE) + + offset; + uint8_t sector = norcow_sectors[norcow_write_sector]; + ensure(flash_unlock_write(), NULL); + for (uint16_t i = 0; i < len; i++, sector_offset++) { + ensure(flash_write_byte(sector, sector_offset, data[i]), NULL); + } + ensure(flash_lock_write(), NULL); + return sectrue; } /* * Complete storage version upgrade */ -secbool norcow_upgrade_finish(void) -{ - erase_sector(norcow_active_sector, secfalse); - norcow_active_sector = norcow_write_sector; - norcow_active_version = NORCOW_VERSION; - return sectrue; +secbool norcow_upgrade_finish(void) { + erase_sector(norcow_active_sector, secfalse); + norcow_active_sector = norcow_write_sector; + norcow_active_version = NORCOW_VERSION; + return sectrue; } diff --git a/storage/norcow.h b/storage/norcow.h index 953f6f1d01..c078c992a9 100644 --- a/storage/norcow.h +++ b/storage/norcow.h @@ -45,9 +45,11 @@ void norcow_wipe(void); secbool norcow_get(uint16_t key, const void **val, uint16_t *len); /* - * Reads the next entry in the storage starting at offset. Returns secfalse if there is none. + * Reads the next entry in the storage starting at offset. Returns secfalse if + * there is none. */ -secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val, uint16_t *len); +secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val, + uint16_t *len); /* * Sets the given key, returns status of the operation. If NULL is passed @@ -55,7 +57,8 @@ secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val, uint1 * then be written using norcow_update_bytes(). */ secbool norcow_set(uint16_t key, const void *val, uint16_t len); -secbool norcow_set_ex(uint16_t key, const void *val, uint16_t len, secbool *found); +secbool norcow_set_ex(uint16_t key, const void *val, uint16_t len, + secbool *found); /* * Deletes the given key, returns status of the operation. @@ -72,7 +75,8 @@ secbool norcow_update_word(uint16_t key, uint16_t offset, uint32_t value); * Update the value of the given key starting at the given offset. * Note that you can only change bits from 1 to 0. */ -secbool norcow_update_bytes(const uint16_t key, const uint16_t offset, const uint8_t *data, const uint16_t len); +secbool norcow_update_bytes(const uint16_t key, const uint16_t offset, + const uint8_t *data, const uint16_t len); /* * Complete storage version upgrade diff --git a/storage/storage.c b/storage/storage.c index 655e85c31f..fa6e90ca38 100644 --- a/storage/storage.c +++ b/storage/storage.c @@ -19,105 +19,108 @@ #include -#include "common.h" -#include "norcow.h" -#include "storage.h" -#include "pbkdf2.h" -#include "sha2.h" -#include "hmac.h" -#include "rand.h" -#include "memzero.h" #include "chacha20poly1305/rfc7539.h" +#include "common.h" +#include "hmac.h" +#include "memzero.h" +#include "norcow.h" +#include "pbkdf2.h" +#include "rand.h" +#include "sha2.h" +#include "storage.h" -#define LOW_MASK 0x55555555 +#define LOW_MASK 0x55555555 // The APP namespace which is reserved for storage related values. -#define APP_STORAGE 0x00 +#define APP_STORAGE 0x00 // Norcow storage key of the PIN entry log and PIN success log. -#define PIN_LOGS_KEY ((APP_STORAGE << 8) | 0x01) +#define PIN_LOGS_KEY ((APP_STORAGE << 8) | 0x01) -// Norcow storage key of the combined salt, EDEK, ESAK and PIN verification code entry. -#define EDEK_PVC_KEY ((APP_STORAGE << 8) | 0x02) +// Norcow storage key of the combined salt, EDEK, ESAK and PIN verification code +// entry. +#define EDEK_PVC_KEY ((APP_STORAGE << 8) | 0x02) // Norcow storage key of the PIN set flag. -#define PIN_NOT_SET_KEY ((APP_STORAGE << 8) | 0x03) +#define PIN_NOT_SET_KEY ((APP_STORAGE << 8) | 0x03) // Norcow storage key of the storage version. -#define VERSION_KEY ((APP_STORAGE << 8) | 0x04) +#define VERSION_KEY ((APP_STORAGE << 8) | 0x04) // Norcow storage key of the storage authentication tag. -#define STORAGE_TAG_KEY ((APP_STORAGE << 8) | 0x05) +#define STORAGE_TAG_KEY ((APP_STORAGE << 8) | 0x05) // The PIN value corresponding to an empty PIN. -#define PIN_EMPTY 1 +#define PIN_EMPTY 1 // Maximum number of failed unlock attempts. -// NOTE: The PIN counter logic relies on this constant being less than or equal to 16. -#define PIN_MAX_TRIES 16 +// NOTE: The PIN counter logic relies on this constant being less than or equal +// to 16. +#define PIN_MAX_TRIES 16 // The total number of iterations to use in PBKDF2. -#define PIN_ITER_COUNT 20000 +#define PIN_ITER_COUNT 20000 // The number of seconds required to derive the KEK and KEIV. -#define DERIVE_SECS 1 +#define DERIVE_SECS 1 // If the top bit of APP is set, then the value is not encrypted. -#define FLAG_PUBLIC 0x80 +#define FLAG_PUBLIC 0x80 // If the top two bits of APP are set, then the value is not encrypted and it // can be written even when the storage is locked. -#define FLAGS_WRITE 0xC0 +#define FLAGS_WRITE 0xC0 // The length of the guard key in words. -#define GUARD_KEY_WORDS 1 +#define GUARD_KEY_WORDS 1 // The length of the PIN entry log or the PIN success log in words. -#define PIN_LOG_WORDS 16 +#define PIN_LOG_WORDS 16 // The length of a word in bytes. -#define WORD_SIZE (sizeof(uint32_t)) +#define WORD_SIZE (sizeof(uint32_t)) // The length of the hashed hardware salt in bytes. -#define HARDWARE_SALT_SIZE SHA256_DIGEST_LENGTH +#define HARDWARE_SALT_SIZE SHA256_DIGEST_LENGTH // The length of the random salt in bytes. -#define RANDOM_SALT_SIZE 4 +#define RANDOM_SALT_SIZE 4 // The length of the data encryption key in bytes. -#define DEK_SIZE 32 +#define DEK_SIZE 32 // The length of the storage authentication key in bytes. -#define SAK_SIZE 16 +#define SAK_SIZE 16 -// The combined length of the data encryption key and the storage authentication key in bytes. -#define KEYS_SIZE (DEK_SIZE + SAK_SIZE) +// The combined length of the data encryption key and the storage authentication +// key in bytes. +#define KEYS_SIZE (DEK_SIZE + SAK_SIZE) // The length of the PIN verification code in bytes. -#define PVC_SIZE 8 +#define PVC_SIZE 8 // The length of the storage authentication tag in bytes. -#define STORAGE_TAG_SIZE 16 +#define STORAGE_TAG_SIZE 16 // The length of the Poly1305 authentication tag in bytes. -#define POLY1305_TAG_SIZE 16 +#define POLY1305_TAG_SIZE 16 // The length of the ChaCha20 IV (aka nonce) in bytes as per RFC 7539. -#define CHACHA20_IV_SIZE 12 +#define CHACHA20_IV_SIZE 12 // The length of the ChaCha20 block in bytes. #define CHACHA20_BLOCK_SIZE 64 // The length of the counter tail in words. -#define COUNTER_TAIL_WORDS 2 +#define COUNTER_TAIL_WORDS 2 // Values used in the guard key integrity check. -#define GUARD_KEY_MODULUS 6311 +#define GUARD_KEY_MODULUS 6311 #define GUARD_KEY_REMAINDER 15 -const char* const VERIFYING_PIN_MSG = "Verifying PIN"; -const char* const PROCESSING_MSG = "Processing"; -const char* const STARTING_MSG = "Starting up"; +const char *const VERIFYING_PIN_MSG = "Verifying PIN"; +const char *const PROCESSING_MSG = "Processing"; +const char *const STARTING_MSG = "Starting up"; static secbool initialized = secfalse; static secbool unlocked = secfalse; @@ -134,742 +137,781 @@ static uint32_t norcow_active_version = 0; static const uint8_t TRUE_BYTE = 0x01; static const uint8_t FALSE_BYTE = 0x00; -static void __handle_fault(const char *msg, const char *file, int line, const char *func); +static void __handle_fault(const char *msg, const char *file, int line, + const char *func); #define handle_fault(msg) (__handle_fault(msg, __FILE__, __LINE__, __func__)) static secbool storage_upgrade(void); -static secbool storage_set_encrypted(const uint16_t key, const void *val, const uint16_t len); -static secbool storage_get_encrypted(const uint16_t key, void *val_dest, const uint16_t max_len, uint16_t *len); +static secbool storage_set_encrypted(const uint16_t key, const void *val, + const uint16_t len); +static secbool storage_get_encrypted(const uint16_t key, void *val_dest, + const uint16_t max_len, uint16_t *len); -static secbool secequal(const void* ptr1, const void* ptr2, size_t n) { - const uint8_t* p1 = ptr1; - const uint8_t* p2 = ptr2; - uint8_t diff = 0; - size_t i; - for (i = 0; i < n; ++i) { - diff |= *p1 ^ *p2; - ++p1; - ++p2; - } +static secbool secequal(const void *ptr1, const void *ptr2, size_t n) { + const uint8_t *p1 = ptr1; + const uint8_t *p2 = ptr2; + uint8_t diff = 0; + size_t i; + for (i = 0; i < n; ++i) { + diff |= *p1 ^ *p2; + ++p1; + ++p2; + } - // Check loop completion in case of a fault injection attack. - if (i != n) { - handle_fault("loop completion check"); - } + // Check loop completion in case of a fault injection attack. + if (i != n) { + handle_fault("loop completion check"); + } - return diff ? secfalse : sectrue; + return diff ? secfalse : sectrue; } -static secbool secequal32(const uint32_t* ptr1, const uint32_t* ptr2, size_t n) { - uint32_t diff = 0; - size_t i; - for (i = 0; i < n; ++i) { - uint32_t mask = random32(); - diff |= (*ptr1 + mask - *ptr2) ^ mask; - ++ptr1; - ++ptr2; - } +static secbool secequal32(const uint32_t *ptr1, const uint32_t *ptr2, + size_t n) { + uint32_t diff = 0; + size_t i; + for (i = 0; i < n; ++i) { + uint32_t mask = random32(); + diff |= (*ptr1 + mask - *ptr2) ^ mask; + ++ptr1; + ++ptr2; + } - // Check loop completion in case of a fault injection attack. - if (i != n) { - handle_fault("loop completion check"); - } + // Check loop completion in case of a fault injection attack. + if (i != n) { + handle_fault("loop completion check"); + } - return diff ? secfalse : sectrue; + return diff ? secfalse : sectrue; } static secbool is_protected(uint16_t key) { - const uint8_t app = key >> 8; - return ((app & FLAG_PUBLIC) == 0 && app != APP_STORAGE) ? sectrue : secfalse; + const uint8_t app = key >> 8; + return ((app & FLAG_PUBLIC) == 0 && app != APP_STORAGE) ? sectrue : secfalse; } /* * Initialize the storage authentication tag for freshly wiped storage. */ static secbool auth_init(void) { - uint8_t tag[SHA256_DIGEST_LENGTH]; - memzero(authentication_sum, sizeof(authentication_sum)); - hmac_sha256(cached_sak, SAK_SIZE, authentication_sum, sizeof(authentication_sum), tag); - return norcow_set(STORAGE_TAG_KEY, tag, STORAGE_TAG_SIZE); + uint8_t tag[SHA256_DIGEST_LENGTH]; + memzero(authentication_sum, sizeof(authentication_sum)); + hmac_sha256(cached_sak, SAK_SIZE, authentication_sum, + sizeof(authentication_sum), tag); + return norcow_set(STORAGE_TAG_KEY, tag, STORAGE_TAG_SIZE); } /* * Update the storage authentication tag with the given key. */ static secbool auth_update(uint16_t key) { - if (sectrue != is_protected(key)) { - return sectrue; - } + if (sectrue != is_protected(key)) { + return sectrue; + } - uint8_t tag[SHA256_DIGEST_LENGTH]; - hmac_sha256(cached_sak, SAK_SIZE, (uint8_t*)&key, sizeof(key), tag); - for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH; i++) { - authentication_sum[i] ^= tag[i]; - } - hmac_sha256(cached_sak, SAK_SIZE, authentication_sum, sizeof(authentication_sum), tag); - return norcow_set(STORAGE_TAG_KEY, tag, STORAGE_TAG_SIZE); + uint8_t tag[SHA256_DIGEST_LENGTH]; + hmac_sha256(cached_sak, SAK_SIZE, (uint8_t *)&key, sizeof(key), tag); + for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH; i++) { + authentication_sum[i] ^= tag[i]; + } + hmac_sha256(cached_sak, SAK_SIZE, authentication_sum, + sizeof(authentication_sum), tag); + return norcow_set(STORAGE_TAG_KEY, tag, STORAGE_TAG_SIZE); } /* - * A secure version of norcow_set(), which updates the storage authentication tag. + * A secure version of norcow_set(), which updates the storage authentication + * tag. */ static secbool auth_set(uint16_t key, const void *val, uint16_t len) { - secbool found; - secbool ret = norcow_set_ex(key, val, len, &found); - if (sectrue == ret && secfalse == found) { - ret = auth_update(key); - if (sectrue != ret) { - norcow_delete(key); - } + secbool found; + secbool ret = norcow_set_ex(key, val, len, &found); + if (sectrue == ret && secfalse == found) { + ret = auth_update(key); + if (sectrue != ret) { + norcow_delete(key); } - return ret; + } + return ret; } /* - * A secure version of norcow_get(), which checks the storage authentication tag. + * A secure version of norcow_get(), which checks the storage authentication + * tag. */ -static secbool auth_get(uint16_t key, const void **val, uint16_t *len) -{ - *val = NULL; - *len = 0; - uint32_t sum[SHA256_DIGEST_LENGTH/sizeof(uint32_t)] = {0}; +static secbool auth_get(uint16_t key, const void **val, uint16_t *len) { + *val = NULL; + *len = 0; + uint32_t sum[SHA256_DIGEST_LENGTH / sizeof(uint32_t)] = {0}; - // Prepare inner and outer digest. - uint32_t odig[SHA256_DIGEST_LENGTH / sizeof(uint32_t)]; - uint32_t idig[SHA256_DIGEST_LENGTH / sizeof(uint32_t)]; - hmac_sha256_prepare(cached_sak, SAK_SIZE, odig, idig); + // Prepare inner and outer digest. + uint32_t odig[SHA256_DIGEST_LENGTH / sizeof(uint32_t)]; + uint32_t idig[SHA256_DIGEST_LENGTH / sizeof(uint32_t)]; + hmac_sha256_prepare(cached_sak, SAK_SIZE, odig, idig); - // Prepare SHA-256 message padding. - uint32_t g[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0}; - uint32_t h[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0}; - g[15] = (SHA256_BLOCK_LENGTH + 2) * 8; - h[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH) * 8; - h[8] = 0x80000000; + // Prepare SHA-256 message padding. + uint32_t g[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0}; + uint32_t h[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0}; + g[15] = (SHA256_BLOCK_LENGTH + 2) * 8; + h[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH) * 8; + h[8] = 0x80000000; - uint32_t offset = 0; - uint16_t k = 0; - uint16_t l = 0; - uint16_t tag_len = 0; - uint16_t entry_count = 0; // Mitigation against fault injection. - uint16_t other_count = 0; // Mitigation against fault injection. - const void *v = NULL; - const void *tag_val = NULL; - while (sectrue == norcow_get_next(&offset, &k, &v, &l)) { - ++entry_count; - if (k == key) { - *val = v; - *len = l; - } else { - ++other_count; - } - if (sectrue != is_protected(k)) { - if (k == STORAGE_TAG_KEY) { - tag_val = v; - tag_len = l; - } - continue; - } - g[0] = (((uint32_t)k & 0xff) << 24) | (((uint32_t)k & 0xff00) << 8) | 0x8000; // Add SHA message padding. - sha256_Transform(idig, g, h); - sha256_Transform(odig, h, h); - for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH/sizeof(uint32_t); i++) { - sum[i] ^= h[i]; - } + uint32_t offset = 0; + uint16_t k = 0; + uint16_t l = 0; + uint16_t tag_len = 0; + uint16_t entry_count = 0; // Mitigation against fault injection. + uint16_t other_count = 0; // Mitigation against fault injection. + const void *v = NULL; + const void *tag_val = NULL; + while (sectrue == norcow_get_next(&offset, &k, &v, &l)) { + ++entry_count; + if (k == key) { + *val = v; + *len = l; + } else { + ++other_count; } - memcpy(h, sum, sizeof(sum)); - - sha256_Transform(idig, h, h); + if (sectrue != is_protected(k)) { + if (k == STORAGE_TAG_KEY) { + tag_val = v; + tag_len = l; + } + continue; + } + g[0] = (((uint32_t)k & 0xff) << 24) | (((uint32_t)k & 0xff00) << 8) | + 0x8000; // Add SHA message padding. + sha256_Transform(idig, g, h); sha256_Transform(odig, h, h); + for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) { + sum[i] ^= h[i]; + } + } + memcpy(h, sum, sizeof(sum)); - memzero(odig, sizeof(odig)); - memzero(idig, sizeof(idig)); + sha256_Transform(idig, h, h); + sha256_Transform(odig, h, h); - // Cache the authentication sum. - for (size_t i = 0; i < SHA256_DIGEST_LENGTH/sizeof(uint32_t); i++) { + memzero(odig, sizeof(odig)); + memzero(idig, sizeof(idig)); + + // Cache the authentication sum. + for (size_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) { #if BYTE_ORDER == LITTLE_ENDIAN - REVERSE32(sum[i], ((uint32_t*)authentication_sum)[i]); + REVERSE32(sum[i], ((uint32_t *)authentication_sum)[i]); #else - ((uint32_t*)authentication_sum)[i] = sum[i]; + ((uint32_t *)authentication_sum)[i] = sum[i]; #endif - } + } - // Check loop completion in case of a fault injection attack. - if (secfalse != norcow_get_next(&offset, &k, &v, &l)) { - handle_fault("loop completion check"); - } + // Check loop completion in case of a fault injection attack. + if (secfalse != norcow_get_next(&offset, &k, &v, &l)) { + handle_fault("loop completion check"); + } - // Check storage authentication tag. + // Check storage authentication tag. #if BYTE_ORDER == LITTLE_ENDIAN - for (size_t i = 0; i < SHA256_DIGEST_LENGTH/sizeof(uint32_t); i++) { - REVERSE32(h[i], h[i]); - } + for (size_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) { + REVERSE32(h[i], h[i]); + } #endif - if (tag_val == NULL || tag_len != STORAGE_TAG_SIZE || sectrue != secequal(h, tag_val, STORAGE_TAG_SIZE)) { - handle_fault("storage tag check"); + if (tag_val == NULL || tag_len != STORAGE_TAG_SIZE || + sectrue != secequal(h, tag_val, STORAGE_TAG_SIZE)) { + handle_fault("storage tag check"); + } + + if (*val == NULL) { + // Check for fault injection. + if (other_count != entry_count) { + handle_fault("sanity check"); } - - if (*val == NULL) { - // Check for fault injection. - if (other_count != entry_count) { - handle_fault("sanity check"); - } - return secfalse; - } - return sectrue; -} - -/* - * Generates a delay of random length. Use this to protect sensitive code against fault injection. - */ -static void wait_random(void) -{ -#ifndef TREZOR_STORAGE_TEST - int wait = random32() & 0xff; - volatile int i = 0; - volatile int j = wait; - while (i < wait) { - if (i + j != wait) { - handle_fault("sanity check"); - } - ++i; - --j; - } - - // Double-check loop completion. - if (i != wait) { - handle_fault("loop completion check"); - } -#endif -} - -static void derive_kek(uint32_t pin, const uint8_t *random_salt, uint8_t kek[SHA256_DIGEST_LENGTH], uint8_t keiv[SHA256_DIGEST_LENGTH]) -{ -#if BYTE_ORDER == BIG_ENDIAN - REVERSE32(pin, pin); -#endif - - uint8_t salt[HARDWARE_SALT_SIZE + RANDOM_SALT_SIZE]; - memcpy(salt, hardware_salt, HARDWARE_SALT_SIZE); - memcpy(salt + HARDWARE_SALT_SIZE, random_salt, RANDOM_SALT_SIZE); - - uint32_t progress = (ui_total - ui_rem) * 1000 / ui_total; - if (ui_callback && ui_message) { - ui_callback(ui_rem, progress, ui_message); - } - - PBKDF2_HMAC_SHA256_CTX ctx; - pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t*) &pin, sizeof(pin), salt, sizeof(salt), 1); - for (int i = 1; i <= 5; i++) { - pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10); - if (ui_callback && ui_message) { - progress = ((ui_total - ui_rem) * 1000 + i * DERIVE_SECS * 100) / ui_total; - ui_callback(ui_rem - i * DERIVE_SECS / 10, progress, ui_message); - } - } - pbkdf2_hmac_sha256_Final(&ctx, kek); - - pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t*) &pin, sizeof(pin), salt, sizeof(salt), 2); - for (int i = 6; i <= 10; i++) { - pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10); - if (ui_callback && ui_message) { - progress = ((ui_total - ui_rem) * 1000 + i * DERIVE_SECS * 100) / ui_total; - ui_callback(ui_rem - i * DERIVE_SECS / 10, progress, ui_message); - } - } - pbkdf2_hmac_sha256_Final(&ctx, keiv); - - ui_rem -= DERIVE_SECS; - memzero(&ctx, sizeof(PBKDF2_HMAC_SHA256_CTX)); - memzero(&pin, sizeof(pin)); - memzero(&salt, sizeof(salt)); -} - -static secbool set_pin(uint32_t pin) -{ - uint8_t buffer[RANDOM_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE]; - uint8_t *salt = buffer; - uint8_t *ekeys = buffer + RANDOM_SALT_SIZE; - uint8_t *pvc = buffer + RANDOM_SALT_SIZE + KEYS_SIZE; - - uint8_t kek[SHA256_DIGEST_LENGTH]; - uint8_t keiv[SHA256_DIGEST_LENGTH]; - chacha20poly1305_ctx ctx; - random_buffer(salt, RANDOM_SALT_SIZE); - derive_kek(pin, salt, kek, keiv); - rfc7539_init(&ctx, kek, keiv); - memzero(kek, sizeof(kek)); - memzero(keiv, sizeof(keiv)); - chacha20poly1305_encrypt(&ctx, cached_keys, ekeys, KEYS_SIZE); - rfc7539_finish(&ctx, 0, KEYS_SIZE, pvc); - memzero(&ctx, sizeof(ctx)); - secbool ret = norcow_set(EDEK_PVC_KEY, buffer, RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE); - memzero(buffer, sizeof(buffer)); - - if (ret == sectrue) - { - if (pin == PIN_EMPTY) { - ret = norcow_set(PIN_NOT_SET_KEY, &TRUE_BYTE, sizeof(TRUE_BYTE)); - } else { - ret = norcow_set(PIN_NOT_SET_KEY, &FALSE_BYTE, sizeof(FALSE_BYTE)); - } - } - - memzero(&pin, sizeof(pin)); - return ret; -} - -static secbool check_guard_key(const uint32_t guard_key) -{ - if (guard_key % GUARD_KEY_MODULUS != GUARD_KEY_REMAINDER) { - return secfalse; - } - - // Check that each byte of (guard_key & 0xAAAAAAAA) has exactly two bits set. - uint32_t count = (guard_key & 0x22222222) + ((guard_key >> 2) & 0x22222222); - count = count + (count >> 4); - if ((count & 0x0e0e0e0e) != 0x04040404) { - return secfalse; - } - - // Check that the guard_key does not contain a run of 5 (or more) zeros or ones. - uint32_t zero_runs = ~guard_key; - zero_runs = zero_runs & (zero_runs >> 2); - zero_runs = zero_runs & (zero_runs >> 1); - zero_runs = zero_runs & (zero_runs >> 1); - - uint32_t one_runs = guard_key; - one_runs = one_runs & (one_runs >> 2); - one_runs = one_runs & (one_runs >> 1); - one_runs = one_runs & (one_runs >> 1); - - if ((one_runs != 0) || (zero_runs != 0)) { - return secfalse; - } - - return sectrue; -} - -static uint32_t generate_guard_key(void) -{ - uint32_t guard_key = 0; - do { - guard_key = random_uniform((UINT32_MAX/GUARD_KEY_MODULUS) + 1) * GUARD_KEY_MODULUS + GUARD_KEY_REMAINDER; - } while (sectrue != check_guard_key(guard_key)); - return guard_key; -} - -static secbool expand_guard_key(const uint32_t guard_key, uint32_t *guard_mask, uint32_t *guard) -{ - if (sectrue != check_guard_key(guard_key)) { - handle_fault("guard key check"); - return secfalse; - } - *guard_mask = ((guard_key & LOW_MASK) << 1) | ((~guard_key) & LOW_MASK); - *guard = (((guard_key & LOW_MASK) << 1) & guard_key) | (((~guard_key) & LOW_MASK) & (guard_key >> 1)); - return sectrue; -} - -static secbool pin_logs_init(uint32_t fails) -{ - if (fails >= PIN_MAX_TRIES) { - return secfalse; - } - - // The format of the PIN_LOGS_KEY entry is: - // guard_key (1 word), pin_success_log (PIN_LOG_WORDS), pin_entry_log (PIN_LOG_WORDS) - uint32_t logs[GUARD_KEY_WORDS + 2*PIN_LOG_WORDS]; - - logs[0] = generate_guard_key(); - - uint32_t guard_mask; - uint32_t guard; - wait_random(); - if (sectrue != expand_guard_key(logs[0], &guard_mask, &guard)) { - return secfalse; - } - - uint32_t unused = guard | ~guard_mask; - for (size_t i = 0; i < 2*PIN_LOG_WORDS; ++i) { - logs[GUARD_KEY_WORDS + i] = unused; - } - - // Set the first word of the PIN entry log to indicate the requested number of fails. - logs[GUARD_KEY_WORDS + PIN_LOG_WORDS] = ((((uint32_t)0xFFFFFFFF) >> (2*fails)) & ~guard_mask) | guard; - - return norcow_set(PIN_LOGS_KEY, logs, sizeof(logs)); -} - -/* - * Initializes the values of VERSION_KEY, EDEK_PVC_KEY, PIN_NOT_SET_KEY and PIN_LOGS_KEY using an empty PIN. - * This function should be called to initialize freshly wiped storage. - */ -static void init_wiped_storage(void) -{ - if (sectrue != initialized) { - // We cannot initialize the storage contents if the hardware_salt is not set. - return; - } - random_buffer(cached_keys, sizeof(cached_keys)); - uint32_t version = NORCOW_VERSION; - ensure(auth_init(), "set_storage_auth_tag failed"); - ensure(storage_set_encrypted(VERSION_KEY, &version, sizeof(version)), "set_storage_version failed"); - ensure(pin_logs_init(0), "init_pin_logs failed"); - ui_total = DERIVE_SECS; - ui_rem = ui_total; - ui_message = PROCESSING_MSG; - ensure(set_pin(PIN_EMPTY), "init_pin failed"); - if (unlocked != sectrue) { - memzero(cached_keys, sizeof(cached_keys)); - } -} - -void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt, const uint16_t salt_len) -{ - initialized = secfalse; - unlocked = secfalse; - norcow_init(&norcow_active_version); - initialized = sectrue; - ui_callback = callback; - - sha256_Raw(salt, salt_len, hardware_salt); - - if (norcow_active_version < NORCOW_VERSION) { - if (sectrue != storage_upgrade()) { - storage_wipe(); - ensure(secfalse, "storage_upgrade failed"); - } - } - - // If there is no EDEK, then generate a random DEK and SAK and store them. - const void *val; - uint16_t len; - if (secfalse == norcow_get(EDEK_PVC_KEY, &val, &len)) { - init_wiped_storage(); - } - memzero(cached_keys, sizeof(cached_keys)); -} - -static secbool pin_fails_reset(void) -{ - const void *logs = NULL; - uint16_t len = 0; - - if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || len != WORD_SIZE*(GUARD_KEY_WORDS + 2*PIN_LOG_WORDS)) { - return secfalse; - } - - uint32_t guard_mask; - uint32_t guard; - wait_random(); - if (sectrue != expand_guard_key(*(const uint32_t*)logs, &guard_mask, &guard)) { - return secfalse; - } - - uint32_t unused = guard | ~guard_mask; - const uint32_t *success_log = ((const uint32_t*)logs) + GUARD_KEY_WORDS; - const uint32_t *entry_log = success_log + PIN_LOG_WORDS; - for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { - if (entry_log[i] == unused) { - return sectrue; - } - if (success_log[i] != guard) { - if (sectrue != norcow_update_word(PIN_LOGS_KEY, sizeof(uint32_t)*(i + GUARD_KEY_WORDS), entry_log[i])) { - return secfalse; - } - } - } - return pin_logs_init(0); -} - -secbool storage_pin_fails_increase(void) -{ - if (sectrue != initialized) { - return secfalse; - } - - const void *logs = NULL; - uint16_t len = 0; - - wait_random(); - if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || len != WORD_SIZE*(GUARD_KEY_WORDS + 2*PIN_LOG_WORDS)) { - handle_fault("no PIN logs"); - return secfalse; - } - - uint32_t guard_mask; - uint32_t guard; - wait_random(); - if (sectrue != expand_guard_key(*(const uint32_t*)logs, &guard_mask, &guard)) { - handle_fault("guard key expansion"); - return secfalse; - } - - const uint32_t *entry_log = ((const uint32_t*)logs) + GUARD_KEY_WORDS + PIN_LOG_WORDS; - for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { - wait_random(); - if ((entry_log[i] & guard_mask) != guard) { - handle_fault("guard bits check"); - return secfalse; - } - if (entry_log[i] != guard) { - wait_random(); - uint32_t word = entry_log[i] & ~guard_mask; - word = ((word >> 1) | word) & LOW_MASK; - word = (word >> 2) | (word >> 1); - - wait_random(); - if (sectrue != norcow_update_word(PIN_LOGS_KEY, sizeof(uint32_t)*(i + GUARD_KEY_WORDS + PIN_LOG_WORDS), (word & ~guard_mask) | guard)) { - handle_fault("PIN logs update"); - return secfalse; - } - return sectrue; - } - - } - handle_fault("PIN log exhausted"); return secfalse; + } + return sectrue; } -static uint32_t hamming_weight(uint32_t value) -{ - value = value - ((value >> 1) & 0x55555555); - value = (value & 0x33333333) + ((value >> 2) & 0x33333333); - value = (value + (value >> 4)) & 0x0F0F0F0F; - value = value + (value >> 8); - value = value + (value >> 16); - return value & 0x3F; +/* + * Generates a delay of random length. Use this to protect sensitive code + * against fault injection. + */ +static void wait_random(void) { +#ifndef TREZOR_STORAGE_TEST + int wait = random32() & 0xff; + volatile int i = 0; + volatile int j = wait; + while (i < wait) { + if (i + j != wait) { + handle_fault("sanity check"); + } + ++i; + --j; + } + + // Double-check loop completion. + if (i != wait) { + handle_fault("loop completion check"); + } +#endif } -static secbool pin_get_fails(uint32_t *ctr) -{ - *ctr = PIN_MAX_TRIES; +static void derive_kek(uint32_t pin, const uint8_t *random_salt, + uint8_t kek[SHA256_DIGEST_LENGTH], + uint8_t keiv[SHA256_DIGEST_LENGTH]) { +#if BYTE_ORDER == BIG_ENDIAN + REVERSE32(pin, pin); +#endif - const void *logs = NULL; - uint16_t len = 0; - wait_random(); - if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || len != WORD_SIZE*(GUARD_KEY_WORDS + 2*PIN_LOG_WORDS)) { - handle_fault("no PIN logs"); + uint8_t salt[HARDWARE_SALT_SIZE + RANDOM_SALT_SIZE]; + memcpy(salt, hardware_salt, HARDWARE_SALT_SIZE); + memcpy(salt + HARDWARE_SALT_SIZE, random_salt, RANDOM_SALT_SIZE); + + uint32_t progress = (ui_total - ui_rem) * 1000 / ui_total; + if (ui_callback && ui_message) { + ui_callback(ui_rem, progress, ui_message); + } + + PBKDF2_HMAC_SHA256_CTX ctx; + pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt, + sizeof(salt), 1); + for (int i = 1; i <= 5; i++) { + pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10); + if (ui_callback && ui_message) { + progress = + ((ui_total - ui_rem) * 1000 + i * DERIVE_SECS * 100) / ui_total; + ui_callback(ui_rem - i * DERIVE_SECS / 10, progress, ui_message); + } + } + pbkdf2_hmac_sha256_Final(&ctx, kek); + + pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt, + sizeof(salt), 2); + for (int i = 6; i <= 10; i++) { + pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10); + if (ui_callback && ui_message) { + progress = + ((ui_total - ui_rem) * 1000 + i * DERIVE_SECS * 100) / ui_total; + ui_callback(ui_rem - i * DERIVE_SECS / 10, progress, ui_message); + } + } + pbkdf2_hmac_sha256_Final(&ctx, keiv); + + ui_rem -= DERIVE_SECS; + memzero(&ctx, sizeof(PBKDF2_HMAC_SHA256_CTX)); + memzero(&pin, sizeof(pin)); + memzero(&salt, sizeof(salt)); +} + +static secbool set_pin(uint32_t pin) { + uint8_t buffer[RANDOM_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE]; + uint8_t *salt = buffer; + uint8_t *ekeys = buffer + RANDOM_SALT_SIZE; + uint8_t *pvc = buffer + RANDOM_SALT_SIZE + KEYS_SIZE; + + uint8_t kek[SHA256_DIGEST_LENGTH]; + uint8_t keiv[SHA256_DIGEST_LENGTH]; + chacha20poly1305_ctx ctx; + random_buffer(salt, RANDOM_SALT_SIZE); + derive_kek(pin, salt, kek, keiv); + rfc7539_init(&ctx, kek, keiv); + memzero(kek, sizeof(kek)); + memzero(keiv, sizeof(keiv)); + chacha20poly1305_encrypt(&ctx, cached_keys, ekeys, KEYS_SIZE); + rfc7539_finish(&ctx, 0, KEYS_SIZE, pvc); + memzero(&ctx, sizeof(ctx)); + secbool ret = + norcow_set(EDEK_PVC_KEY, buffer, RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE); + memzero(buffer, sizeof(buffer)); + + if (ret == sectrue) { + if (pin == PIN_EMPTY) { + ret = norcow_set(PIN_NOT_SET_KEY, &TRUE_BYTE, sizeof(TRUE_BYTE)); + } else { + ret = norcow_set(PIN_NOT_SET_KEY, &FALSE_BYTE, sizeof(FALSE_BYTE)); + } + } + + memzero(&pin, sizeof(pin)); + return ret; +} + +static secbool check_guard_key(const uint32_t guard_key) { + if (guard_key % GUARD_KEY_MODULUS != GUARD_KEY_REMAINDER) { + return secfalse; + } + + // Check that each byte of (guard_key & 0xAAAAAAAA) has exactly two bits set. + uint32_t count = (guard_key & 0x22222222) + ((guard_key >> 2) & 0x22222222); + count = count + (count >> 4); + if ((count & 0x0e0e0e0e) != 0x04040404) { + return secfalse; + } + + // Check that the guard_key does not contain a run of 5 (or more) zeros or + // ones. + uint32_t zero_runs = ~guard_key; + zero_runs = zero_runs & (zero_runs >> 2); + zero_runs = zero_runs & (zero_runs >> 1); + zero_runs = zero_runs & (zero_runs >> 1); + + uint32_t one_runs = guard_key; + one_runs = one_runs & (one_runs >> 2); + one_runs = one_runs & (one_runs >> 1); + one_runs = one_runs & (one_runs >> 1); + + if ((one_runs != 0) || (zero_runs != 0)) { + return secfalse; + } + + return sectrue; +} + +static uint32_t generate_guard_key(void) { + uint32_t guard_key = 0; + do { + guard_key = random_uniform((UINT32_MAX / GUARD_KEY_MODULUS) + 1) * + GUARD_KEY_MODULUS + + GUARD_KEY_REMAINDER; + } while (sectrue != check_guard_key(guard_key)); + return guard_key; +} + +static secbool expand_guard_key(const uint32_t guard_key, uint32_t *guard_mask, + uint32_t *guard) { + if (sectrue != check_guard_key(guard_key)) { + handle_fault("guard key check"); + return secfalse; + } + *guard_mask = ((guard_key & LOW_MASK) << 1) | ((~guard_key) & LOW_MASK); + *guard = (((guard_key & LOW_MASK) << 1) & guard_key) | + (((~guard_key) & LOW_MASK) & (guard_key >> 1)); + return sectrue; +} + +static secbool pin_logs_init(uint32_t fails) { + if (fails >= PIN_MAX_TRIES) { + return secfalse; + } + + // The format of the PIN_LOGS_KEY entry is: + // guard_key (1 word), pin_success_log (PIN_LOG_WORDS), pin_entry_log + // (PIN_LOG_WORDS) + uint32_t logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS]; + + logs[0] = generate_guard_key(); + + uint32_t guard_mask; + uint32_t guard; + wait_random(); + if (sectrue != expand_guard_key(logs[0], &guard_mask, &guard)) { + return secfalse; + } + + uint32_t unused = guard | ~guard_mask; + for (size_t i = 0; i < 2 * PIN_LOG_WORDS; ++i) { + logs[GUARD_KEY_WORDS + i] = unused; + } + + // Set the first word of the PIN entry log to indicate the requested number of + // fails. + logs[GUARD_KEY_WORDS + PIN_LOG_WORDS] = + ((((uint32_t)0xFFFFFFFF) >> (2 * fails)) & ~guard_mask) | guard; + + return norcow_set(PIN_LOGS_KEY, logs, sizeof(logs)); +} + +/* + * Initializes the values of VERSION_KEY, EDEK_PVC_KEY, PIN_NOT_SET_KEY and + * PIN_LOGS_KEY using an empty PIN. This function should be called to initialize + * freshly wiped storage. + */ +static void init_wiped_storage(void) { + if (sectrue != initialized) { + // We cannot initialize the storage contents if the hardware_salt is not + // set. + return; + } + random_buffer(cached_keys, sizeof(cached_keys)); + uint32_t version = NORCOW_VERSION; + ensure(auth_init(), "set_storage_auth_tag failed"); + ensure(storage_set_encrypted(VERSION_KEY, &version, sizeof(version)), + "set_storage_version failed"); + ensure(pin_logs_init(0), "init_pin_logs failed"); + ui_total = DERIVE_SECS; + ui_rem = ui_total; + ui_message = PROCESSING_MSG; + ensure(set_pin(PIN_EMPTY), "init_pin failed"); + if (unlocked != sectrue) { + memzero(cached_keys, sizeof(cached_keys)); + } +} + +void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt, + const uint16_t salt_len) { + initialized = secfalse; + unlocked = secfalse; + norcow_init(&norcow_active_version); + initialized = sectrue; + ui_callback = callback; + + sha256_Raw(salt, salt_len, hardware_salt); + + if (norcow_active_version < NORCOW_VERSION) { + if (sectrue != storage_upgrade()) { + storage_wipe(); + ensure(secfalse, "storage_upgrade failed"); + } + } + + // If there is no EDEK, then generate a random DEK and SAK and store them. + const void *val; + uint16_t len; + if (secfalse == norcow_get(EDEK_PVC_KEY, &val, &len)) { + init_wiped_storage(); + } + memzero(cached_keys, sizeof(cached_keys)); +} + +static secbool pin_fails_reset(void) { + const void *logs = NULL; + uint16_t len = 0; + + if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || + len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { + return secfalse; + } + + uint32_t guard_mask; + uint32_t guard; + wait_random(); + if (sectrue != + expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { + return secfalse; + } + + uint32_t unused = guard | ~guard_mask; + const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS; + const uint32_t *entry_log = success_log + PIN_LOG_WORDS; + for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { + if (entry_log[i] == unused) { + return sectrue; + } + if (success_log[i] != guard) { + if (sectrue != norcow_update_word( + PIN_LOGS_KEY, sizeof(uint32_t) * (i + GUARD_KEY_WORDS), + entry_log[i])) { return secfalse; + } } + } + return pin_logs_init(0); +} - uint32_t guard_mask; - uint32_t guard; +secbool storage_pin_fails_increase(void) { + if (sectrue != initialized) { + return secfalse; + } + + const void *logs = NULL; + uint16_t len = 0; + + wait_random(); + if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || + len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { + handle_fault("no PIN logs"); + return secfalse; + } + + uint32_t guard_mask; + uint32_t guard; + wait_random(); + if (sectrue != + expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { + handle_fault("guard key expansion"); + return secfalse; + } + + const uint32_t *entry_log = + ((const uint32_t *)logs) + GUARD_KEY_WORDS + PIN_LOG_WORDS; + for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { wait_random(); - if (sectrue != expand_guard_key(*(const uint32_t*)logs, &guard_mask, &guard)) { - handle_fault("guard key expansion"); + if ((entry_log[i] & guard_mask) != guard) { + handle_fault("guard bits check"); + return secfalse; + } + if (entry_log[i] != guard) { + wait_random(); + uint32_t word = entry_log[i] & ~guard_mask; + word = ((word >> 1) | word) & LOW_MASK; + word = (word >> 2) | (word >> 1); + + wait_random(); + if (sectrue != + norcow_update_word( + PIN_LOGS_KEY, + sizeof(uint32_t) * (i + GUARD_KEY_WORDS + PIN_LOG_WORDS), + (word & ~guard_mask) | guard)) { + handle_fault("PIN logs update"); return secfalse; + } + return sectrue; } - const uint32_t unused = guard | ~guard_mask; + } + handle_fault("PIN log exhausted"); + return secfalse; +} - const uint32_t *success_log = ((const uint32_t*)logs) + GUARD_KEY_WORDS; - const uint32_t *entry_log = success_log + PIN_LOG_WORDS; - volatile int current = -1; - volatile size_t i; - for (i = 0; i < PIN_LOG_WORDS; ++i) { - if ((entry_log[i] & guard_mask) != guard || (success_log[i] & guard_mask) != guard || (entry_log[i] & success_log[i]) != entry_log[i]) { - handle_fault("PIN logs format check"); - return secfalse; - } +static uint32_t hamming_weight(uint32_t value) { + value = value - ((value >> 1) & 0x55555555); + value = (value & 0x33333333) + ((value >> 2) & 0x33333333); + value = (value + (value >> 4)) & 0x0F0F0F0F; + value = value + (value >> 8); + value = value + (value >> 16); + return value & 0x3F; +} - if (current == -1) { - if (entry_log[i] != guard) { - current = i; - } - } else { - if (entry_log[i] != unused) { - handle_fault("PIN entry log format check"); - return secfalse; - } - } +static secbool pin_get_fails(uint32_t *ctr) { + *ctr = PIN_MAX_TRIES; + + const void *logs = NULL; + uint16_t len = 0; + wait_random(); + if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || + len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { + handle_fault("no PIN logs"); + return secfalse; + } + + uint32_t guard_mask; + uint32_t guard; + wait_random(); + if (sectrue != + expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { + handle_fault("guard key expansion"); + return secfalse; + } + const uint32_t unused = guard | ~guard_mask; + + const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS; + const uint32_t *entry_log = success_log + PIN_LOG_WORDS; + volatile int current = -1; + volatile size_t i; + for (i = 0; i < PIN_LOG_WORDS; ++i) { + if ((entry_log[i] & guard_mask) != guard || + (success_log[i] & guard_mask) != guard || + (entry_log[i] & success_log[i]) != entry_log[i]) { + handle_fault("PIN logs format check"); + return secfalse; } - if (current < 0 || current >= PIN_LOG_WORDS || i != PIN_LOG_WORDS) { - handle_fault("PIN log exhausted"); - return secfalse; - } - - // Strip the guard bits from the current entry word and duplicate each data bit. - wait_random(); - uint32_t word = entry_log[current] & ~guard_mask; - word = ((word >> 1) | word ) & LOW_MASK; - word = word | (word << 1); - // Verify that the entry word has form 0*1*. - if ((word & (word + 1)) != 0) { + if (current == -1) { + if (entry_log[i] != guard) { + current = i; + } + } else { + if (entry_log[i] != unused) { handle_fault("PIN entry log format check"); return secfalse; + } } + } - if (current == 0) { - ++current; - } + if (current < 0 || current >= PIN_LOG_WORDS || i != PIN_LOG_WORDS) { + handle_fault("PIN log exhausted"); + return secfalse; + } - // Count the number of set bits in the two current words of the success log. - wait_random(); - *ctr = hamming_weight(success_log[current-1] ^ entry_log[current-1]) + hamming_weight(success_log[current] ^ entry_log[current]); - return sectrue; + // Strip the guard bits from the current entry word and duplicate each data + // bit. + wait_random(); + uint32_t word = entry_log[current] & ~guard_mask; + word = ((word >> 1) | word) & LOW_MASK; + word = word | (word << 1); + // Verify that the entry word has form 0*1*. + if ((word & (word + 1)) != 0) { + handle_fault("PIN entry log format check"); + return secfalse; + } + + if (current == 0) { + ++current; + } + + // Count the number of set bits in the two current words of the success log. + wait_random(); + *ctr = hamming_weight(success_log[current - 1] ^ entry_log[current - 1]) + + hamming_weight(success_log[current] ^ entry_log[current]); + return sectrue; } -secbool storage_is_unlocked(void) -{ - if (sectrue != initialized) { - return secfalse; - } +secbool storage_is_unlocked(void) { + if (sectrue != initialized) { + return secfalse; + } - return unlocked; + return unlocked; } -void storage_lock(void) -{ - unlocked = secfalse; - memzero(cached_keys, sizeof(cached_keys)); - memzero(authentication_sum, sizeof(authentication_sum)); +void storage_lock(void) { + unlocked = secfalse; + memzero(cached_keys, sizeof(cached_keys)); + memzero(authentication_sum, sizeof(authentication_sum)); } -static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) -{ - const void *buffer = NULL; - uint16_t len = 0; - if (sectrue != initialized || sectrue != norcow_get(EDEK_PVC_KEY, &buffer, &len) || len != RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE) { - handle_fault("no EDEK"); - return secfalse; - } +static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) { + const void *buffer = NULL; + uint16_t len = 0; + if (sectrue != initialized || + sectrue != norcow_get(EDEK_PVC_KEY, &buffer, &len) || + len != RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE) { + handle_fault("no EDEK"); + return secfalse; + } - const uint8_t *ekeys = (const uint8_t*) buffer + RANDOM_SALT_SIZE; - const uint32_t *pvc = (const uint32_t*) buffer + (RANDOM_SALT_SIZE + KEYS_SIZE)/sizeof(uint32_t); - _Static_assert(((RANDOM_SALT_SIZE + KEYS_SIZE) & 3) == 0, "PVC unaligned"); - _Static_assert((PVC_SIZE & 3) == 0, "PVC size unaligned"); + const uint8_t *ekeys = (const uint8_t *)buffer + RANDOM_SALT_SIZE; + const uint32_t *pvc = (const uint32_t *)buffer + + (RANDOM_SALT_SIZE + KEYS_SIZE) / sizeof(uint32_t); + _Static_assert(((RANDOM_SALT_SIZE + KEYS_SIZE) & 3) == 0, "PVC unaligned"); + _Static_assert((PVC_SIZE & 3) == 0, "PVC size unaligned"); - uint8_t keys[KEYS_SIZE]; - uint8_t tag[POLY1305_TAG_SIZE] __attribute__((aligned(sizeof(uint32_t)))); - chacha20poly1305_ctx ctx; + uint8_t keys[KEYS_SIZE]; + uint8_t tag[POLY1305_TAG_SIZE] __attribute__((aligned(sizeof(uint32_t)))); + chacha20poly1305_ctx ctx; - // Decrypt the data encryption key and the storage authentication key and check the PIN verification code. - rfc7539_init(&ctx, kek, keiv); - chacha20poly1305_decrypt(&ctx, ekeys, keys, KEYS_SIZE); - rfc7539_finish(&ctx, 0, KEYS_SIZE, tag); - memzero(&ctx, sizeof(ctx)); - wait_random(); - if (secequal32((const uint32_t*) tag, pvc, PVC_SIZE/sizeof(uint32_t)) != sectrue) { - memzero(keys, sizeof(keys)); - memzero(tag, sizeof(tag)); - return secfalse; - } - memcpy(cached_keys, keys, sizeof(keys)); + // Decrypt the data encryption key and the storage authentication key and + // check the PIN verification code. + rfc7539_init(&ctx, kek, keiv); + chacha20poly1305_decrypt(&ctx, ekeys, keys, KEYS_SIZE); + rfc7539_finish(&ctx, 0, KEYS_SIZE, tag); + memzero(&ctx, sizeof(ctx)); + wait_random(); + if (secequal32((const uint32_t *)tag, pvc, PVC_SIZE / sizeof(uint32_t)) != + sectrue) { memzero(keys, sizeof(keys)); memzero(tag, sizeof(tag)); + return secfalse; + } + memcpy(cached_keys, keys, sizeof(keys)); + memzero(keys, sizeof(keys)); + memzero(tag, sizeof(tag)); - // Check that the authenticated version number matches the norcow version. - // NOTE: storage_get_encrypted() calls auth_get(), which initializes the authentication_sum. - uint32_t version; - if (sectrue != storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) || len != sizeof(version) || version != norcow_active_version) { - handle_fault("storage version check"); - return secfalse; - } + // Check that the authenticated version number matches the norcow version. + // NOTE: storage_get_encrypted() calls auth_get(), which initializes the + // authentication_sum. + uint32_t version; + if (sectrue != + storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) || + len != sizeof(version) || version != norcow_active_version) { + handle_fault("storage version check"); + return secfalse; + } - return sectrue; + return sectrue; } -static secbool unlock(uint32_t pin) -{ - if (sectrue != initialized) { - return secfalse; - } +static secbool unlock(uint32_t pin) { + if (sectrue != initialized) { + return secfalse; + } - // Get the pin failure counter - uint32_t ctr; - if (sectrue != pin_get_fails(&ctr)) { - memzero(&pin, sizeof(pin)); - return secfalse; - } + // Get the pin failure counter + uint32_t ctr; + if (sectrue != pin_get_fails(&ctr)) { + memzero(&pin, sizeof(pin)); + return secfalse; + } + // Wipe storage if too many failures + wait_random(); + if (ctr >= PIN_MAX_TRIES) { + storage_wipe(); + error_shutdown("Too many wrong PIN", "attempts. Storage has", "been wiped.", + NULL); + return secfalse; + } + + // Sleep for 2^ctr - 1 seconds before checking the PIN. + uint32_t wait = (1 << ctr) - 1; + ui_total += wait; + uint32_t progress = 0; + for (ui_rem = ui_total; ui_rem > ui_total - wait; ui_rem--) { + for (int i = 0; i < 10; i++) { + if (ui_callback && ui_message) { + if (ui_total > 1000000) { // precise enough + progress = (ui_total - ui_rem) / (ui_total / 1000); + } else { + progress = ((ui_total - ui_rem) * 10 + i) * 100 / ui_total; + } + if (sectrue == ui_callback(ui_rem, progress, ui_message)) { + return secfalse; + } + } + hal_delay(100); + } + } + + // Read the random salt from EDEK_PVC_KEY and use it to derive the KEK and + // KEIV from the PIN. + const void *salt = NULL; + uint16_t len = 0; + if (sectrue != initialized || + sectrue != norcow_get(EDEK_PVC_KEY, &salt, &len) || + len != RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE) { + memzero(&pin, sizeof(pin)); + handle_fault("no EDEK"); + return secfalse; + } + uint8_t kek[SHA256_DIGEST_LENGTH]; + uint8_t keiv[SHA256_DIGEST_LENGTH]; + derive_kek(pin, (const uint8_t *)salt, kek, keiv); + memzero(&pin, sizeof(pin)); + + // First, we increase PIN fail counter in storage, even before checking the + // PIN. If the PIN is correct, we reset the counter afterwards. If not, we + // check if this is the last allowed attempt. + if (sectrue != storage_pin_fails_increase()) { + return secfalse; + } + + // Check that the PIN fail counter was incremented. + uint32_t ctr_ck; + if (sectrue != pin_get_fails(&ctr_ck) || ctr + 1 != ctr_ck) { + handle_fault("PIN counter increment"); + return secfalse; + } + + // Check that the PIN was correct. + if (sectrue != decrypt_dek(kek, keiv)) { // Wipe storage if too many failures wait_random(); - if (ctr >= PIN_MAX_TRIES) { - storage_wipe(); - error_shutdown("Too many wrong PIN", "attempts. Storage has", "been wiped.", NULL); - return secfalse; + if (ctr + 1 >= PIN_MAX_TRIES) { + storage_wipe(); + error_shutdown("Too many wrong PIN", "attempts. Storage has", + "been wiped.", NULL); } + return secfalse; + } + memzero(kek, sizeof(kek)); + memzero(keiv, sizeof(keiv)); - // Sleep for 2^ctr - 1 seconds before checking the PIN. - uint32_t wait = (1 << ctr) - 1; - ui_total += wait; - uint32_t progress = 0; - for (ui_rem = ui_total; ui_rem > ui_total - wait; ui_rem--) { - for (int i = 0; i < 10; i++) { - if (ui_callback && ui_message) { - if (ui_total > 1000000) { // precise enough - progress = (ui_total - ui_rem) / (ui_total / 1000); - } else { - progress = ((ui_total - ui_rem) * 10 + i) * 100 / ui_total; - } - if (sectrue == ui_callback(ui_rem, progress, ui_message)) { - return secfalse; - } - } - hal_delay(100); - } - } + unlocked = sectrue; - // Read the random salt from EDEK_PVC_KEY and use it to derive the KEK and KEIV from the PIN. - const void *salt = NULL; - uint16_t len = 0; - if (sectrue != initialized || sectrue != norcow_get(EDEK_PVC_KEY, &salt, &len) || len != RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE) { - memzero(&pin, sizeof(pin)); - handle_fault("no EDEK"); - return secfalse; - } - uint8_t kek[SHA256_DIGEST_LENGTH]; - uint8_t keiv[SHA256_DIGEST_LENGTH]; - derive_kek(pin, (const uint8_t*) salt, kek, keiv); - memzero(&pin, sizeof(pin)); - - // First, we increase PIN fail counter in storage, even before checking the - // PIN. If the PIN is correct, we reset the counter afterwards. If not, we - // check if this is the last allowed attempt. - if (sectrue != storage_pin_fails_increase()) { - return secfalse; - } - - // Check that the PIN fail counter was incremented. - uint32_t ctr_ck; - if (sectrue != pin_get_fails(&ctr_ck) || ctr + 1 != ctr_ck) { - handle_fault("PIN counter increment"); - return secfalse; - } - - // Check that the PIN was correct. - if (sectrue != decrypt_dek(kek, keiv)) { - // Wipe storage if too many failures - wait_random(); - if (ctr + 1 >= PIN_MAX_TRIES) { - storage_wipe(); - error_shutdown("Too many wrong PIN", "attempts. Storage has", "been wiped.", NULL); - } - return secfalse; - } - memzero(kek, sizeof(kek)); - memzero(keiv, sizeof(keiv)); - - unlocked = sectrue; - - // Finally set the counter to 0 to indicate success. - return pin_fails_reset(); + // Finally set the counter to 0 to indicate success. + return pin_fails_reset(); } -secbool storage_unlock(uint32_t pin) -{ - ui_total = DERIVE_SECS; - ui_rem = ui_total; - if (pin == PIN_EMPTY) { - if (ui_message == NULL) { - ui_message = STARTING_MSG; - } else { - ui_message = PROCESSING_MSG; - } +secbool storage_unlock(uint32_t pin) { + ui_total = DERIVE_SECS; + ui_rem = ui_total; + if (pin == PIN_EMPTY) { + if (ui_message == NULL) { + ui_message = STARTING_MSG; } else { - ui_message = VERIFYING_PIN_MSG; + ui_message = PROCESSING_MSG; } - return unlock(pin); + } else { + ui_message = VERIFYING_PIN_MSG; + } + return unlock(pin); } /* @@ -877,410 +919,414 @@ secbool storage_unlock(uint32_t pin) * If val_dest is not NULL and max_len >= len, then the data is decrypted * to val_dest using cached_dek as the decryption key. */ -static secbool storage_get_encrypted(const uint16_t key, void *val_dest, const uint16_t max_len, uint16_t *len) -{ - const void *val_stored = NULL; +static secbool storage_get_encrypted(const uint16_t key, void *val_dest, + const uint16_t max_len, uint16_t *len) { + const void *val_stored = NULL; - if (sectrue != auth_get(key, &val_stored, len)) { - return secfalse; - } + if (sectrue != auth_get(key, &val_stored, len)) { + return secfalse; + } - if (*len < CHACHA20_IV_SIZE + POLY1305_TAG_SIZE) { - handle_fault("ciphertext length check"); - return secfalse; - } - *len -= CHACHA20_IV_SIZE + POLY1305_TAG_SIZE; + if (*len < CHACHA20_IV_SIZE + POLY1305_TAG_SIZE) { + handle_fault("ciphertext length check"); + return secfalse; + } + *len -= CHACHA20_IV_SIZE + POLY1305_TAG_SIZE; - if (val_dest == NULL) { - return sectrue; - } - - if (*len > max_len) { - return secfalse; - } - - const uint8_t *iv = (const uint8_t*) val_stored; - const uint8_t *tag_stored = (const uint8_t*) val_stored + CHACHA20_IV_SIZE; - const uint8_t *ciphertext = (const uint8_t*) val_stored + CHACHA20_IV_SIZE + POLY1305_TAG_SIZE; - uint8_t tag_computed[POLY1305_TAG_SIZE]; - chacha20poly1305_ctx ctx; - rfc7539_init(&ctx, cached_dek, iv); - rfc7539_auth(&ctx, (const uint8_t*)&key, sizeof(key)); - chacha20poly1305_decrypt(&ctx, ciphertext, (uint8_t*) val_dest, *len); - rfc7539_finish(&ctx, sizeof(key), *len, tag_computed); - memzero(&ctx, sizeof(ctx)); - - // Verify authentication tag. - if (secequal(tag_computed, tag_stored, POLY1305_TAG_SIZE) != sectrue) { - memzero(val_dest, max_len); - memzero(tag_computed, sizeof(tag_computed)); - handle_fault("authentication tag check"); - return secfalse; - } - - memzero(tag_computed, sizeof(tag_computed)); + if (val_dest == NULL) { return sectrue; + } + + if (*len > max_len) { + return secfalse; + } + + const uint8_t *iv = (const uint8_t *)val_stored; + const uint8_t *tag_stored = (const uint8_t *)val_stored + CHACHA20_IV_SIZE; + const uint8_t *ciphertext = + (const uint8_t *)val_stored + CHACHA20_IV_SIZE + POLY1305_TAG_SIZE; + uint8_t tag_computed[POLY1305_TAG_SIZE]; + chacha20poly1305_ctx ctx; + rfc7539_init(&ctx, cached_dek, iv); + rfc7539_auth(&ctx, (const uint8_t *)&key, sizeof(key)); + chacha20poly1305_decrypt(&ctx, ciphertext, (uint8_t *)val_dest, *len); + rfc7539_finish(&ctx, sizeof(key), *len, tag_computed); + memzero(&ctx, sizeof(ctx)); + + // Verify authentication tag. + if (secequal(tag_computed, tag_stored, POLY1305_TAG_SIZE) != sectrue) { + memzero(val_dest, max_len); + memzero(tag_computed, sizeof(tag_computed)); + handle_fault("authentication tag check"); + return secfalse; + } + + memzero(tag_computed, sizeof(tag_computed)); + return sectrue; } /* * Finds the data stored under key and writes its length to len. If val_dest is * not NULL and max_len >= len, then the data is copied to val_dest. */ -secbool storage_get(const uint16_t key, void *val_dest, const uint16_t max_len, uint16_t *len) -{ - const uint8_t app = key >> 8; - // APP == 0 is reserved for PIN related values - if (sectrue != initialized || app == APP_STORAGE) { - return secfalse; - } +secbool storage_get(const uint16_t key, void *val_dest, const uint16_t max_len, + uint16_t *len) { + const uint8_t app = key >> 8; + // APP == 0 is reserved for PIN related values + if (sectrue != initialized || app == APP_STORAGE) { + return secfalse; + } - // If the top bit of APP is set, then the value is not encrypted and can be read from a locked device. - secbool ret = secfalse; - if ((app & FLAG_PUBLIC) != 0) { - const void *val_stored = NULL; - if (sectrue != norcow_get(key, &val_stored, len)) { - return secfalse; - } - if (val_dest == NULL) { - return sectrue; - } - if (*len > max_len) { - return secfalse; - } - memcpy(val_dest, val_stored, *len); - ret = sectrue; - } else { - if (sectrue != unlocked) { - return secfalse; - } - ret = storage_get_encrypted(key, val_dest, max_len, len); + // If the top bit of APP is set, then the value is not encrypted and can be + // read from a locked device. + secbool ret = secfalse; + if ((app & FLAG_PUBLIC) != 0) { + const void *val_stored = NULL; + if (sectrue != norcow_get(key, &val_stored, len)) { + return secfalse; } + if (val_dest == NULL) { + return sectrue; + } + if (*len > max_len) { + return secfalse; + } + memcpy(val_dest, val_stored, *len); + ret = sectrue; + } else { + if (sectrue != unlocked) { + return secfalse; + } + ret = storage_get_encrypted(key, val_dest, max_len, len); + } - return ret; + return ret; } /* - * Encrypts the data at val using cached_dek as the encryption key and stores the ciphertext under key. + * Encrypts the data at val using cached_dek as the encryption key and stores + * the ciphertext under key. */ -static secbool storage_set_encrypted(const uint16_t key, const void *val, const uint16_t len) -{ - if (len > UINT16_MAX - CHACHA20_IV_SIZE - POLY1305_TAG_SIZE) { - return secfalse; - } +static secbool storage_set_encrypted(const uint16_t key, const void *val, + const uint16_t len) { + if (len > UINT16_MAX - CHACHA20_IV_SIZE - POLY1305_TAG_SIZE) { + return secfalse; + } - // Preallocate space on the flash storage. - if (sectrue != auth_set(key, NULL, CHACHA20_IV_SIZE + POLY1305_TAG_SIZE + len)) { - return secfalse; - } + // Preallocate space on the flash storage. + if (sectrue != + auth_set(key, NULL, CHACHA20_IV_SIZE + POLY1305_TAG_SIZE + len)) { + return secfalse; + } - // Write the IV to the flash. - uint8_t buffer[CHACHA20_BLOCK_SIZE]; - random_buffer(buffer, CHACHA20_IV_SIZE); - uint16_t offset = 0; - if (sectrue != norcow_update_bytes(key, offset, buffer, CHACHA20_IV_SIZE)) { - return secfalse; - } - offset += CHACHA20_IV_SIZE + POLY1305_TAG_SIZE; + // Write the IV to the flash. + uint8_t buffer[CHACHA20_BLOCK_SIZE]; + random_buffer(buffer, CHACHA20_IV_SIZE); + uint16_t offset = 0; + if (sectrue != norcow_update_bytes(key, offset, buffer, CHACHA20_IV_SIZE)) { + return secfalse; + } + offset += CHACHA20_IV_SIZE + POLY1305_TAG_SIZE; - // Encrypt all blocks except for the last one. - chacha20poly1305_ctx ctx; - rfc7539_init(&ctx, cached_dek, buffer); - rfc7539_auth(&ctx, (const uint8_t*)&key, sizeof(key)); - size_t i; - for (i = 0; i + CHACHA20_BLOCK_SIZE < len; i += CHACHA20_BLOCK_SIZE, offset += CHACHA20_BLOCK_SIZE) { - chacha20poly1305_encrypt(&ctx, ((const uint8_t*) val) + i, buffer, CHACHA20_BLOCK_SIZE); - if (sectrue != norcow_update_bytes(key, offset, buffer, CHACHA20_BLOCK_SIZE)) { - memzero(&ctx, sizeof(ctx)); - memzero(buffer, sizeof(buffer)); - return secfalse; - } + // Encrypt all blocks except for the last one. + chacha20poly1305_ctx ctx; + rfc7539_init(&ctx, cached_dek, buffer); + rfc7539_auth(&ctx, (const uint8_t *)&key, sizeof(key)); + size_t i; + for (i = 0; i + CHACHA20_BLOCK_SIZE < len; + i += CHACHA20_BLOCK_SIZE, offset += CHACHA20_BLOCK_SIZE) { + chacha20poly1305_encrypt(&ctx, ((const uint8_t *)val) + i, buffer, + CHACHA20_BLOCK_SIZE); + if (sectrue != + norcow_update_bytes(key, offset, buffer, CHACHA20_BLOCK_SIZE)) { + memzero(&ctx, sizeof(ctx)); + memzero(buffer, sizeof(buffer)); + return secfalse; } + } - // Encrypt final block and compute message authentication tag. - chacha20poly1305_encrypt(&ctx, ((const uint8_t*) val) + i, buffer, len - i); - secbool ret = norcow_update_bytes(key, offset, buffer, len - i); - if (sectrue == ret) { - rfc7539_finish(&ctx, sizeof(key), len, buffer); - ret = norcow_update_bytes(key, CHACHA20_IV_SIZE, buffer, POLY1305_TAG_SIZE); - } - memzero(&ctx, sizeof(ctx)); - memzero(buffer, sizeof(buffer)); - return ret; + // Encrypt final block and compute message authentication tag. + chacha20poly1305_encrypt(&ctx, ((const uint8_t *)val) + i, buffer, len - i); + secbool ret = norcow_update_bytes(key, offset, buffer, len - i); + if (sectrue == ret) { + rfc7539_finish(&ctx, sizeof(key), len, buffer); + ret = norcow_update_bytes(key, CHACHA20_IV_SIZE, buffer, POLY1305_TAG_SIZE); + } + memzero(&ctx, sizeof(ctx)); + memzero(buffer, sizeof(buffer)); + return ret; } -secbool storage_set(const uint16_t key, const void *val, const uint16_t len) -{ - const uint8_t app = key >> 8; +secbool storage_set(const uint16_t key, const void *val, const uint16_t len) { + const uint8_t app = key >> 8; - // APP == 0 is reserved for PIN related values - if (sectrue != initialized || app == APP_STORAGE) { - return secfalse; - } + // APP == 0 is reserved for PIN related values + if (sectrue != initialized || app == APP_STORAGE) { + return secfalse; + } - if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) { - return secfalse; - } + if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) { + return secfalse; + } - secbool ret = secfalse; - if ((app & FLAG_PUBLIC) != 0) { - ret = norcow_set(key, val, len); - } else { - ret = storage_set_encrypted(key, val, len); - } - return ret; + secbool ret = secfalse; + if ((app & FLAG_PUBLIC) != 0) { + ret = norcow_set(key, val, len); + } else { + ret = storage_set_encrypted(key, val, len); + } + return ret; } -secbool storage_delete(const uint16_t key) -{ - const uint8_t app = key >> 8; +secbool storage_delete(const uint16_t key) { + const uint8_t app = key >> 8; - // APP == 0 is reserved for storage related values - if (sectrue != initialized || app == APP_STORAGE) { - return secfalse; - } + // APP == 0 is reserved for storage related values + if (sectrue != initialized || app == APP_STORAGE) { + return secfalse; + } - if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) { - return secfalse; - } + if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) { + return secfalse; + } - secbool ret = norcow_delete(key); - if (sectrue == ret) { - ret = auth_update(key); - } - return ret; + secbool ret = norcow_delete(key); + if (sectrue == ret) { + ret = auth_update(key); + } + return ret; } -secbool storage_set_counter(const uint16_t key, const uint32_t count) -{ - const uint8_t app = key >> 8; - if ((app & FLAG_PUBLIC) == 0) { - return secfalse; - } +secbool storage_set_counter(const uint16_t key, const uint32_t count) { + const uint8_t app = key >> 8; + if ((app & FLAG_PUBLIC) == 0) { + return secfalse; + } - // The count is stored as a 32-bit integer followed by a tail of "1" bits, - // which is used as a tally. - uint32_t value[1 + COUNTER_TAIL_WORDS]; - memset(value, 0xff, sizeof(value)); - value[0] = count; - return storage_set(key, value, sizeof(value)); + // The count is stored as a 32-bit integer followed by a tail of "1" bits, + // which is used as a tally. + uint32_t value[1 + COUNTER_TAIL_WORDS]; + memset(value, 0xff, sizeof(value)); + value[0] = count; + return storage_set(key, value, sizeof(value)); } -secbool storage_next_counter(const uint16_t key, uint32_t *count) -{ - const uint8_t app = key >> 8; - // APP == 0 is reserved for PIN related values - if (sectrue != initialized || app == APP_STORAGE || (app & FLAG_PUBLIC) == 0) { - return secfalse; - } +secbool storage_next_counter(const uint16_t key, uint32_t *count) { + const uint8_t app = key >> 8; + // APP == 0 is reserved for PIN related values + if (sectrue != initialized || app == APP_STORAGE || + (app & FLAG_PUBLIC) == 0) { + return secfalse; + } - if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) { - return secfalse; - } + if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) { + return secfalse; + } - uint16_t len = 0; - const uint32_t *val_stored = NULL; - if (sectrue != norcow_get(key, (const void**)&val_stored, &len)) { - *count = 0; - return storage_set_counter(key, 0); - } + uint16_t len = 0; + const uint32_t *val_stored = NULL; + if (sectrue != norcow_get(key, (const void **)&val_stored, &len)) { + *count = 0; + return storage_set_counter(key, 0); + } - if (len < sizeof(uint32_t) || len % sizeof(uint32_t) != 0) { - return secfalse; - } - uint16_t len_words = len / sizeof(uint32_t); + if (len < sizeof(uint32_t) || len % sizeof(uint32_t) != 0) { + return secfalse; + } + uint16_t len_words = len / sizeof(uint32_t); - uint16_t i = 1; - while (i < len_words && val_stored[i] == 0) { - ++i; - } + uint16_t i = 1; + while (i < len_words && val_stored[i] == 0) { + ++i; + } - *count = val_stored[0] + 1 + 32 * (i - 1); + *count = val_stored[0] + 1 + 32 * (i - 1); - if (i < len_words) { - *count += hamming_weight(~val_stored[i]); - return norcow_update_word(key, sizeof(uint32_t) * i, val_stored[i] >> 1); - } else { - return storage_set_counter(key, *count); - } + if (i < len_words) { + *count += hamming_weight(~val_stored[i]); + return norcow_update_word(key, sizeof(uint32_t) * i, val_stored[i] >> 1); + } else { + return storage_set_counter(key, *count); + } } -secbool storage_has_pin(void) -{ - if (sectrue != initialized) { - return secfalse; - } +secbool storage_has_pin(void) { + if (sectrue != initialized) { + return secfalse; + } - const void *val = NULL; - uint16_t len; - if (sectrue != norcow_get(PIN_NOT_SET_KEY, &val, &len) || (len > 0 && *(uint8_t*)val != FALSE_BYTE)) { - return secfalse; - } - return sectrue; + const void *val = NULL; + uint16_t len; + if (sectrue != norcow_get(PIN_NOT_SET_KEY, &val, &len) || + (len > 0 && *(uint8_t *)val != FALSE_BYTE)) { + return secfalse; + } + return sectrue; } -uint32_t storage_get_pin_rem(void) -{ - if (sectrue != initialized) { - return 0; - } +uint32_t storage_get_pin_rem(void) { + if (sectrue != initialized) { + return 0; + } - uint32_t ctr = 0; - if (sectrue != pin_get_fails(&ctr)) { - return 0; - } - return PIN_MAX_TRIES - ctr; + uint32_t ctr = 0; + if (sectrue != pin_get_fails(&ctr)) { + return 0; + } + return PIN_MAX_TRIES - ctr; } -secbool storage_change_pin(uint32_t oldpin, uint32_t newpin) -{ - if (sectrue != initialized) { - return secfalse; - } +secbool storage_change_pin(uint32_t oldpin, uint32_t newpin) { + if (sectrue != initialized) { + return secfalse; + } - ui_total = 2 * DERIVE_SECS; - ui_rem = ui_total; - ui_message = (oldpin != PIN_EMPTY && newpin == PIN_EMPTY) ? VERIFYING_PIN_MSG : PROCESSING_MSG; + ui_total = 2 * DERIVE_SECS; + ui_rem = ui_total; + ui_message = (oldpin != PIN_EMPTY && newpin == PIN_EMPTY) ? VERIFYING_PIN_MSG + : PROCESSING_MSG; - if (sectrue != unlock(oldpin)) { - return secfalse; - } - secbool ret = set_pin(newpin); - memzero(&oldpin, sizeof(oldpin)); - memzero(&newpin, sizeof(newpin)); - return ret; + if (sectrue != unlock(oldpin)) { + return secfalse; + } + secbool ret = set_pin(newpin); + memzero(&oldpin, sizeof(oldpin)); + memzero(&newpin, sizeof(newpin)); + return ret; } -void storage_wipe(void) -{ - norcow_wipe(); - norcow_active_version = NORCOW_VERSION; - memzero(authentication_sum, sizeof(authentication_sum)); - memzero(cached_keys, sizeof(cached_keys)); - init_wiped_storage(); +void storage_wipe(void) { + norcow_wipe(); + norcow_active_version = NORCOW_VERSION; + memzero(authentication_sum, sizeof(authentication_sum)); + memzero(cached_keys, sizeof(cached_keys)); + init_wiped_storage(); } -static void __handle_fault(const char *msg, const char *file, int line, const char *func) -{ - static secbool in_progress = secfalse; +static void __handle_fault(const char *msg, const char *file, int line, + const char *func) { + static secbool in_progress = secfalse; - // If fault handling is already in progress, then we are probably facing a fault injection attack, so wipe. - if (secfalse != in_progress) { - storage_wipe(); - __fatal_error("Fault detected", msg, file, line, func); - } - - // We use the PIN fail counter as a fault counter. Increment the counter, check that it was incremented and halt. - in_progress = sectrue; - uint32_t ctr; - if (sectrue != pin_get_fails(&ctr)) { - storage_wipe(); - __fatal_error("Fault detected", msg, file, line, func); - } - - if (sectrue != storage_pin_fails_increase()) { - storage_wipe(); - __fatal_error("Fault detected", msg, file, line, func); - } - - uint32_t ctr_new; - if (sectrue != pin_get_fails(&ctr_new) || ctr + 1 != ctr_new) { - storage_wipe(); - } + // If fault handling is already in progress, then we are probably facing a + // fault injection attack, so wipe. + if (secfalse != in_progress) { + storage_wipe(); __fatal_error("Fault detected", msg, file, line, func); + } + + // We use the PIN fail counter as a fault counter. Increment the counter, + // check that it was incremented and halt. + in_progress = sectrue; + uint32_t ctr; + if (sectrue != pin_get_fails(&ctr)) { + storage_wipe(); + __fatal_error("Fault detected", msg, file, line, func); + } + + if (sectrue != storage_pin_fails_increase()) { + storage_wipe(); + __fatal_error("Fault detected", msg, file, line, func); + } + + uint32_t ctr_new; + if (sectrue != pin_get_fails(&ctr_new) || ctr + 1 != ctr_new) { + storage_wipe(); + } + __fatal_error("Fault detected", msg, file, line, func); } /* - * Reads the PIN fail counter in version 0 format. Returns the current number of failed PIN entries. + * Reads the PIN fail counter in version 0 format. Returns the current number of + * failed PIN entries. */ -static secbool v0_pin_get_fails(uint32_t *ctr) -{ - const uint16_t V0_PIN_FAIL_KEY = 0x0001; - // The PIN_FAIL_KEY points to an area of words, initialized to - // 0xffffffff (meaning no PIN failures). The first non-zero word - // in this area is the current PIN failure counter. If PIN_FAIL_KEY - // has no configuration or is empty, the PIN failure counter is 0. - // We rely on the fact that flash allows to clear bits and we clear one - // bit to indicate PIN failure. On success, the word is set to 0, - // indicating that the next word is the PIN failure counter. +static secbool v0_pin_get_fails(uint32_t *ctr) { + const uint16_t V0_PIN_FAIL_KEY = 0x0001; + // The PIN_FAIL_KEY points to an area of words, initialized to + // 0xffffffff (meaning no PIN failures). The first non-zero word + // in this area is the current PIN failure counter. If PIN_FAIL_KEY + // has no configuration or is empty, the PIN failure counter is 0. + // We rely on the fact that flash allows to clear bits and we clear one + // bit to indicate PIN failure. On success, the word is set to 0, + // indicating that the next word is the PIN failure counter. - // Find the current pin failure counter - const void *val = NULL; - uint16_t len = 0; - if (secfalse != norcow_get(V0_PIN_FAIL_KEY, &val, &len)) { - for (unsigned int i = 0; i < len / sizeof(uint32_t); i++) { - uint32_t word = ((const uint32_t*)val)[i]; - if (word != 0) { - *ctr = hamming_weight(~word); - return sectrue; - } - } + // Find the current pin failure counter + const void *val = NULL; + uint16_t len = 0; + if (secfalse != norcow_get(V0_PIN_FAIL_KEY, &val, &len)) { + for (unsigned int i = 0; i < len / sizeof(uint32_t); i++) { + uint32_t word = ((const uint32_t *)val)[i]; + if (word != 0) { + *ctr = hamming_weight(~word); + return sectrue; + } } + } - // No PIN failures - *ctr = 0; - return sectrue; + // No PIN failures + *ctr = 0; + return sectrue; } -static secbool storage_upgrade(void) -{ - const uint16_t V0_PIN_KEY = 0x0000; - const uint16_t V0_PIN_FAIL_KEY = 0x0001; - uint16_t key = 0; - uint16_t len = 0; - const void *val = NULL; +static secbool storage_upgrade(void) { + const uint16_t V0_PIN_KEY = 0x0000; + const uint16_t V0_PIN_FAIL_KEY = 0x0001; + uint16_t key = 0; + uint16_t len = 0; + const void *val = NULL; - if (norcow_active_version == 0) { - random_buffer(cached_keys, sizeof(cached_keys)); + if (norcow_active_version == 0) { + random_buffer(cached_keys, sizeof(cached_keys)); - // Initialize the storage authentication tag. - auth_init(); + // Initialize the storage authentication tag. + auth_init(); - // Set the new storage version number. - uint32_t version = NORCOW_VERSION; - if (sectrue != storage_set_encrypted(VERSION_KEY, &version, sizeof(version))) { - return secfalse; - } + // Set the new storage version number. + uint32_t version = NORCOW_VERSION; + if (sectrue != + storage_set_encrypted(VERSION_KEY, &version, sizeof(version))) { + return secfalse; + } - // Set EDEK_PVC_KEY and PIN_NOT_SET_KEY. - ui_total = DERIVE_SECS; - ui_rem = ui_total; - ui_message = PROCESSING_MSG; - if (sectrue == norcow_get(V0_PIN_KEY, &val, &len)) { - set_pin(*(const uint32_t*)val); - } else { - set_pin(PIN_EMPTY); - } - - // Convert PIN failure counter. - uint32_t fails = 0; - v0_pin_get_fails(&fails); - pin_logs_init(fails); - - // Copy the remaining entries (encrypting the protected ones). - uint32_t offset = 0; - while (sectrue == norcow_get_next(&offset, &key, &val, &len)) { - if (key == V0_PIN_KEY || key == V0_PIN_FAIL_KEY) { - continue; - } - - secbool ret; - if (((key >> 8) & FLAG_PUBLIC) != 0) { - ret = norcow_set(key, val, len); - } else { - ret = storage_set_encrypted(key, val, len); - } - - if (sectrue != ret) { - return secfalse; - } - } - - unlocked = secfalse; - memzero(cached_keys, sizeof(cached_keys)); + // Set EDEK_PVC_KEY and PIN_NOT_SET_KEY. + ui_total = DERIVE_SECS; + ui_rem = ui_total; + ui_message = PROCESSING_MSG; + if (sectrue == norcow_get(V0_PIN_KEY, &val, &len)) { + set_pin(*(const uint32_t *)val); } else { - return secfalse; + set_pin(PIN_EMPTY); } - norcow_active_version = NORCOW_VERSION; - return norcow_upgrade_finish(); + // Convert PIN failure counter. + uint32_t fails = 0; + v0_pin_get_fails(&fails); + pin_logs_init(fails); + + // Copy the remaining entries (encrypting the protected ones). + uint32_t offset = 0; + while (sectrue == norcow_get_next(&offset, &key, &val, &len)) { + if (key == V0_PIN_KEY || key == V0_PIN_FAIL_KEY) { + continue; + } + + secbool ret; + if (((key >> 8) & FLAG_PUBLIC) != 0) { + ret = norcow_set(key, val, len); + } else { + ret = storage_set_encrypted(key, val, len); + } + + if (sectrue != ret) { + return secfalse; + } + } + + unlocked = secfalse; + memzero(cached_keys, sizeof(cached_keys)); + } else { + return secfalse; + } + + norcow_active_version = NORCOW_VERSION; + return norcow_upgrade_finish(); } diff --git a/storage/storage.h b/storage/storage.h index e3a163247e..becfb29907 100644 --- a/storage/storage.h +++ b/storage/storage.h @@ -20,13 +20,15 @@ #ifndef __STORAGE_H__ #define __STORAGE_H__ -#include #include +#include #include "secbool.h" -typedef secbool (*PIN_UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress, const char* message); +typedef secbool (*PIN_UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress, + const char *message); -void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt, const uint16_t salt_len); +void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt, + const uint16_t salt_len); void storage_wipe(void); secbool storage_is_unlocked(void); void storage_lock(void); @@ -35,7 +37,8 @@ secbool storage_has_pin(void); secbool storage_pin_fails_increase(void); uint32_t storage_get_pin_rem(void); secbool storage_change_pin(const uint32_t oldpin, const uint32_t newpin); -secbool storage_get(const uint16_t key, void *val, const uint16_t max_len, uint16_t *len); +secbool storage_get(const uint16_t key, void *val, const uint16_t max_len, + uint16_t *len); secbool storage_set(const uint16_t key, const void *val, uint16_t len); secbool storage_delete(const uint16_t key); secbool storage_set_counter(const uint16_t key, const uint32_t count); diff --git a/storage/tests/Makefile b/storage/tests/Makefile new file mode 100644 index 0000000000..16cc25552c --- /dev/null +++ b/storage/tests/Makefile @@ -0,0 +1,12 @@ +.PHONY: tests + +build: + $(MAKE) -C c + $(MAKE) -C c0 + +## tests commands: +tests: + pytest -k "not hypothesis" + +tests_all: + pytest diff --git a/storage/tests/README.md b/storage/tests/README.md new file mode 100644 index 0000000000..72f9b83e1c --- /dev/null +++ b/storage/tests/README.md @@ -0,0 +1,10 @@ +# Trezor Storage tests + +This repository contains all the necessary files to properly test Trezor's internal storage. + +This repository consists of: + +- `c`: The actual C version is implemented in the main `storage` folder, however we need some other accompanying files to build it on computer. +- `c0`: This is the older version of Trezor storage. It is used to test upgrades from the older format to the newer one. +- `python`: Python version. Serves as a reference implementation and is implemented purely for the goal of properly testing the C version. +- `tests`: Most of the tests run the two implementations against each other. Uses Pytest and [hypothesis](https://hypothesis.works) for random tests. diff --git a/storage/tests/c/Makefile b/storage/tests/c/Makefile new file mode 100644 index 0000000000..a1fc96c2e3 --- /dev/null +++ b/storage/tests/c/Makefile @@ -0,0 +1,25 @@ +CC = gcc +CFLAGS = -Wall -Wshadow -Wextra -Wpedantic -Werror -fPIC -DTREZOR_STORAGE_TEST +LIBS = +INC = -I ../../../crypto -I ../../../storage -I . +OBJ = flash.o common.o +OBJ += ../../../storage/storage.o ../../../storage/norcow.o +OBJ += ../../../crypto/pbkdf2.o +OBJ += ../../../crypto/rand.o +OBJ += ../../../crypto/chacha20poly1305/rfc7539.o +OBJ += ../../../crypto/chacha20poly1305/chacha20poly1305.o +OBJ += ../../../crypto/chacha20poly1305/poly1305-donna.o +OBJ += ../../../crypto/chacha20poly1305/chacha_merged.o +OBJ += ../../../crypto/hmac.o +OBJ += ../../../crypto/sha2.o +OBJ += ../../../crypto/memzero.o +OUT = libtrezor-storage.so + +$(OUT): $(OBJ) + $(CC) $(CFLAGS) $(LIBS) $(OBJ) -shared -o $(OUT) + +%.o: %.c %.h + $(CC) $(CFLAGS) $(INC) -c $< -o $@ + +clean: + rm -f $(OUT) $(OBJ) diff --git a/storage/tests/c/common.c b/storage/tests/c/common.c new file mode 100644 index 0000000000..faa109c07c --- /dev/null +++ b/storage/tests/c/common.c @@ -0,0 +1,57 @@ +/* + * 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 + +#include "common.h" + +void __shutdown(void) +{ + printf("SHUTDOWN\n"); + exit(3); +} + +void __fatal_error(const char *expr, const char *msg, const char *file, int line, const char *func) +{ + printf("\nFATAL ERROR:\n"); + if (expr) { + printf("expr: %s\n", expr); + } + if (msg) { + printf("msg : %s\n", msg); + } + if (file) { + printf("file: %s:%d\n", file, line); + } + if (func) { + printf("func: %s\n", func); + } + __shutdown(); +} + +void error_shutdown(const char *line1, const char *line2, const char *line3, const char *line4) { + // For testing do not treat pin_fails_check_max as a fatal error. + (void) line1; + (void) line2; + (void) line3; + (void) line4; + return; +} diff --git a/storage/tests/c/common.h b/storage/tests/c/common.h new file mode 100644 index 0000000000..e58a1b0077 --- /dev/null +++ b/storage/tests/c/common.h @@ -0,0 +1,32 @@ +/* + * 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 "secbool.h" + +void __fatal_error(const char *expr, const char *msg, const char *file, int line, const char *func); +void 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__)) + +#define hal_delay(ms) (void)ms; + +#endif diff --git a/storage/tests/c/flash.c b/storage/tests/c/flash.c new file mode 100644 index 0000000000..6282e3997d --- /dev/null +++ b/storage/tests/c/flash.c @@ -0,0 +1,129 @@ +/* + * 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 + +#include "common.h" +#include "flash.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, // - 0x08103FFF | 16 KiB + [13] = 0x08104000, // - 0x08107FFF | 16 KiB + [14] = 0x08108000, // - 0x0810BFFF | 16 KiB + [15] = 0x0810C000, // - 0x0810FFFF | 16 KiB + [16] = 0x08110000, // - 0x0811FFFF | 64 KiB + [17] = 0x08120000, // - 0x0813FFFF | 128 KiB + [18] = 0x08140000, // - 0x0815FFFF | 128 KiB + [19] = 0x08160000, // - 0x0817FFFF | 128 KiB + [20] = 0x08180000, // - 0x0819FFFF | 128 KiB + [21] = 0x081A0000, // - 0x081BFFFF | 128 KiB + [22] = 0x081C0000, // - 0x081DFFFF | 128 KiB + [23] = 0x081E0000, // - 0x081FFFFF | 128 KiB + [24] = 0x08200000, // last element - not a valid sector +}; +const uint32_t FLASH_SIZE = 0x200000; +uint8_t *FLASH_BUFFER = NULL; + +void flash_init(void) +{ + assert(FLASH_SIZE == FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT] - FLASH_SECTOR_TABLE[0]); +} + +secbool flash_unlock_write(void) +{ + return sectrue; +} + +secbool flash_lock_write(void) +{ + return sectrue; +} + +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 FLASH_BUFFER + addr - FLASH_SECTOR_TABLE[0]; +} + +secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len)) +{ + if (progress) { + progress(0, len); + } + for (int i = 0; i < len; i++) { + const uint8_t sector = sectors[i]; + const uint32_t offset = FLASH_SECTOR_TABLE[sector] - FLASH_SECTOR_TABLE[0]; + const uint32_t size = FLASH_SECTOR_TABLE[sector + 1] - FLASH_SECTOR_TABLE[sector]; + memset(FLASH_BUFFER + offset, 0xFF, size); + if (progress) { + progress(i + 1, len); + } + } + return sectrue; +} + +secbool flash_write_byte(uint8_t sector, uint32_t offset, uint8_t data) +{ + uint8_t *flash = (uint8_t *)flash_get_address(sector, offset, 1); + if (!flash) { + return secfalse; + } + if ((flash[0] & data) != data) { + return secfalse; // we cannot change zeroes to ones + } + flash[0] = data; + return sectrue; +} + +secbool flash_write_word(uint8_t sector, uint32_t offset, uint32_t data) +{ + if (offset % 4) { // we write only at 4-byte boundary + return secfalse; + } + uint32_t *flash = (uint32_t *)flash_get_address(sector, offset, sizeof(data)); + if (!flash) { + return secfalse; + } + if ((flash[0] & data) != data) { + return secfalse; // we cannot change zeroes to ones + } + flash[0] = data; + return sectrue; +} diff --git a/storage/tests/c/flash.h b/storage/tests/c/flash.h new file mode 100644 index 0000000000..102c9e1904 --- /dev/null +++ b/storage/tests/c/flash.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 FLASH_H +#define FLASH_H + +#include +#include +#include "secbool.h" + +#define FLASH_SECTOR_COUNT 24 + +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_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len)); +static inline secbool flash_erase(uint8_t sector) { return flash_erase_sectors(§or, 1, NULL); } +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 diff --git a/storage/tests/c/libtrezor-storage.so b/storage/tests/c/libtrezor-storage.so new file mode 100755 index 0000000000..83aa072847 Binary files /dev/null and b/storage/tests/c/libtrezor-storage.so differ diff --git a/storage/tests/c/norcow_config.h b/storage/tests/c/norcow_config.h new file mode 100644 index 0000000000..04e278c762 --- /dev/null +++ b/storage/tests/c/norcow_config.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 __NORCOW_CONFIG_H__ +#define __NORCOW_CONFIG_H__ + +#include "flash.h" + +#define NORCOW_SECTOR_COUNT 2 +#define NORCOW_SECTOR_SIZE (64*1024) +#define NORCOW_SECTORS {4, 16} + +/* + * The length of the sector header in bytes. The header is preserved between sector erasures. + */ +#if TREZOR_MODEL == 1 +#define NORCOW_HEADER_LEN (0x100) +#else +#define NORCOW_HEADER_LEN 0 +#endif + +/* + * Current storage version. + */ +#define NORCOW_VERSION ((uint32_t)0x00000001) + +#endif diff --git a/storage/tests/c/secbool.h b/storage/tests/c/secbool.h new file mode 100644 index 0000000000..76dfb38dc1 --- /dev/null +++ b/storage/tests/c/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/storage/tests/c/storage.py b/storage/tests/c/storage.py new file mode 100644 index 0000000000..d446b0dcf4 --- /dev/null +++ b/storage/tests/c/storage.py @@ -0,0 +1,83 @@ +import ctypes as c +import os + +sectrue = -1431655766 # 0xAAAAAAAAA +fname = os.path.join(os.path.dirname(__file__), "libtrezor-storage.so") + + +class Storage: + def __init__(self) -> None: + self.lib = c.cdll.LoadLibrary(fname) + self.flash_size = c.cast(self.lib.FLASH_SIZE, c.POINTER(c.c_uint32))[0] + self.flash_buffer = c.create_string_buffer(self.flash_size) + c.cast(self.lib.FLASH_BUFFER, c.POINTER(c.c_void_p))[0] = c.addressof( + self.flash_buffer + ) + + def init(self, salt: bytes) -> None: + self.lib.storage_init(0, salt, c.c_uint16(len(salt))) + + def wipe(self) -> None: + self.lib.storage_wipe() + + def unlock(self, pin: int) -> bool: + return sectrue == self.lib.storage_unlock(c.c_uint32(pin)) + + def lock(self) -> None: + self.lib.storage_lock() + + def has_pin(self) -> bool: + return sectrue == self.lib.storage_has_pin() + + def get_pin_rem(self) -> int: + return self.lib.storage_get_pin_rem() + + def change_pin(self, oldpin: int, newpin: int) -> bool: + return sectrue == self.lib.storage_change_pin( + c.c_uint32(oldpin), c.c_uint32(newpin) + ) + + def get(self, key: int) -> bytes: + val_len = c.c_uint16() + if sectrue != self.lib.storage_get(c.c_uint16(key), None, 0, c.byref(val_len)): + raise RuntimeError("Failed to find key in storage.") + s = c.create_string_buffer(val_len.value) + if sectrue != self.lib.storage_get( + c.c_uint16(key), s, val_len, c.byref(val_len) + ): + raise RuntimeError("Failed to get value from storage.") + return s.raw + + def set(self, key: int, val: bytes) -> None: + if sectrue != self.lib.storage_set(c.c_uint16(key), val, c.c_uint16(len(val))): + raise RuntimeError("Failed to set value in storage.") + + def set_counter(self, key: int, count: int) -> bool: + return sectrue == self.lib.storage_set_counter( + c.c_uint16(key), c.c_uint32(count) + ) + + def next_counter(self, key: int) -> int: + count = c.c_uint32() + if sectrue == self.lib.storage_next_counter(c.c_uint16(key), c.byref(count)): + return count.value + else: + return None + + def delete(self, key: int) -> bool: + return sectrue == self.lib.storage_delete(c.c_uint16(key)) + + def _dump(self) -> bytes: + # return just sectors 4 and 16 of the whole flash + return [ + self.flash_buffer[0x010000 : 0x010000 + 0x10000], + self.flash_buffer[0x110000 : 0x110000 + 0x10000], + ] + + def _get_flash_buffer(self) -> bytes: + return bytes(self.flash_buffer) + + def _set_flash_buffer(self, buf: bytes) -> None: + if len(buf) != self.flash_size: + raise RuntimeError("Failed to set flash buffer due to length mismatch.") + self.flash_buffer.value = buf diff --git a/storage/tests/c0/Makefile b/storage/tests/c0/Makefile new file mode 100644 index 0000000000..854ffa6931 --- /dev/null +++ b/storage/tests/c0/Makefile @@ -0,0 +1,14 @@ +CC=gcc +CFLAGS=-Wall -fPIC +LIBS= +OBJ=storage.o norcow.o flash.o +OUT=libtrezor-storage0.so + +$(OUT): $(OBJ) + $(CC) $(CFLAGS) $(LIBS) $(OBJ) -shared -o $(OUT) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OUT) $(OBJ) diff --git a/storage/tests/c0/common.h b/storage/tests/c0/common.h new file mode 100644 index 0000000000..6f2b178c82 --- /dev/null +++ b/storage/tests/c0/common.h @@ -0,0 +1,29 @@ +/* + * 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 "secbool.h" + +#define ensure(expr, msg) (((expr) == sectrue) ? (void)0 : (void)1) + +#define hal_delay(ms) (void)ms; + +#endif diff --git a/storage/tests/c0/flash.c b/storage/tests/c0/flash.c new file mode 100644 index 0000000000..3a0a143f2f --- /dev/null +++ b/storage/tests/c0/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 + +#include "common.h" +#include "flash.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, // - 0x08103FFF | 16 KiB + [13] = 0x08104000, // - 0x08107FFF | 16 KiB + [14] = 0x08108000, // - 0x0810BFFF | 16 KiB + [15] = 0x0810C000, // - 0x0810FFFF | 16 KiB + [16] = 0x08110000, // - 0x0811FFFF | 64 KiB + [17] = 0x08120000, // - 0x0813FFFF | 128 KiB + [18] = 0x08140000, // - 0x0815FFFF | 128 KiB + [19] = 0x08160000, // - 0x0817FFFF | 128 KiB + [20] = 0x08180000, // - 0x0819FFFF | 128 KiB + [21] = 0x081A0000, // - 0x081BFFFF | 128 KiB + [22] = 0x081C0000, // - 0x081DFFFF | 128 KiB + [23] = 0x081E0000, // - 0x081FFFFF | 128 KiB + [24] = 0x08200000, // last element - not a valid sector +}; + +const uint32_t FLASH_SIZE = 0x200000; +uint8_t *FLASH_BUFFER = NULL; + +void flash_init(void) +{ + assert(FLASH_SIZE == FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT] - FLASH_SECTOR_TABLE[0]); +} + +secbool flash_unlock(void) +{ + return sectrue; +} + +secbool flash_lock(void) +{ + return sectrue; +} + +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 FLASH_BUFFER + addr - FLASH_SECTOR_TABLE[0]; +} + +secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len)) +{ + if (progress) { + progress(0, len); + } + for (int i = 0; i < len; i++) { + const uint8_t sector = sectors[i]; + const uint32_t offset = FLASH_SECTOR_TABLE[sector] - FLASH_SECTOR_TABLE[0]; + const uint32_t size = FLASH_SECTOR_TABLE[sector + 1] - FLASH_SECTOR_TABLE[sector]; + memset(FLASH_BUFFER + offset, 0xFF, size); + if (progress) { + progress(i + 1, len); + } + } + return sectrue; +} + +secbool flash_write_byte(uint8_t sector, uint32_t offset, uint8_t data) +{ + uint8_t *flash = (uint8_t *)flash_get_address(sector, offset, 1); + if (!flash) { + return secfalse; + } + if ((flash[0] & data) != data) { + return secfalse; // we cannot change zeroes to ones + } + flash[0] = data; + return sectrue; +} + +secbool flash_write_word(uint8_t sector, uint32_t offset, uint32_t data) +{ + if (offset % 4) { // we write only at 4-byte boundary + return secfalse; + } + uint32_t *flash = (uint32_t *)flash_get_address(sector, offset, sizeof(data)); + if (!flash) { + return secfalse; + } + if ((flash[0] & data) != data) { + return secfalse; // we cannot change zeroes to ones + } + flash[0] = data; + return sectrue; +} diff --git a/storage/tests/c0/flash.h b/storage/tests/c0/flash.h new file mode 100644 index 0000000000..436dceb429 --- /dev/null +++ b/storage/tests/c0/flash.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 FLASH_H +#define FLASH_H + +#include +#include +#include "secbool.h" + +#define FLASH_SECTOR_COUNT 24 + +void flash_init(void); + +secbool __wur flash_unlock(void); +secbool __wur flash_lock(void); + +const void *flash_get_address(uint8_t sector, uint32_t offset, uint32_t size); + +secbool __wur flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len)); +static inline secbool flash_erase_sector(uint8_t sector) { return flash_erase_sectors(§or, 1, NULL); } +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 diff --git a/storage/tests/c0/libtrezor-storage0.so b/storage/tests/c0/libtrezor-storage0.so new file mode 100755 index 0000000000..e03da87a7d Binary files /dev/null and b/storage/tests/c0/libtrezor-storage0.so differ diff --git a/storage/tests/c0/norcow.c b/storage/tests/c0/norcow.c new file mode 100644 index 0000000000..ed54be3b2b --- /dev/null +++ b/storage/tests/c0/norcow.c @@ -0,0 +1,305 @@ +/* + * 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 "norcow.h" +#include "flash.h" +#include "common.h" + +// NRCW = 4e524357 +#define NORCOW_MAGIC ((uint32_t)0x5743524e) +#define NORCOW_MAGIC_LEN (sizeof(uint32_t)) + +static const uint8_t norcow_sectors[NORCOW_SECTOR_COUNT] = NORCOW_SECTORS; +static uint8_t norcow_active_sector = 0; +static uint32_t norcow_active_offset = NORCOW_MAGIC_LEN; + +/* + * Returns pointer to sector, starting with offset + * Fails when there is not enough space for data of given size + */ +static const void *norcow_ptr(uint8_t sector, uint32_t offset, uint32_t size) +{ + ensure(sectrue * (sector <= NORCOW_SECTOR_COUNT), "invalid sector"); + return flash_get_address(norcow_sectors[sector], offset, size); +} + +/* + * Writes data to given sector, starting from offset + */ +static secbool norcow_write(uint8_t sector, uint32_t offset, uint32_t prefix, const uint8_t *data, uint16_t len) +{ + if (sector >= NORCOW_SECTOR_COUNT) { + return secfalse; + } + ensure(flash_unlock(), NULL); + + // write prefix + ensure(flash_write_word(norcow_sectors[sector], offset, prefix), NULL); + + if (len > 0) { + offset += sizeof(uint32_t); + // write data + for (uint16_t i = 0; i < len; i++, offset++) { + ensure(flash_write_byte(norcow_sectors[sector], offset, data[i]), NULL); + } + // pad with zeroes + for (; offset % 4; offset++) { + ensure(flash_write_byte(norcow_sectors[sector], offset, 0x00), NULL); + } + } + ensure(flash_lock(), NULL); + return sectrue; +} + +/* + * Erases sector (and sets a magic) + */ +static void norcow_erase(uint8_t sector, secbool set_magic) +{ + ensure(sectrue * (sector <= NORCOW_SECTOR_COUNT), "invalid sector"); + ensure(flash_erase_sector(norcow_sectors[sector]), "erase failed"); + if (sectrue == set_magic) { + ensure(norcow_write(sector, 0, NORCOW_MAGIC, NULL, 0), "set magic failed"); + } +} + +#define ALIGN4(X) (X) = ((X) + 3) & ~3 + +/* + * Reads one item starting from offset + */ +static secbool read_item(uint8_t sector, uint32_t offset, uint16_t *key, const void **val, uint16_t *len, uint32_t *pos) +{ + *pos = offset; + + const void *k = norcow_ptr(sector, *pos, 2); + if (k == NULL) return secfalse; + *pos += 2; + memcpy(key, k, sizeof(uint16_t)); + if (*key == 0xFFFF) { + return secfalse; + } + + const void *l = norcow_ptr(sector, *pos, 2); + if (l == NULL) return secfalse; + *pos += 2; + memcpy(len, l, sizeof(uint16_t)); + + *val = norcow_ptr(sector, *pos, *len); + if (*val == NULL) return secfalse; + *pos += *len; + ALIGN4(*pos); + return sectrue; +} + +/* + * Writes one item starting from offset + */ +static secbool write_item(uint8_t sector, uint32_t offset, uint16_t key, const void *val, uint16_t len, uint32_t *pos) +{ + uint32_t prefix = (len << 16) | key; + *pos = offset + sizeof(uint32_t) + len; + ALIGN4(*pos); + return norcow_write(sector, offset, prefix, val, len); +} + +/* + * Finds item in given sector + */ +static secbool find_item(uint8_t sector, uint16_t key, const void **val, uint16_t *len) +{ + *val = 0; + *len = 0; + uint32_t offset = NORCOW_MAGIC_LEN; + for (;;) { + uint16_t k, l; + const void *v; + uint32_t pos; + if (sectrue != read_item(sector, offset, &k, &v, &l, &pos)) { + break; + } + if (key == k) { + *val = v; + *len = l; + } + offset = pos; + } + return sectrue * (*val != NULL); +} + +/* + * Finds first unused offset in given sector + */ +static uint32_t find_free_offset(uint8_t sector) +{ + uint32_t offset = NORCOW_MAGIC_LEN; + for (;;) { + uint16_t key, len; + const void *val; + uint32_t pos; + if (sectrue != read_item(sector, offset, &key, &val, &len, &pos)) { + break; + } + offset = pos; + } + return offset; +} + +/* + * Compacts active sector and sets new active sector + */ +static void compact() +{ + uint8_t norcow_next_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT; + norcow_erase(norcow_next_sector, sectrue); + + uint32_t offset = NORCOW_MAGIC_LEN, offsetw = NORCOW_MAGIC_LEN; + + for (;;) { + // read item + uint16_t k, l; + const void *v; + uint32_t pos; + secbool r = read_item(norcow_active_sector, offset, &k, &v, &l, &pos); + if (sectrue != r) { + break; + } + offset = pos; + + // check if not already saved + const void *v2; + uint16_t l2; + r = find_item(norcow_next_sector, k, &v2, &l2); + if (sectrue == r) { + continue; + } + + // scan for latest instance + uint32_t offsetr = offset; + for (;;) { + uint16_t k2; + uint32_t posr; + r = read_item(norcow_active_sector, offsetr, &k2, &v2, &l2, &posr); + if (sectrue != r) { + break; + } + if (k == k2) { + v = v2; + l = l2; + } + offsetr = posr; + } + + // copy the last item + uint32_t posw; + ensure(write_item(norcow_next_sector, offsetw, k, v, l, &posw), "compaction write failed"); + offsetw = posw; + } + + norcow_erase(norcow_active_sector, secfalse); + norcow_active_sector = norcow_next_sector; + norcow_active_offset = find_free_offset(norcow_active_sector); +} + +/* + * Initializes storage + */ +void norcow_init(void) +{ + flash_init(); + secbool found = secfalse; + // detect active sector - starts with magic + for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) { + const uint32_t *magic = norcow_ptr(i, 0, NORCOW_MAGIC_LEN); + if (magic != NULL && *magic == NORCOW_MAGIC) { + found = sectrue; + norcow_active_sector = i; + break; + } + } + // no active sectors found - let's erase + if (sectrue == found) { + norcow_active_offset = find_free_offset(norcow_active_sector); + } else { + norcow_wipe(); + } +} + +/* + * Wipe the storage + */ +void norcow_wipe(void) +{ + norcow_erase(0, sectrue); + for (uint8_t i = 1; i < NORCOW_SECTOR_COUNT; i++) { + norcow_erase(i, secfalse); + } + norcow_active_sector = 0; + norcow_active_offset = NORCOW_MAGIC_LEN; +} + +/* + * Looks for the given key, returns status of the operation + */ +secbool norcow_get(uint16_t key, const void **val, uint16_t *len) +{ + return find_item(norcow_active_sector, key, val, len); +} + +/* + * Sets the given key, returns status of the operation + */ +secbool norcow_set(uint16_t key, const void *val, uint16_t len) +{ + // check whether there is enough free space + // and compact if full + if (norcow_active_offset + sizeof(uint32_t) + len > NORCOW_SECTOR_SIZE) { + compact(); + } + // write item + uint32_t pos; + secbool r = write_item(norcow_active_sector, norcow_active_offset, key, val, len, &pos); + if (sectrue == r) { + norcow_active_offset = pos; + } + return r; +} + +/* + * Update a word in flash at the given pointer. The pointer must point + * into the NORCOW area. + */ +secbool norcow_update(uint16_t key, uint16_t offset, uint32_t value) +{ + const void *ptr; + uint16_t len; + if (sectrue != find_item(norcow_active_sector, key, &ptr, &len)) { + return secfalse; + } + if ((offset & 3) != 0 || offset >= len) { + return secfalse; + } + uint32_t sector_offset = (const uint8_t*) ptr - (const uint8_t *)norcow_ptr(norcow_active_sector, 0, NORCOW_SECTOR_SIZE) + offset; + ensure(flash_unlock(), NULL); + ensure(flash_write_word(norcow_sectors[norcow_active_sector], sector_offset, value), NULL); + ensure(flash_lock(), NULL); + return sectrue; +} diff --git a/storage/tests/c0/norcow.h b/storage/tests/c0/norcow.h new file mode 100644 index 0000000000..00bab4d0ae --- /dev/null +++ b/storage/tests/c0/norcow.h @@ -0,0 +1,58 @@ +/* + * 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_H__ +#define __NORCOW_H__ + +#include +#include "secbool.h" + +/* + * Storage parameters + */ + +#include "norcow_config.h" + +/* + * Initialize storage + */ +void norcow_init(void); + +/* + * Wipe the storage + */ +void norcow_wipe(void); + +/* + * Looks for the given key, returns status of the operation + */ +secbool norcow_get(uint16_t key, const void **val, uint16_t *len); + +/* + * Sets the given key, returns status of the operation + */ +secbool norcow_set(uint16_t key, const void *val, uint16_t len); + +/* + * Update a word in flash in the given key at the given offset. + * Note that you can only change bits from 1 to 0. + */ +secbool norcow_update(uint16_t key, uint16_t offset, uint32_t value); + +#endif diff --git a/storage/tests/c0/norcow_config.h b/storage/tests/c0/norcow_config.h new file mode 100644 index 0000000000..ff7b1eb0be --- /dev/null +++ b/storage/tests/c0/norcow_config.h @@ -0,0 +1,29 @@ +/* + * 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 (64*1024) +#define NORCOW_SECTORS {4, 16} + +#endif diff --git a/storage/tests/c0/secbool.h b/storage/tests/c0/secbool.h new file mode 100644 index 0000000000..76dfb38dc1 --- /dev/null +++ b/storage/tests/c0/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/storage/tests/c0/storage.c b/storage/tests/c0/storage.c new file mode 100644 index 0000000000..5e0f8343bb --- /dev/null +++ b/storage/tests/c0/storage.c @@ -0,0 +1,238 @@ +/* + * 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 "common.h" +#include "norcow.h" +#include "storage.h" + +// Norcow storage key of configured PIN. +#define PIN_KEY 0x0000 + +// Maximum PIN length. +#define PIN_MAXLEN 32 + +// Byte-length of flash section containing fail counters. +#define PIN_FAIL_KEY 0x0001 +#define PIN_FAIL_SECTOR_SIZE 32 + +// Maximum number of failed unlock attempts. +#define PIN_MAX_TRIES 15 + +static secbool initialized = secfalse; +static secbool unlocked = secfalse; +static PIN_UI_WAIT_CALLBACK ui_callback = NULL; + +void storage_init(PIN_UI_WAIT_CALLBACK callback) +{ + initialized = secfalse; + unlocked = secfalse; + norcow_init(); + initialized = sectrue; + ui_callback = callback; +} + +static secbool pin_fails_reset(uint16_t ofs) +{ + return norcow_update(PIN_FAIL_KEY, ofs, 0); +} + +static secbool pin_fails_increase(const uint32_t *ptr, uint16_t ofs) +{ + uint32_t ctr = *ptr; + ctr = ctr << 1; + + if (sectrue != norcow_update(PIN_FAIL_KEY, ofs, ctr)) { + return secfalse; + } + + uint32_t check = *ptr; + if (ctr != check) { + return secfalse; + } + return sectrue; +} + +static void pin_fails_check_max(uint32_t ctr) +{ + if (~ctr >= (1 << PIN_MAX_TRIES)) { + norcow_wipe(); + ensure(secfalse, "pin_fails_check_max"); + } +} + +static secbool pin_cmp(const uint32_t pin) +{ + const void *spin = NULL; + uint16_t spinlen = 0; + norcow_get(PIN_KEY, &spin, &spinlen); + if (NULL != spin && spinlen == sizeof(uint32_t)) { + return sectrue * (pin == *(const uint32_t*)spin); + } else { + return sectrue * (1 == pin); + } +} + +static secbool pin_get_fails(const uint32_t **pinfail, uint32_t *pofs) +{ + const void *vpinfail; + uint16_t pinfaillen; + unsigned int ofs; + // The PIN_FAIL_KEY points to an area of words, initialized to + // 0xffffffff (meaning no pin failures). The first non-zero word + // in this area is the current pin failure counter. If PIN_FAIL_KEY + // has no configuration or is empty, the pin failure counter is 0. + // We rely on the fact that flash allows to clear bits and we clear one + // bit to indicate pin failure. On success, the word is set to 0, + // indicating that the next word is the pin failure counter. + + // Find the current pin failure counter + if (secfalse != norcow_get(PIN_FAIL_KEY, &vpinfail, &pinfaillen)) { + *pinfail = vpinfail; + for (ofs = 0; ofs < pinfaillen / sizeof(uint32_t); ofs++) { + if (((const uint32_t *) vpinfail)[ofs]) { + *pinfail = vpinfail; + *pofs = ofs; + return sectrue; + } + } + } + + // No pin failure section, or all entries used -> create a new one. + uint32_t pinarea[PIN_FAIL_SECTOR_SIZE]; + memset(pinarea, 0xff, sizeof(pinarea)); + if (sectrue != norcow_set(PIN_FAIL_KEY, pinarea, sizeof(pinarea))) { + return secfalse; + } + if (sectrue != norcow_get(PIN_FAIL_KEY, &vpinfail, &pinfaillen)) { + return secfalse; + } + *pinfail = vpinfail; + *pofs = 0; + return sectrue; +} + +secbool storage_check_pin(const uint32_t pin) +{ + const uint32_t *pinfail = NULL; + uint32_t ofs; + uint32_t ctr; + + // Get the pin failure counter + if (pin_get_fails(&pinfail, &ofs) != sectrue) { + return secfalse; + } + + // Read current failure counter + ctr = pinfail[ofs]; + // Wipe storage if too many failures + pin_fails_check_max(ctr); + + // Sleep for ~ctr seconds before checking the PIN. + uint32_t progress; + for (uint32_t wait = ~ctr; wait > 0; wait--) { + for (int i = 0; i < 10; i++) { + if (ui_callback) { + if ((~ctr) > 1000000) { // precise enough + progress = (~ctr - wait) / ((~ctr) / 1000); + } else { + progress = ((~ctr - wait) * 10 + i) * 100 / (~ctr); + } + ui_callback(wait, progress); + } + hal_delay(100); + } + } + // Show last frame if we were waiting + if ((~ctr > 0) && ui_callback) { + ui_callback(0, 1000); + } + + // First, we increase PIN fail counter in storage, even before checking the + // PIN. If the PIN is correct, we reset the counter afterwards. If not, we + // check if this is the last allowed attempt. + if (sectrue != pin_fails_increase(pinfail + ofs, ofs * sizeof(uint32_t))) { + return secfalse; + } + if (sectrue != pin_cmp(pin)) { + // Wipe storage if too many failures + pin_fails_check_max(ctr << 1); + return secfalse; + } + // Finally set the counter to 0 to indicate success. + return pin_fails_reset(ofs * sizeof(uint32_t)); +} + +secbool storage_unlock(const uint32_t pin) +{ + unlocked = secfalse; + if (sectrue == initialized && sectrue == storage_check_pin(pin)) { + unlocked = sectrue; + } + return unlocked; +} + +secbool storage_get(const uint16_t key, const void **val, uint16_t *len) +{ + const uint8_t app = key >> 8; + // APP == 0 is reserved for PIN related values + if (sectrue != initialized || app == 0) { + return secfalse; + } + // top bit of APP set indicates the value can be read from unlocked device + if (sectrue != unlocked && ((app & 0x80) == 0)) { + return secfalse; + } + return norcow_get(key, val, len); +} + +secbool storage_set(const uint16_t key, const void *val, uint16_t len) +{ + const uint8_t app = key >> 8; + // APP == 0 is reserved for PIN related values + if (sectrue != initialized || sectrue != unlocked || app == 0) { + return secfalse; + } + return norcow_set(key, val, len); +} + +secbool storage_has_pin(void) +{ + if (sectrue != initialized) { + return secfalse; + } + return sectrue == pin_cmp(1) ? secfalse : sectrue; +} + +secbool storage_change_pin(const uint32_t oldpin, const uint32_t newpin) +{ + if (sectrue != initialized || sectrue != unlocked) { + return secfalse; + } + if (sectrue != storage_check_pin(oldpin)) { + return secfalse; + } + return norcow_set(PIN_KEY, &newpin, sizeof(uint32_t)); +} + +void storage_wipe(void) +{ + norcow_wipe(); +} diff --git a/storage/tests/c0/storage.h b/storage/tests/c0/storage.h new file mode 100644 index 0000000000..7975281756 --- /dev/null +++ b/storage/tests/c0/storage.h @@ -0,0 +1,38 @@ +/* + * 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 __STORAGE_H__ +#define __STORAGE_H__ + +#include +#include +#include "secbool.h" + +typedef void (*PIN_UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress); + +void storage_init(PIN_UI_WAIT_CALLBACK callback); +void storage_wipe(void); +secbool storage_check_pin(const uint32_t pin); +secbool storage_unlock(const uint32_t pin); +secbool storage_has_pin(void); +secbool storage_change_pin(const uint32_t oldpin, const uint32_t newpin); +secbool storage_get(const uint16_t key, const void **val, uint16_t *len); +secbool storage_set(const uint16_t key, const void *val, uint16_t len); + +#endif diff --git a/storage/tests/c0/storage.py b/storage/tests/c0/storage.py new file mode 100644 index 0000000000..235695c63f --- /dev/null +++ b/storage/tests/c0/storage.py @@ -0,0 +1,63 @@ +import ctypes as c +import os + +sectrue = -1431655766 # 0xAAAAAAAAA +fname = os.path.join(os.path.dirname(__file__), "libtrezor-storage0.so") + + +class Storage: + def __init__(self) -> None: + self.lib = c.cdll.LoadLibrary(fname) + self.flash_size = c.cast(self.lib.FLASH_SIZE, c.POINTER(c.c_uint32))[0] + self.flash_buffer = c.create_string_buffer(self.flash_size) + c.cast(self.lib.FLASH_BUFFER, c.POINTER(c.c_void_p))[0] = c.addressof( + self.flash_buffer + ) + + def init(self) -> None: + self.lib.storage_init(0) + + def wipe(self) -> None: + self.lib.storage_wipe() + + def check_pin(self, pin: int) -> bool: + return sectrue == self.lib.storage_check_pin(c.c_uint32(pin)) + + def unlock(self, pin: int) -> bool: + return sectrue == self.lib.storage_unlock(c.c_uint32(pin)) + + def has_pin(self) -> bool: + return sectrue == self.lib.storage_has_pin() + + def change_pin(self, oldpin: int, newpin: int) -> bool: + return sectrue == self.lib.storage_change_pin( + c.c_uint32(oldpin), c.c_uint32(newpin) + ) + + def get(self, key: int) -> bytes: + val_ptr = c.c_void_p() + val_len = c.c_uint16() + if sectrue != self.lib.storage_get( + c.c_uint16(key), c.byref(val_ptr), c.byref(val_len) + ): + raise RuntimeError("Failed to find key in storage.") + return c.string_at(val_ptr, size=val_len.value) + + def set(self, key: int, val: bytes) -> None: + if sectrue != self.lib.storage_set(c.c_uint16(key), val, c.c_uint16(len(val))): + raise RuntimeError("Failed to set value in storage.") + + def _dump(self) -> bytes: + # return just sectors 4 and 16 of the whole flash + return [ + self.flash_buffer[0x010000 : 0x010000 + 0x10000], + self.flash_buffer[0x110000 : 0x110000 + 0x10000], + ] + + def _get_flash_buffer(self) -> bytes: + return bytes(self.flash_buffer) + + def _set_flash_buffer(self, buf: bytes) -> None: + if len(buf) != self.flash_size: + raise RuntimeError("Failed to set flash buffer due to length mismatch.") + self.flash_buffer = buf diff --git a/storage/tests/python/__init__.py b/storage/tests/python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/storage/tests/python/src/__init__.py b/storage/tests/python/src/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/storage/tests/python/src/consts.py b/storage/tests/python/src/consts.py new file mode 100644 index 0000000000..1785c36239 --- /dev/null +++ b/storage/tests/python/src/consts.py @@ -0,0 +1,152 @@ +# ----- PIN and encryption related ----- # + +# App ID where PIN log is stored. +PIN_APP_ID = 0x00 + +# Storage key of the combined salt, EDEK, ESEK and PIN verification code entry. +EDEK_ESEK_PVC_KEY = (PIN_APP_ID << 8) | 0x02 + +# Storage key of the PIN set flag. +PIN_NOT_SET_KEY = (PIN_APP_ID << 8) | 0x03 + +# Norcow storage key of the storage version. +VERSION_KEY = (PIN_APP_ID << 8) | 0x04 + +# Norcow storage key of the storage authentication tag. +SAT_KEY = (PIN_APP_ID << 8) | 0x05 + +# The PIN value corresponding to an empty PIN. +PIN_EMPTY = 1 + +# Maximum number of failed unlock attempts. +PIN_MAX_TRIES = 16 + +# The total number of iterations to use in PBKDF2. +PIN_ITER_COUNT = 20000 + +# The length of the data encryption key in bytes. +DEK_SIZE = 32 + +# The length of the storage authentication key in bytes. +SAK_SIZE = 16 + +# The length of the storage authentication tag in bytes. +SAT_SIZE = 16 + +# The length of the random salt in bytes. +PIN_SALT_SIZE = 4 +PIN_HARDWARE_SALT_SIZE = 32 + +# The length of the PIN verification code in bytes. +PVC_SIZE = 8 + +# The length of KEK in bytes. +KEK_SIZE = 32 + +# The length of KEIV in bytes. +KEIV_SIZE = 12 + +# Size of counter. 4B integer and 8B tail. +COUNTER_TAIL = 12 +COUNTER_TAIL_SIZE = 8 +COUNTER_MAX_TAIL = 64 + +# ----- PIN logs ----- # + +# Storage key of the PIN entry log and PIN success log. +PIN_LOG_KEY = (PIN_APP_ID << 8) | 0x01 + +# Length of items in the PIN entry log +PIN_LOG_GUARD_KEY_SIZE = 4 + +# Values used for the guard key integrity check. +GUARD_KEY_MODULUS = 6311 +GUARD_KEY_REMAINDER = 15 +GUARD_KEY_RANDOM_MAX = (0xFFFFFFFF // GUARD_KEY_MODULUS) + 1 + +# Length of both success log and entry log +PIN_LOG_SIZE = 64 + +# Used for in guard bits operations. +LOW_MASK = 0x55555555 + +# Log initialized to all FFs. +ALL_FF_LOG = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + +# ----- Bytes ----- + +# If the top bit of APP is set, then the value is not encrypted. +FLAG_PUBLIC = 0x80 + +# If the top two bits of APP are set, then the value is not encrypted and it +# can be written even when the storage is locked. +FLAG_WRITE = 0xC0 + +# Length of word in bytes. +WORD_SIZE = 4 + +# Boolean values are stored as a simple 0/1 int. +TRUE_BYTE = b"\x01" +FALSE_BYTE = b"\x00" + +# ----- Crypto ----- # + +# The length of the Poly1305 MAC in bytes. +POLY1305_MAC_SIZE = 16 + +# The length of the ChaCha20 IV (aka nonce) in bytes as per RFC 7539. +CHACHA_IV_SIZE = 12 + +# ----- Norcow ----- # + +NORCOW_SECTOR_COUNT = 2 +NORCOW_SECTOR_SIZE = 64 * 1024 + +# Magic flag at the beggining of an active sector. +NORCOW_MAGIC = b"NRC2" + +# Norcow version, set in the storage header, but also as an encrypted item. +NORCOW_VERSION = b"\x01\x00\x00\x00" + +# Norcow magic combined with the version, which is stored as its negation. +NORCOW_MAGIC_AND_VERSION = NORCOW_MAGIC + bytes( + [ + ~NORCOW_VERSION[0] & 0xFF, + ~NORCOW_VERSION[1] & 0xFF, + ~NORCOW_VERSION[2] & 0xFF, + ~NORCOW_VERSION[3] & 0xFF, + ] +) + +# Signalizes free storage. +NORCOW_KEY_FREE = 0xFFFF + + +# |-----------|-------------------| +# | Private | APP = 0 | +# | Protected | 1 <= APP <= 127 | +# | Public | 128 <= APP <= 255 | + + +def is_app_public(app: int): + if app & FLAG_PUBLIC: + return True + return False + + +def is_app_protected(app: int): + if is_app_public(app): + return False + if is_app_private(app): + return False + return True + + +def is_app_private(app: int): + return app == PIN_APP_ID + + +def is_app_lock_writable(app: int): + if app & FLAG_WRITE == FLAG_WRITE: + return True + return False diff --git a/storage/tests/python/src/crypto.py b/storage/tests/python/src/crypto.py new file mode 100644 index 0000000000..037d82a0e5 --- /dev/null +++ b/storage/tests/python/src/crypto.py @@ -0,0 +1,112 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, hmac +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms +from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +from . import consts, prng + + +def derive_kek_keiv(salt: bytes, pin: int) -> (bytes, bytes): + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=consts.KEK_SIZE + consts.KEIV_SIZE, + salt=bytes(salt), + iterations=10000, + backend=default_backend(), + ) + pbkdf_output = kdf.derive(pin.to_bytes(4, "little")) + # the first 256b is Key Encryption Key + kek = pbkdf_output[: consts.KEK_SIZE] + # following with 96b of Initialization Vector + keiv = pbkdf_output[consts.KEK_SIZE :] + + return kek, keiv + + +def chacha_poly_encrypt( + key: bytes, iv: bytes, data: bytes, additional_data: bytes = None +) -> (bytes, bytes): + chacha = ChaCha20Poly1305(key) + chacha_output = chacha.encrypt(iv, bytes(data), additional_data) + # cipher text and 128b authentication tag + return chacha_output[: len(data)], chacha_output[len(data) :] + + +def chacha_poly_decrypt( + key: bytes, app_key: int, iv: bytes, data: bytes, additional_data: bytes = None +) -> bytes: + chacha = ChaCha20Poly1305(key) + chacha_output = chacha.decrypt(bytes(iv), bytes(data), additional_data) + return chacha_output + + +def decrypt_edek_esak( + pin: int, salt: bytes, edek_esak: bytes, pvc: bytes +) -> (bytes, bytes): + """ + Decrypts EDEK, ESAK to DEK, SAK and checks PIN in the process. + Raises: + InvalidPinError: if PIN is invalid + """ + kek, keiv = derive_kek_keiv(salt, pin) + + algorithm = algorithms.ChaCha20(kek, (1).to_bytes(4, "little") + keiv) + cipher = Cipher(algorithm, mode=None, backend=default_backend()) + decryptor = cipher.decryptor() + dek_sak = decryptor.update(bytes(edek_esak)) + dek = dek_sak[: consts.DEK_SIZE] + sak = dek_sak[consts.DEK_SIZE :] + + if not validate_pin(kek, keiv, dek_sak, pvc): + raise InvalidPinError("Invalid PIN") + + return dek, sak + + +def validate_pin(kek: bytes, keiv: bytes, dek_sak: bytes, pvc: bytes) -> bool: + """ + This a little bit hackish. We do not store the whole + authentication tag so we can't decrypt using ChaCha20Poly1305 + because it obviously checks the tag first and fails. + So we are using the sole ChaCha20 cipher to decipher and then encrypt + again with Chacha20Poly1305 here to get the tag and compare it to PVC. + """ + _, tag = chacha_poly_encrypt(kek, keiv, dek_sak) + prng.random32() + prng.random32() + return tag[: consts.PVC_SIZE] == pvc + + +def calculate_hmacs(sak: bytes, keys: bytes) -> bytes: + """ + This calculates HMAC-SHA-256(SAK, (XOR_i) HMAC-SHA-256(SAK, KEY_i)). + In other words, it does HMAC for every KEY and XORs it all together. + One more final HMAC is then performed on the result. + """ + hmacs = _hmac(sak, keys[0]) + for key in keys[1:]: + hmacs = _xor(hmacs, _hmac(sak, key)) + return _final_hmac(sak, hmacs) + + +def init_hmacs(sak: bytes) -> bytes: + return _final_hmac(sak, b"\x00" * hashes.SHA256.digest_size) + + +def _final_hmac(sak: bytes, data: bytes) -> bytes: + return _hmac(sak, data)[: consts.SAT_SIZE] + + +def _hmac(key: bytes, data: bytes) -> bytes: + h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend()) + h.update(data) + return h.finalize() + + +def _xor(first: bytes, second: bytes) -> bytes: + return bytes(a ^ b for a, b in zip(first, second)) + + +class InvalidPinError(ValueError): + pass diff --git a/storage/tests/python/src/helpers.py b/storage/tests/python/src/helpers.py new file mode 100644 index 0000000000..83ec34e2ef --- /dev/null +++ b/storage/tests/python/src/helpers.py @@ -0,0 +1,45 @@ +import sys + +from . import consts + + +def expand_to_log_size(value: int) -> int: + result = 0 + for i in range(0, consts.PIN_LOG_SIZE, 4): + result = result | (value << i * 8) + return result + + +def to_int_by_words(array: bytes) -> int: + """ + Converts array of bytes into an int by reading word size + of bytes then converted to int using the system's endianness. + """ + assert len(array) % consts.WORD_SIZE == 0 + n = 0 + for i in range(0, len(array), consts.WORD_SIZE): + n = (n << (consts.WORD_SIZE * 8)) + int.from_bytes( + array[i : i + consts.WORD_SIZE], sys.byteorder + ) + return n + + +def to_bytes_by_words(n: int, length: int) -> bytes: + """ + Converting int back to bytes by words. + """ + mask = (1 << (consts.WORD_SIZE * 8)) - 1 + array = bytes() + for i in reversed(range(0, length, consts.WORD_SIZE)): + array = array + ((n >> (i * 8)) & mask).to_bytes( + consts.WORD_SIZE, sys.byteorder + ) + return array + + +def int_to_word(n: int) -> bytes: + return n.to_bytes(consts.WORD_SIZE, sys.byteorder) + + +def word_to_int(b: bytes) -> int: + return int.from_bytes(b, sys.byteorder) diff --git a/storage/tests/python/src/norcow.py b/storage/tests/python/src/norcow.py new file mode 100644 index 0000000000..3a8baca33f --- /dev/null +++ b/storage/tests/python/src/norcow.py @@ -0,0 +1,174 @@ +import sys +from struct import pack + +from . import consts + + +def align4_int(i: int): + return (4 - i) % 4 + + +def align4_data(data): + return data + b"\x00" * align4_int(len(data)) + + +class Norcow: + def __init__(self): + self.sectors = None + + def init(self): + if self.sectors: + for sector in range(consts.NORCOW_SECTOR_COUNT): + if self.sectors[sector][:8] == consts.NORCOW_MAGIC_AND_VERSION: + self.active_sector = sector + self.active_offset = len(consts.NORCOW_MAGIC_AND_VERSION) + break + else: + self.wipe() + + def wipe(self, sector: int = 0): + self.sectors = [ + bytearray([0xFF] * consts.NORCOW_SECTOR_SIZE) + for _ in range(consts.NORCOW_SECTOR_COUNT) + ] + self.sectors[sector][:8] = consts.NORCOW_MAGIC_AND_VERSION + self.active_sector = sector + self.active_offset = len(consts.NORCOW_MAGIC_AND_VERSION) + + def get(self, key: int) -> bytes: + value, _ = self._find_item(key) + return value + + def set(self, key: int, val: bytes): + if key == consts.NORCOW_KEY_FREE: + raise RuntimeError("Norcow: key 0xFFFF is not allowed") + + found_value, pos = self._find_item(key) + if found_value is not False: + if self._is_updatable(found_value, val): + self._write(pos, key, val) + return + else: + self._delete_old(pos, found_value) + + if self.active_offset + 4 + len(val) > consts.NORCOW_SECTOR_SIZE: + self._compact() + + self._append(key, val) + + def delete(self, key: int): + if key == consts.NORCOW_KEY_FREE: + raise RuntimeError("Norcow: key 0xFFFF is not allowed") + + found_value, pos = self._find_item(key) + if found_value is False: + return False + self._delete_old(pos, found_value) + return True + + def replace(self, key: int, new_value: bytes) -> bool: + old_value, offset = self._find_item(key) + if not old_value: + raise RuntimeError("Norcow: key not found") + if len(old_value) != len(new_value): + raise RuntimeError( + "Norcow: replace works only with items of the same length" + ) + self._write(offset, key, new_value) + + def _is_updatable(self, old: bytes, new: bytes) -> bool: + """ + Item is updatable if the new value is the same or + it changes 1 to 0 only (the flash memory does not + allow to flip 0 to 1 unless you wipe it). + """ + if len(old) != len(new): + return False + if old == new: + return True + for a, b in zip(old, new): + if a & b != b: + return False + return True + + def _delete_old(self, pos: int, value: bytes): + wiped_data = b"\x00" * len(value) + self._write(pos, 0x0000, wiped_data) + + def _append(self, key: int, value: bytes): + self.active_offset += self._write(self.active_offset, key, value) + + def _write(self, pos: int, key: int, new_value: bytes) -> int: + data = pack(" consts.NORCOW_SECTOR_SIZE: + raise RuntimeError("Norcow: item too big") + self.sectors[self.active_sector][pos : pos + len(data)] = data + return len(data) + + def _find_item(self, key: int) -> (bytes, int): + offset = len(consts.NORCOW_MAGIC_AND_VERSION) + value = False + pos = offset + while True: + try: + k, v = self._read_item(offset) + if k == key: + value = v + pos = offset + except ValueError: + break + offset = offset + self._norcow_item_length(v) + return value, pos + + def _get_all_keys(self) -> (bytes, int): + offset = len(consts.NORCOW_MAGIC_AND_VERSION) + keys = set() + while True: + try: + k, v = self._read_item(offset) + keys.add(k) + except ValueError: + break + offset = offset + self._norcow_item_length(v) + return keys + + def _norcow_item_length(self, data: bytes) -> int: + # APP_ID, KEY_ID, LENGTH, DATA, ALIGNMENT + return 1 + 1 + 2 + len(data) + align4_int(len(data)) + + def _read_item(self, offset: int) -> (int, bytes): + key = self.sectors[self.active_sector][offset : offset + 2] + key = int.from_bytes(key, sys.byteorder) + if key == consts.NORCOW_KEY_FREE: + raise ValueError("Norcow: no data on this offset") + length = self.sectors[self.active_sector][offset + 2 : offset + 4] + length = int.from_bytes(length, sys.byteorder) + value = self.sectors[self.active_sector][offset + 4 : offset + 4 + length] + return key, value + + def _compact(self): + offset = len(consts.NORCOW_MAGIC_AND_VERSION) + data = list() + while True: + try: + k, v = self._read_item(offset) + if k != 0x00: + data.append((k, v)) + except ValueError: + break + offset = offset + self._norcow_item_length(v) + sector = self.active_sector + self.wipe((sector + 1) % consts.NORCOW_SECTOR_COUNT) + for key, value in data: + self._append(key, value) + + def _set_sectors(self, data): + if list(map(len, data)) != [ + consts.NORCOW_SECTOR_SIZE, + consts.NORCOW_SECTOR_SIZE, + ]: + raise RuntimeError("Norcow: set_sectors called with invalid data length") + self.sectors = [bytearray(sector) for sector in data] + + def _dump(self): + return [bytes(sector) for sector in self.sectors] diff --git a/storage/tests/python/src/pin_log.py b/storage/tests/python/src/pin_log.py new file mode 100644 index 0000000000..0416baaeb6 --- /dev/null +++ b/storage/tests/python/src/pin_log.py @@ -0,0 +1,123 @@ +from . import consts, helpers, prng + + +class PinLog: + def __init__(self, norcow): + self.norcow = norcow + + def init(self): + guard_key = self._generate_guard_key() + guard_mask, guard = self.derive_guard_mask_and_value(guard_key) + + pin_success_log = (~guard_mask & consts.ALL_FF_LOG) | guard + pin_entry_log = (~guard_mask & consts.ALL_FF_LOG) | guard + self._write_log(guard_key, pin_success_log, pin_entry_log) + + def derive_guard_mask_and_value(self, guard_key: int) -> (int, int): + if guard_key > 0xFFFFFFFF: + raise ValueError("Invalid guard key") + + guard_mask = ((guard_key & consts.LOW_MASK) << 1) | ( + (~guard_key & 0xFFFFFFFF) & consts.LOW_MASK + ) + guard = (((guard_key & consts.LOW_MASK) << 1) & guard_key) | ( + ((~guard_key & 0xFFFFFFFF) & consts.LOW_MASK) & (guard_key >> 1) + ) + return helpers.expand_to_log_size(guard_mask), helpers.expand_to_log_size(guard) + + def write_attempt(self): + guard_key, pin_success_log, pin_entry_log = self._get_logs() + guard_mask, guard = self.derive_guard_mask_and_value(guard_key) + assert (pin_entry_log & guard_mask) == guard + + clean_pin_entry_log = self.remove_guard_bits(guard_mask, pin_entry_log) + clean_pin_entry_log = clean_pin_entry_log >> 2 # set 11 to 00 + pin_entry_log = ( + clean_pin_entry_log & (~guard_mask & consts.ALL_FF_LOG) + ) | guard + + self._write_log(guard_key, pin_success_log, pin_entry_log) + + def write_success(self): + guard_key, pin_success_log, pin_entry_log = self._get_logs() + pin_success_log = pin_entry_log + + self._write_log(guard_key, pin_success_log, pin_entry_log) + + def get_failures_count(self) -> int: + guard_key, pin_succes_log, pin_entry_log = self._get_logs() + guard_mask, _ = self.derive_guard_mask_and_value(guard_key) + + pin_succes_log = self.remove_guard_bits(guard_mask, pin_succes_log) + pin_entry_log = self.remove_guard_bits(guard_mask, pin_entry_log) + + # divide by two because bits are doubled after remove_guard_bits() + return bin(pin_succes_log - pin_entry_log).count("1") // 2 + + def remove_guard_bits(self, guard_mask: int, log: int) -> int: + """ + Removes all guard bits and replaces each guard bit + with its neighbour value. + Example: 0g0gg1 -> 000011 + """ + log = log & (~guard_mask & consts.ALL_FF_LOG) + log = ((log >> 1) | log) & helpers.expand_to_log_size(consts.LOW_MASK) + log = log | (log << 1) + return log + + def _generate_guard_key(self) -> int: + while True: + r = prng.random_uniform(consts.GUARD_KEY_RANDOM_MAX) + r = (r * consts.GUARD_KEY_MODULUS + consts.GUARD_KEY_REMAINDER) & 0xFFFFFFFF + if self._check_guard_key(r): + return r + + def _check_guard_key(self, guard_key: int) -> bool: + """ + Checks if guard_key is congruent to 15 modulo 6311 and + some other conditions, see the docs. + """ + count = (guard_key & 0x22222222) + ((guard_key >> 2) & 0x22222222) + count = count + (count >> 4) + + zero_runs = ~guard_key & 0xFFFFFFFF + zero_runs = zero_runs & (zero_runs >> 2) + zero_runs = zero_runs & (zero_runs >> 1) + zero_runs = zero_runs & (zero_runs >> 1) + one_runs = guard_key + one_runs = one_runs & (one_runs >> 2) + one_runs = one_runs & (one_runs >> 1) + one_runs = one_runs & (one_runs >> 1) + + return ( + ((count & 0x0E0E0E0E) == 0x04040404) + & (one_runs == 0) + & (zero_runs == 0) + & (guard_key % consts.GUARD_KEY_MODULUS == consts.GUARD_KEY_REMAINDER) + ) + + def _get_logs(self) -> (int, int, int): + pin_log = self.norcow.get(consts.PIN_LOG_KEY) + guard_key = pin_log[: consts.PIN_LOG_GUARD_KEY_SIZE] + guard_key = helpers.word_to_int(guard_key) + guard_mask, guard = self.derive_guard_mask_and_value(guard_key) + pin_entry_log = pin_log[consts.PIN_LOG_GUARD_KEY_SIZE + consts.PIN_LOG_SIZE :] + pin_entry_log = helpers.to_int_by_words(pin_entry_log) + pin_success_log = pin_log[ + consts.PIN_LOG_GUARD_KEY_SIZE : consts.PIN_LOG_GUARD_KEY_SIZE + + consts.PIN_LOG_SIZE + ] + pin_success_log = helpers.to_int_by_words(pin_success_log) + + return guard_key, pin_success_log, pin_entry_log + + def _write_log(self, guard_key: int, pin_success_log: int, pin_entry_log: int): + pin_log = ( + helpers.int_to_word(guard_key) + + helpers.to_bytes_by_words(pin_success_log, consts.PIN_LOG_SIZE) + + helpers.to_bytes_by_words(pin_entry_log, consts.PIN_LOG_SIZE) + ) + try: + self.norcow.replace(consts.PIN_LOG_KEY, pin_log) + except RuntimeError: + self.norcow.set(consts.PIN_LOG_KEY, pin_log) diff --git a/storage/tests/python/src/prng.py b/storage/tests/python/src/prng.py new file mode 100644 index 0000000000..6461bfcccb --- /dev/null +++ b/storage/tests/python/src/prng.py @@ -0,0 +1,34 @@ +import sys + +seed = 0 + + +def random_buffer(length: int) -> bytes: + length = length + if length % 4 != 0: + raise ValueError("Use only for whole words (multiples of 4 bytes)") + b = bytearray(length) + for i in range(length): + if i % 4 == 0: + rand = random32().to_bytes(4, sys.byteorder) + b[i] = rand[i % 4] + return bytes(b) + + +def random_reseed(reseed: int = 0): + global seed + seed = reseed + + +def random32(): + global seed + seed = (1664525 * seed + 1013904223) & 0xFFFFFFFF + return seed + + +def random_uniform(n: int): + max = 0xFFFFFFFF - (0xFFFFFFFF % n) + while True: + x = random32() + if x < max: + return x // (max // n) diff --git a/storage/tests/python/src/storage.py b/storage/tests/python/src/storage.py new file mode 100644 index 0000000000..e223d287f2 --- /dev/null +++ b/storage/tests/python/src/storage.py @@ -0,0 +1,237 @@ +import hashlib +import sys + +from . import consts, crypto, helpers, prng +from .norcow import Norcow +from .pin_log import PinLog + + +class Storage: + def __init__(self): + self.initialized = False + self.unlocked = False + self.dek = None + self.sak = None + self.nc = Norcow() + self.pin_log = PinLog(self.nc) + + def init(self, hardware_salt: bytes = b""): + """ + Initializes storage. Normally we would check if EDEK is already present, + but we simplify things in the python version and suppose we are starting + a new storage each time. + """ + self.nc.init() + self.initialized = True + self.hw_salt_hash = hashlib.sha256(hardware_salt).digest() + + edek_esak_pvc = self.nc.get(consts.EDEK_ESEK_PVC_KEY) + if not edek_esak_pvc: + self._init_pin() + + def _init_pin(self): + """ + Initalizes PIN counters, generates random + Data Encryption Key and Storage Authentication Key + """ + self.dek = prng.random_buffer(consts.DEK_SIZE) + self.sak = prng.random_buffer(consts.SAK_SIZE) + + self.nc.set(consts.SAT_KEY, crypto.init_hmacs(self.sak)) + self._set_encrypt(consts.VERSION_KEY, b"\x01\x00\x00\x00") + self.pin_log.init() + self._set_pin(consts.PIN_EMPTY) + self.unlocked = False + + def _set_pin(self, pin: int): + random_salt = prng.random_buffer(consts.PIN_SALT_SIZE) + salt = self.hw_salt_hash + random_salt + kek, keiv = crypto.derive_kek_keiv(salt, pin) + + # Encrypted Data Encryption Key and Encrypted Storage Authentication Key + edek_esak, tag = crypto.chacha_poly_encrypt(kek, keiv, self.dek + self.sak) + # Pin Verification Code + pvc = tag[: consts.PVC_SIZE] + + self.nc.set(consts.EDEK_ESEK_PVC_KEY, random_salt + edek_esak + pvc) + if pin == consts.PIN_EMPTY: + self._set_bool(consts.PIN_NOT_SET_KEY, True) + else: + self._set_bool(consts.PIN_NOT_SET_KEY, False) + + def wipe(self): + self.nc.wipe() + self._init_pin() + + def check_pin(self, pin: int) -> bool: + self.pin_log.write_attempt() + + data = self.nc.get(consts.EDEK_ESEK_PVC_KEY) + salt = self.hw_salt_hash + data[: consts.PIN_SALT_SIZE] + edek_esak = data[consts.PIN_SALT_SIZE : -consts.PVC_SIZE] + pvc = data[-consts.PVC_SIZE :] + + try: + dek, sak = crypto.decrypt_edek_esak(pin, salt, edek_esak, pvc) + self.pin_log.write_success() + self.dek = dek + self.sak = sak + return True + except crypto.InvalidPinError: + fails = self.pin_log.get_failures_count() + if fails >= consts.PIN_MAX_TRIES: + self.wipe() + return False + + def lock(self) -> None: + self.unlocked = False + + def unlock(self, pin: int) -> bool: + if not self.initialized or not self.check_pin(pin): + return False + + version = self._decrypt(consts.VERSION_KEY) + if version != consts.NORCOW_VERSION: + return False + + self.unlocked = True + return True + + def has_pin(self) -> bool: + val = self.nc.get(consts.PIN_NOT_SET_KEY) + return val != consts.TRUE_BYTE + + def get_pin_rem(self) -> int: + return consts.PIN_MAX_TRIES - self.pin_log.get_failures_count() + + def change_pin(self, oldpin: int, newpin: int) -> bool: + if not self.initialized or not self.unlocked: + return False + if not self.check_pin(oldpin): + return False + self._set_pin(newpin) + return True + + def get(self, key: int) -> bytes: + app = key >> 8 + if not self.initialized or consts.is_app_private(app): + raise RuntimeError("Storage not initialized or app is private") + if not self.unlocked and not consts.is_app_public(app): + # public fields can be read from an unlocked device + raise RuntimeError("Storage locked") + if consts.is_app_public(app): + return self.nc.get(key) + return self._get_encrypted(key) + + def set(self, key: int, val: bytes) -> bool: + app = key >> 8 + self._check_lock(app) + if consts.is_app_public(app): + return self.nc.set(key, val) + return self._set_encrypt(key, val) + + def set_counter(self, key: int, val: int): + app = key >> 8 + if not consts.is_app_public(app): + raise RuntimeError("Counter can be set only for public items") + counter = val.to_bytes(4, sys.byteorder) + bytearray( + b"\xFF" * consts.COUNTER_TAIL_SIZE + ) + self.set(key, counter) + + def next_counter(self, key: int) -> int: + app = key >> 8 + self._check_lock(app) + + current = self.get(key) + if current is False: + self.set_counter(key, 0) + return 0 + + base = int.from_bytes(current[:4], sys.byteorder) + tail = helpers.to_int_by_words(current[4:]) + tail_count = "{0:064b}".format(tail).count("0") + increased_count = base + tail_count + 1 + + if tail_count == consts.COUNTER_MAX_TAIL: + self.set_counter(key, increased_count) + return increased_count + + self.set( + key, + current[:4] + + helpers.to_bytes_by_words(tail >> 1, consts.COUNTER_TAIL_SIZE), + ) + return increased_count + + def delete(self, key: int) -> bool: + app = key >> 8 + self._check_lock(app) + ret = self.nc.delete(key) + if consts.is_app_protected(app): + sat = self._calculate_authentication_tag() + self.nc.set(consts.SAT_KEY, sat) + return ret + + def _check_lock(self, app: int): + if not self.initialized or consts.is_app_private(app): + raise RuntimeError("Storage not initialized or app is private") + if not self.unlocked and not consts.is_app_lock_writable(app): + raise RuntimeError("Storage locked and app is not public-writable") + + def _get_encrypted(self, key: int) -> bytes: + if not consts.is_app_protected(key): + raise RuntimeError("Only protected values are encrypted") + sat = self.nc.get(consts.SAT_KEY) + if not sat: + raise RuntimeError("SAT not found") + if sat != self._calculate_authentication_tag(): + raise RuntimeError("Storage authentication tag mismatch") + return self._decrypt(key) + + def _decrypt(self, key: int) -> bytes: + data = self.nc.get(key) + iv = data[: consts.CHACHA_IV_SIZE] + # cipher text with MAC + tag = data[ + consts.CHACHA_IV_SIZE : consts.CHACHA_IV_SIZE + consts.POLY1305_MAC_SIZE + ] + ciphertext = data[consts.CHACHA_IV_SIZE + consts.POLY1305_MAC_SIZE :] + return crypto.chacha_poly_decrypt( + self.dek, key, iv, ciphertext + tag, key.to_bytes(2, sys.byteorder) + ) + + def _set_encrypt(self, key: int, val: bytes): + # In C, data are preallocated beforehand for encrypted values, + # to match the behaviour we do the same. + preallocate = b"\xFF" * ( + consts.CHACHA_IV_SIZE + len(val) + consts.POLY1305_MAC_SIZE + ) + self.nc.set(key, preallocate) + if consts.is_app_protected(key >> 8): + sat = self._calculate_authentication_tag() + self.nc.set(consts.SAT_KEY, sat) + + iv = prng.random_buffer(consts.CHACHA_IV_SIZE) + cipher_text, tag = crypto.chacha_poly_encrypt( + self.dek, iv, val, key.to_bytes(2, sys.byteorder) + ) + return self.nc.replace(key, iv + tag + cipher_text) + + def _calculate_authentication_tag(self) -> bytes: + keys = [] + for key in self.nc._get_all_keys(): + if consts.is_app_protected(key >> 8): + keys.append(key.to_bytes(2, sys.byteorder)) + if not keys: + return crypto.init_hmacs(self.sak) + return crypto.calculate_hmacs(self.sak, keys) + + def _set_bool(self, key: int, val: bool) -> bool: + if val: + return self.nc.set(key, consts.TRUE_BYTE) + # False is stored as an empty value + return self.nc.set(key, consts.FALSE_BYTE) + + def _dump(self) -> bytes: + return self.nc._dump() diff --git a/storage/tests/python/tests/__init__.py b/storage/tests/python/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/storage/tests/python/tests/common.py b/storage/tests/python/tests/common.py new file mode 100644 index 0000000000..e1497054a0 --- /dev/null +++ b/storage/tests/python/tests/common.py @@ -0,0 +1,2 @@ +def all_ff_bytes(data: bytes): + return all(i == 0xFF for i in data) diff --git a/storage/tests/python/tests/conftest.py b/storage/tests/python/tests/conftest.py new file mode 100644 index 0000000000..9c908e2d1d --- /dev/null +++ b/storage/tests/python/tests/conftest.py @@ -0,0 +1,8 @@ +from ..src import prng + + +def pytest_runtest_teardown(item): + """ + Called after each test ran to reset the PRNG + """ + prng.seed = 0 diff --git a/storage/tests/python/tests/test_helpers.py b/storage/tests/python/tests/test_helpers.py new file mode 100644 index 0000000000..4e5a58f712 --- /dev/null +++ b/storage/tests/python/tests/test_helpers.py @@ -0,0 +1,13 @@ +from ..src import consts, helpers + + +def test_read_bytes_by_words(): + array = b"\x04\x03\x02\x01\x08\x07\x06\x05" + n = helpers.to_int_by_words(array) + assert n == 0x0102030405060708 + assert array == helpers.to_bytes_by_words(n, consts.PIN_LOG_SIZE)[56:] + + array = b"\xFF\xFF\xFF\x01\x01\x05\x09\x01" + n = helpers.to_int_by_words(array) + assert n == 0x01FFFFFF01090501 + assert array == helpers.to_bytes_by_words(n, consts.PIN_LOG_SIZE)[56:] diff --git a/storage/tests/python/tests/test_norcow.py b/storage/tests/python/tests/test_norcow.py new file mode 100644 index 0000000000..38b9369c8e --- /dev/null +++ b/storage/tests/python/tests/test_norcow.py @@ -0,0 +1,155 @@ +import pytest + +from ..src import consts, norcow +from . import common + + +def test_norcow_set(): + n = norcow.Norcow() + n.init() + n.set(0x0001, b"123") + data = n._dump()[0][:256] + assert data[:8] == b"NRC2\xFE\xFF\xFF\xFF" + assert data[8:10] == b"\x01\x00" # app + key + assert data[10:12] == b"\x03\x00" # length + assert data[12:15] == b"123" # data + assert common.all_ff_bytes(data[16:]) + + n.wipe() + n.set(0x0901, b"hello") + data = n._dump()[0][:256] + assert data[:8] == b"NRC2\xFE\xFF\xFF\xFF" + assert data[8:10] == b"\x01\x09" # app + key + assert data[10:12] == b"\x05\x00" # length + assert data[12:17] == b"hello" # data + assert data[17:20] == b"\x00\x00\x00" # alignment + assert common.all_ff_bytes(data[20:]) + + offset = 20 + n.set(0x0102, b"world!") + data = n._dump()[0][:256] + assert data[offset : offset + 2] == b"\x02\x01" # app + key + assert data[offset + 2 : offset + 4] == b"\x06\x00" # length + assert data[offset + 4 : offset + 10] == b"world!" # data + assert data[offset + 10 : offset + 12] == b"\x00\x00" # alignment + assert common.all_ff_bytes(data[offset + 12 :]) + + +def test_norcow_read_item(): + n = norcow.Norcow() + n.init() + n.set(0x0001, b"123") + n.set(0x0002, b"456") + n.set(0x0101, b"789") + key, value = n._read_item(16) + assert key == 0x0002 + assert value == b"456" + key, value = n._read_item(24) + assert key == 0x0101 + assert value == b"789" + + with pytest.raises(ValueError) as e: + key, value = n._read_item(204) + assert "no data" in str(e) + + +def test_norcow_get_item(): + n = norcow.Norcow() + n.init() + n.set(0x0001, b"123") + n.set(0x0002, b"456") + n.set(0x0101, b"789") + value = n.get(0x0001) + assert value == b"123" + assert ( + n._dump()[0][:40].hex() + == "4e524332feffffff010003003132330002000300343536000101030037383900ffffffffffffffff" + ) + + # replacing item with the same value (update) + n.set(0x0101, b"789") + value = n.get(0x0101) + assert value == b"789" + assert ( + n._dump()[0][:40].hex() + == "4e524332feffffff010003003132330002000300343536000101030037383900ffffffffffffffff" + ) + + # replacing item with value with less 1 bits than before (update) + n.set(0x0101, b"788") + value = n.get(0x0101) + assert value == b"788" + assert ( + n._dump()[0][:40].hex() + == "4e524332feffffff010003003132330002000300343536000101030037383800ffffffffffffffff" + ) + + # replacing item with value with more 1 bits than before (wipe and new entry) + n.set(0x0101, b"787") + value = n.get(0x0101) + assert value == b"787" + assert ( + n._dump()[0][:44].hex() + == "4e524332feffffff0100030031323300020003003435360000000300000000000101030037383700ffffffff" + ) + + n.set(0x0002, b"world") + n.set(0x0002, b"earth") + value = n.get(0x0002) + assert value == b"earth" + + +def test_norcow_replace_item(): + n = norcow.Norcow() + n.init() + n.set(0x0001, b"123") + n.set(0x0002, b"456") + n.set(0x0101, b"789") + value = n.get(0x0002) + assert value == b"456" + + n.replace(0x0001, b"000") + value = n.get(0x0001) + assert value == b"000" + + n.replace(0x0002, b"111") + value = n.get(0x0002) + assert value == b"111" + value = n.get(0x0001) + assert value == b"000" + value = n.get(0x0101) + assert value == b"789" + + with pytest.raises(RuntimeError) as e: + n.replace(0x0001, b"00000") + assert "same length" in str(e) + + +def test_norcow_compact(): + n = norcow.Norcow() + n.init() + n.set(0x0101, b"ahoj") + n.set(0x0101, b"a" * (consts.NORCOW_SECTOR_SIZE - 100)) + n.set(0x0101, b"hello") + + n.set(0x0103, b"123456789x") + n.set(0x0104, b"123456789x") + n.set(0x0105, b"123456789x") + n.set(0x0106, b"123456789x") + mem = n._dump() + assert mem[0][:8] == consts.NORCOW_MAGIC_AND_VERSION + assert mem[0][200:300] == b"\x00" * 100 + + # compact is triggered + n.set(0x0107, b"123456789x") + mem = n._dump() + # assert the other sector is active + assert mem[1][:8] == consts.NORCOW_MAGIC_AND_VERSION + # assert the deleted item was not copied + assert mem[0][200:300] == b"\xff" * 100 + + n.set(0x0108, b"123456789x") + n.set(0x0109, b"123456789x") + + assert n.get(0x0101) == b"hello" + assert n.get(0x0103) == b"123456789x" diff --git a/storage/tests/python/tests/test_pin.py b/storage/tests/python/tests/test_pin.py new file mode 100644 index 0000000000..a6f517922d --- /dev/null +++ b/storage/tests/python/tests/test_pin.py @@ -0,0 +1,28 @@ +from ..src.storage import Storage + + +def test_set_pin_success(): + s = Storage() + hw_salt = b"\x00\x00\x00\x00\x00\x00" + s.init(hw_salt) + s._set_pin(1) + assert s.unlock(1) + + s = Storage() + s.init(hw_salt) + s._set_pin(229922) + assert s.unlock(229922) + + +def test_set_pin_failure(): + s = Storage() + hw_salt = b"\x00\x00\x00\x00\x00\x00" + s.init(hw_salt) + s._set_pin(1) + assert s.unlock(1) + assert not s.unlock(1234) + + s = Storage() + s.init(hw_salt) + s._set_pin(229922) + assert not s.unlock(1122992211) diff --git a/storage/tests/python/tests/test_pin_log.py b/storage/tests/python/tests/test_pin_log.py new file mode 100644 index 0000000000..5ef72c521e --- /dev/null +++ b/storage/tests/python/tests/test_pin_log.py @@ -0,0 +1,12 @@ +from ..src import pin_log + + +def test_generate_guard_key(): + p = pin_log.PinLog(None) + + assert p._generate_guard_key() == 2267428717 + assert p._generate_guard_key() == 1653399972 + assert p._check_guard_key(2267428717) + assert p._check_guard_key(1653399972) + assert p._check_guard_key(3706993061) + assert p._check_guard_key(3593237286) diff --git a/storage/tests/python/tests/test_prng.py b/storage/tests/python/tests/test_prng.py new file mode 100644 index 0000000000..5d2ae8e43c --- /dev/null +++ b/storage/tests/python/tests/test_prng.py @@ -0,0 +1,11 @@ +from ..src import prng + + +def test_prng(): + buf = prng.random_buffer(4) + assert buf == b"\x5f\xf3\x6e\x3c" + buf = prng.random_buffer(4) + assert buf == b"\x32\x29\x50\x47" + + buf = prng.random_buffer(8) + assert buf == b"\xe9\xf6\xcc\xd1\x34\x53\xf9\xaa" diff --git a/storage/tests/test.py b/storage/tests/test.py new file mode 100755 index 0000000000..0ee5f4a0e4 --- /dev/null +++ b/storage/tests/test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +from hashlib import sha256 + +from c.storage import Storage as StorageC +from python.src.storage import Storage as StoragePy + + +def hash(data): + return sha256(data).hexdigest()[:16] + + +# Strings for testing ChaCha20 encryption. +test_strings = [ + b"Short string.", + b"", + b"Although ChaCha20 is a stream cipher, it operates on blocks of 64 bytes. This string is over 152 bytes in length so that we test multi-block encryption.", + b"This string is exactly 64 bytes long, that is exactly one block.", +] + +# Unique device ID for testing. +uid = b"\x67\xce\x6a\xe8\xf7\x9b\x73\x96\x83\x88\x21\x5e" + +sc = StorageC() +sp = StoragePy() +a = [] + +for s in [sc, sp]: + print(s.__class__) + s.init(uid) + assert s.unlock(3) is False + assert s.unlock(1) is True + s.set(0xBEEF, b"hello") + s.set(0x03FE, b"world!") + s.set(0xBEEF, b"satoshi") + s.set(0xBEEF, b"Satoshi") + for value in test_strings: + s.set(0x0301, value) + assert s.get(0x0301) == value + d = s._dump() + print(d[0][:512].hex()) + h = [hash(x) for x in d] + print(h) + a.append(h[0]) + a.append(h[1]) + print() + +print("-------------") +print("Equals:", a[0] == a[2] and a[1] == a[3]) diff --git a/storage/tests/tests/__init__.py b/storage/tests/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/storage/tests/tests/common.py b/storage/tests/tests/common.py new file mode 100644 index 0000000000..c66ea8091e --- /dev/null +++ b/storage/tests/tests/common.py @@ -0,0 +1,23 @@ +from c.storage import Storage as StorageC +from python.src import prng +from python.src.storage import Storage as StoragePy + +test_uid = b"\x67\xce\x6a\xe8\xf7\x9b\x73\x96\x83\x88\x21\x5e" + + +def init( + unlock: bool = False, reseed: int = 0, uid: int = test_uid +) -> (StorageC, StoragePy): + sc = StorageC() + sp = StoragePy() + sc.lib.random_reseed(reseed) + prng.random_reseed(reseed) + for s in (sc, sp): + s.init(uid) + if unlock: + assert s.unlock(1) + return sc, sp + + +def memory_equals(sc, sp) -> bool: + return sc._dump() == sp._dump() diff --git a/storage/tests/tests/storage_model.py b/storage/tests/tests/storage_model.py new file mode 100644 index 0000000000..06eedc4600 --- /dev/null +++ b/storage/tests/tests/storage_model.py @@ -0,0 +1,68 @@ +# Logical storage model used for testing. + + +class StorageModel: + _EMPTY_PIN = 1 + _PIN_MAX_TRIES = 16 + + def __init__(self) -> None: + self.wipe() + + def init(self, salt: bytes) -> None: + self.unlocked = False + + def wipe(self) -> None: + self.unlocked = False + self.pin = 1 + self.pin_rem = self._PIN_MAX_TRIES + self.dict = {} + + def lock(self) -> None: + self.unlocked = False + + def unlock(self, pin: int) -> bool: + if pin == self.pin: + self.pin_rem = self._PIN_MAX_TRIES + self.unlocked = True + return True + else: + self.pin_rem -= 1 + if self.pin_rem <= 0: + self.wipe() + return False + + def has_pin(self) -> bool: + return self.pin != self._EMPTY_PIN + + def get_pin_rem(self) -> int: + return self.pin_rem + + def change_pin(self, oldpin: int, newpin: int) -> bool: + if self.unlocked and self.unlock(oldpin): + self.pin = newpin + return True + else: + return False + + def get(self, key: int) -> bytes: + if (key & 0x8000 != 0 or self.unlocked) and self.dict.get(key) is not None: + return self.dict[key] + raise RuntimeError("Failed to find key in storage.") + + def set(self, key: int, val: bytes) -> None: + if self.unlocked: + self.dict[key] = val + else: + raise RuntimeError("Failed to set value in storage.") + + def delete(self, key: int) -> bool: + if not self.unlocked: + return False + try: + self.dict.pop(key) + except KeyError: + return False + return True + + def __iter__(self): + return iter(self.dict.items()) diff --git a/storage/tests/tests/test_compact.py b/storage/tests/tests/test_compact.py new file mode 100644 index 0000000000..671b5d6595 --- /dev/null +++ b/storage/tests/tests/test_compact.py @@ -0,0 +1,32 @@ +import pytest + +from python.src import consts + +from . import common + + +def test_compact(): + sc, sp = common.init(unlock=True) + for s in (sc, sp): + s.set(0xBEEF, b"hello") + s.set(0xBEEF, b"asdasdasdasd") + s.set(0xBEEF, b"fsdasdasdasdasdsadasdsadasdasd") + s.set(0x0101, b"a" * (consts.NORCOW_SECTOR_SIZE - 600)) + s.set(0x03FE, b"world!") + s.set(0x04FE, b"world!xfffffffffffffffffffffffffffff") + s.set(0x05FE, b"world!affffffffffffffffffffffffffffff") + s.set(0x0101, b"s") + s.set(0x06FE, b"world!aaaaaaaaaaaaaaaaaaaaaaaaab") + s.set(0x07FE, b"worxxxxxxxxxxxxxxxxxx") + s.set(0x09EE, b"worxxxxxxxxxxxxxxxxxx") + assert common.memory_equals(sc, sp) + + sc, sp = common.init(unlock=True) + for s in (sc, sp): + s.set(0xBEEF, b"asdasdasdasd") + s.set(0xBEEF, b"fsdasdasdasdasdsadasdsadasdasd") + s.set(0x8101, b"a" * (consts.NORCOW_SECTOR_SIZE - 1000)) + with pytest.raises(RuntimeError): + s.set(0x0101, b"a" * (consts.NORCOW_SECTOR_SIZE - 100)) + s.set(0x0101, b"hello") + assert common.memory_equals(sc, sp) diff --git a/storage/tests/tests/test_pin.py b/storage/tests/tests/test_pin.py new file mode 100644 index 0000000000..915deb4b31 --- /dev/null +++ b/storage/tests/tests/test_pin.py @@ -0,0 +1,66 @@ +import pytest + +from python.src import consts + +from . import common + + +def test_init_pin(): + sc, sp = common.init(uid=b"\x00\x00\x00\x00\x00\x00") + assert common.memory_equals(sc, sp) + + sc, sp = common.init(uid=b"\x22\x00\xDD\x00\x00\xBE") + assert common.memory_equals(sc, sp) + + +def test_change_pin(): + sc, sp = common.init(unlock=True) + for s in (sc, sp): + assert s.change_pin(1, 2221) + # invalid PIN + assert not s.change_pin(99991, 1) + assert s.unlock(2221) + assert s.change_pin(2221, 999991) + assert s.change_pin(999991, 991) + assert s.unlock(991) + assert not s.unlock(99991) + + assert common.memory_equals(sc, sp) + + +def test_has_pin(): + sc, sp = common.init() + for s in (sc, sp): + assert not s.has_pin() + assert s.unlock(1) + assert not s.has_pin() + assert s.change_pin(1, 221) + assert s.has_pin() + assert s.change_pin(221, 1) + assert not s.has_pin() + + +def test_wipe_after_max_pin(): + sc, sp = common.init(unlock=True) + for s in (sc, sp): + assert s.change_pin(1, 2221) + assert s.unlock(2221) + s.set(0x0202, b"Hello") + + # try an invalid PIN MAX - 1 times + for i in range(consts.PIN_MAX_TRIES - 1): + assert not s.unlock(99991) + # this should pass + assert s.unlock(2221) + assert s.get(0x0202) == b"Hello" + + # try an invalid PIN MAX times, the storage should get wiped + for i in range(consts.PIN_MAX_TRIES): + assert not s.unlock(99991) + assert i == consts.PIN_MAX_TRIES - 1 + # this should return False and raise an exception, the storage is wiped + assert not s.unlock(2221) + with pytest.raises(RuntimeError): + assert s.get(0x0202) == b"Hello" + + assert common.memory_equals(sc, sp) diff --git a/storage/tests/tests/test_random.py b/storage/tests/tests/test_random.py new file mode 100644 index 0000000000..01f4f71c64 --- /dev/null +++ b/storage/tests/tests/test_random.py @@ -0,0 +1,83 @@ +import hypothesis.strategies as st +from hypothesis import assume, settings +from hypothesis.stateful import Bundle, RuleBasedStateMachine, invariant, rule + +from . import common +from .storage_model import StorageModel + + +class StorageComparison(RuleBasedStateMachine): + def __init__(self): + super(StorageComparison, self).__init__() + self.sc, self.sp = common.init(unlock=True) + self.sm = StorageModel() + self.sm.init(b"") + self.sm.unlock(1) + self.storages = (self.sc, self.sp, self.sm) + + keys = Bundle("keys") + values = Bundle("values") + pins = Bundle("pins") + + @rule(target=keys, app=st.integers(1, 0xFF), key=st.integers(0, 0xFF)) + def k(self, app, key): + return (app << 8) | key + + @rule(target=values, v=st.binary(min_size=0, max_size=10000)) + def v(self, v): + return v + + @rule(target=pins, p=st.integers(1, 3)) + def p(self, p): + return p + + @rule(k=keys, v=values) + def set(self, k, v): + assume(k != 0xFFFF) + for s in self.storages: + s.set(k, v) + + @rule(k=keys) + def delete(self, k): + assume(k != 0xFFFF) + assert len(set(s.delete(k) for s in self.storages)) == 1 + + @rule(p=pins) + def check_pin(self, p): + assert len(set(s.unlock(p) for s in self.storages)) == 1 + self.ensure_unlocked() + + @rule(oldpin=pins, newpin=pins) + def change_pin(self, oldpin, newpin): + assert len(set(s.change_pin(oldpin, newpin) for s in self.storages)) == 1 + self.ensure_unlocked() + + @rule() + def lock(self): + for s in self.storages: + s.lock() + self.ensure_unlocked() + + @invariant() + def values_agree(self): + for k, v in self.sm: + assert self.sc.get(k) == v + + @invariant() + def dumps_agree(self): + assert self.sc._dump() == self.sp._dump() + + @invariant() + def pin_counters_agree(self): + assert len(set(s.get_pin_rem() for s in self.storages)) == 1 + + def ensure_unlocked(self): + if not self.sm.unlocked: + for s in self.storages: + assert s.unlock(self.sm.pin) + + +TestStorageComparison = StorageComparison.TestCase +TestStorageComparison.settings = settings( + deadline=2000, max_examples=30, stateful_step_count=50 +) diff --git a/storage/tests/tests/test_random_upgrade.py b/storage/tests/tests/test_random_upgrade.py new file mode 100644 index 0000000000..4aaa1a507b --- /dev/null +++ b/storage/tests/tests/test_random_upgrade.py @@ -0,0 +1,73 @@ +import hypothesis.strategies as st +from hypothesis import assume, settings +from hypothesis.stateful import Bundle, RuleBasedStateMachine, invariant, rule + +from c0.storage import Storage as StorageC0 +from c.storage import Storage as StorageC + +from . import common +from .storage_model import StorageModel + + +class StorageUpgrade(RuleBasedStateMachine): + def __init__(self): + super(StorageUpgrade, self).__init__() + self.sc = StorageC0() + self.sc.init() + self.sm = StorageModel() + self.sm.init(common.test_uid) + self.storages = (self.sc, self.sm) + self.ensure_unlocked() + + keys = Bundle("keys") + values = Bundle("values") + pins = Bundle("pins") + + @rule(target=keys, app=st.integers(1, 0xFF), key=st.integers(0, 0xFF)) + def k(self, app, key): + return (app << 8) | key + + @rule(target=values, v=st.binary(min_size=0, max_size=10000)) + def v(self, v): + return v + + @rule(target=pins, p=st.integers(1, 3)) + def p(self, p): + return p + + @rule(k=keys, v=values) + def set(self, k, v): + assume(k != 0xFFFF) + for s in self.storages: + s.set(k, v) + + @rule(p=pins) + def check_pin(self, p): + assert self.sm.unlock(p) == self.sc.check_pin(p) + self.ensure_unlocked() + + @rule(oldpin=pins, newpin=pins) + def change_pin(self, oldpin, newpin): + assert self.sm.change_pin(oldpin, newpin) == self.sc.change_pin(oldpin, newpin) + self.ensure_unlocked() + + @invariant() + def check_upgrade(self): + sc1 = StorageC() + sc1._set_flash_buffer(self.sc._get_flash_buffer()) + sc1.init(common.test_uid) + assert self.sm.get_pin_rem() == sc1.get_pin_rem() + assert sc1.unlock(self.sm.pin) + for k, v in self.sm: + assert sc1.get(k) == v + + def ensure_unlocked(self): + if not self.sm.unlocked: + for s in self.storages: + assert s.unlock(self.sm.pin) + + +TestStorageUpgrade = StorageUpgrade.TestCase +TestStorageUpgrade.settings = settings( + deadline=None, max_examples=30, stateful_step_count=50 +) diff --git a/storage/tests/tests/test_set_get.py b/storage/tests/tests/test_set_get.py new file mode 100644 index 0000000000..727bc20a27 --- /dev/null +++ b/storage/tests/tests/test_set_get.py @@ -0,0 +1,178 @@ +import pytest + +from . import common + +# Strings for testing ChaCha20 encryption. +chacha_strings = [ + b"Short string.", + b"", + b"Although ChaCha20 is a stream cipher, it operates on blocks of 64 bytes. This string is over 152 bytes in length so that we test multi-block encryption.", + b"This string is exactly 64 bytes long, that is exactly one block.", +] + + +def test_set_get(): + sc, sp = common.init(unlock=True) + for s in (sc, sp): + s.set(0xBEEF, b"Hello") + s.set(0xCAFE, b"world! ") + s.set(0xDEAD, b"How\n") + s.set(0xAAAA, b"are") + s.set(0x0901, b"you?") + s.set(0x0902, b"Lorem") + s.set(0x0903, b"ipsum") + s.set(0xDEAD, b"A\n") + s.set(0xDEAD, b"AAAAAAAAAAA") + s.set(0x2200, b"BBBB") + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + s.change_pin(1, 2221) + s.change_pin(2221, 991) + s.set(0xAAAA, b"something else") + assert common.memory_equals(sc, sp) + + # check data are not changed by gets + datasc = sc._dump() + datasp = sp._dump() + + for s in (sc, sp): + assert s.get(0xAAAA) == b"something else" + assert s.get(0x0901) == b"you?" + assert s.get(0x0902) == b"Lorem" + assert s.get(0x0903) == b"ipsum" + assert s.get(0xDEAD) == b"AAAAAAAAAAA" + assert s.get(0x2200) == b"BBBB" + + assert datasc == sc._dump() + assert datasp == sp._dump() + + # test locked storage + for s in (sc, sp): + s.lock() + with pytest.raises(RuntimeError): + s.set(0xAAAA, b"test public") + with pytest.raises(RuntimeError): + s.set(0x0901, b"test protected") + with pytest.raises(RuntimeError): + s.get(0x0901) + assert s.get(0xAAAA) == b"something else" + + # check that storage functions after unlock + for s in (sc, sp): + s.unlock(991) + s.set(0xAAAA, b"public") + s.set(0x0902, b"protected") + assert s.get(0xAAAA) == b"public" + assert s.get(0x0902) == b"protected" + + # test delete + for s in (sc, sp): + assert s.delete(0x0902) + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + assert not s.delete(0x7777) + assert not s.delete(0x0902) + assert common.memory_equals(sc, sp) + + +def test_invalid_key(): + for s in common.init(unlock=True): + with pytest.raises(RuntimeError): + s.set(0xFFFF, b"Hello") + + +def test_chacha_strings(): + sc, sp = common.init(unlock=True) + for s in (sc, sp): + for i, string in enumerate(chacha_strings): + s.set(0x0301 + i, string) + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + for i, string in enumerate(chacha_strings): + assert s.get(0x0301 + i) == string + + +def test_set_repeated(): + test_strings = [[0x0501, b""], [0x0502, b"test"], [0x8501, b""], [0x8502, b"test"]] + sc, sp = common.init(unlock=True) + for s in (sc, sp): + for key, val in test_strings: + s.set(key, val) + s.set(key, val) + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + for key, val in test_strings: + s.set(key, val) + assert common.memory_equals(sc, sp) + + for key, val in test_strings: + for s in (sc, sp): + assert s.delete(key) + assert common.memory_equals(sc, sp) + + +def test_set_similar(): + sc, sp = common.init(unlock=True) + for s in (sc, sp): + s.set(0xBEEF, b"Satoshi") + s.set(0xBEEF, b"satoshi") + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + s.wipe() + s.unlock(1) + s.set(0xBEEF, b"satoshi") + s.set(0xBEEF, b"Satoshi") + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + s.wipe() + s.unlock(1) + s.set(0xBEEF, b"satoshi") + s.set(0xBEEF, b"Satoshi") + s.set(0xBEEF, b"Satoshi") + s.set(0xBEEF, b"SatosHi") + s.set(0xBEEF, b"satoshi") + s.set(0xBEEF, b"satoshi\x00") + assert common.memory_equals(sc, sp) + + +def test_set_locked(): + sc, sp = common.init() + for s in (sc, sp): + with pytest.raises(RuntimeError): + s.set(0x0303, b"test") + with pytest.raises(RuntimeError): + s.set(0x8003, b"test") + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + s.set(0xC001, b"Ahoj") + s.set(0xC003, b"test") + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + s.get(0xC001) == b"Ahoj" + s.get(0xC003) == b"test" + + +def test_counter(): + sc, sp = common.init(unlock=True) + for i in range(0, 200): + for s in (sc, sp): + assert i == s.next_counter(0xC001) + assert common.memory_equals(sc, sp) + + for s in (sc, sp): + s.lock() + s.set_counter(0xC001, 500) + assert common.memory_equals(sc, sp) + + for i in range(501, 700): + for s in (sc, sp): + assert i == s.next_counter(0xC001) + assert common.memory_equals(sc, sp) diff --git a/storage/tests/tests/test_upgrade.py b/storage/tests/tests/test_upgrade.py new file mode 100644 index 0000000000..a089438ca8 --- /dev/null +++ b/storage/tests/tests/test_upgrade.py @@ -0,0 +1,75 @@ +from c0.storage import Storage as StorageC0 +from c.storage import Storage as StorageC +from python.src.storage import Storage as StoragePy + +from . import common + +# Strings for testing ChaCha20 encryption. +chacha_strings = [ + b"Short string.", + b"", + b"Although ChaCha20 is a stream cipher, it operates on blocks of 64 bytes. This string is over 152 bytes in length so that we test multi-block encryption.", + b"This string is exactly 64 bytes long, that is exactly one block.", +] + + +def set_values(s): + s.set(0xBEEF, b"Hello") + s.set(0xCAFE, b"world! ") + s.set(0xDEAD, b"How\n") + s.change_pin(1, 1222) + s.set(0xAAAA, b"are") + s.set(0x0901, b"you?") + s.set(0x0902, b"Lorem") + s.set(0x0903, b"ipsum") + s.change_pin(1222, 199) + s.set(0xDEAD, b"A\n") + s.set(0xDEAD, b"AAAAAAAAAAA") + s.set(0x2200, b"BBBB") + for i, string in enumerate(chacha_strings): + s.set(0x0301 + i, string) + + +def check_values(s): + assert s.unlock(199) + assert s.get(0xAAAA) == b"are" + assert s.get(0x0901) == b"you?" + assert s.get(0x0902) == b"Lorem" + assert s.get(0x0903) == b"ipsum" + assert s.get(0xDEAD) == b"AAAAAAAAAAA" + assert s.get(0x2200) == b"BBBB" + for i, string in enumerate(chacha_strings): + assert s.get(0x0301 + i) == string + + +def test_upgrade(): + sc0 = StorageC0() + sc0.init() + assert sc0.unlock(1) + set_values(sc0) + for _ in range(10): + assert not sc0.unlock(3) + + sc1 = StorageC() + sc1._set_flash_buffer(sc0._get_flash_buffer()) + sc1.init(common.test_uid) + assert sc1.get_pin_rem() == 6 + check_values(sc1) + + +def test_python_set_sectors(): + sp0 = StoragePy() + sp0.init(common.test_uid) + assert sp0.unlock(1) + set_values(sp0) + for _ in range(10): + assert not sp0.unlock(3) + assert sp0.get_pin_rem() == 6 + + sp1 = StoragePy() + sp1.nc._set_sectors(sp0._dump()) + sp1.init(common.test_uid) + common.memory_equals(sp0, sp1) + + assert sp1.get_pin_rem() == 6 + check_values(sp1) diff --git a/core/tools/clang-format-check b/tools/clang-format-check similarity index 100% rename from core/tools/clang-format-check rename to tools/clang-format-check diff --git a/core/help.awk b/tools/help.awk similarity index 100% rename from core/help.awk rename to tools/help.awk diff --git a/tools/style.c.exclude b/tools/style.c.exclude new file mode 100644 index 0000000000..e67f656a24 --- /dev/null +++ b/tools/style.c.exclude @@ -0,0 +1,15 @@ +^\./core/embed/bootloader/protob/ +^\./crypto/aes/ +^\./crypto/chacha20poly1305/ +^\./crypto/ed25519-donna/ +^\./crypto/gui/ +^\./crypto/monero/base58 +^\./crypto/monero/int-util +^\./crypto/blake2 +^\./crypto/check_mem +^\./crypto/groestl +^\./crypto/ripemd160 +^\./crypto/segwit_addr +^\./crypto/sha2 +^\./crypto/sha3 +^\./legacy/vendor diff --git a/tools/style.c.include b/tools/style.c.include new file mode 100644 index 0000000000..276419c797 --- /dev/null +++ b/tools/style.c.include @@ -0,0 +1,5 @@ +^\./common/ +^\./core/embed/ +^\./crypto/ +^\./legacy/ +^\./storage/ diff --git a/tools/style.py.exclude b/tools/style.py.exclude new file mode 100644 index 0000000000..fa02c76299 --- /dev/null +++ b/tools/style.py.exclude @@ -0,0 +1,2 @@ +^\./legacy/firmware/protob/messages_pb2\.py +^\./legacy/vendor diff --git a/tools/style.py.include b/tools/style.py.include new file mode 100644 index 0000000000..07bba6d035 --- /dev/null +++ b/tools/style.py.include @@ -0,0 +1,5 @@ +^\./common/ +^\./core/src/ +^\./crypto/ +^\./legacy/ +^\./storage/