56
.github/workflows/build-workflow.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Compilation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
nano_debug_build:
|
||||
name: Build debug application for NanoS, X and S+
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- SDK: "$NANOS_SDK"
|
||||
artifact: nanos
|
||||
- SDK: "$NANOX_SDK"
|
||||
artifact: nanox
|
||||
- SDK: "$NANOSP_SDK"
|
||||
artifact: nanosp
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Build an altcoin
|
||||
run: |
|
||||
make BOLOS_SDK=${{ matrix.SDK }} DEBUG=1 ALLOW_DATA=1 CHAIN=ethereum_classic
|
||||
mv bin/app.elf ethereum_classic_${{ matrix.artifact }}.elf
|
||||
|
||||
- name: Upload altcoin binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ethereum_classic_${{ matrix.artifact }}
|
||||
path: ./ethereum_classic_${{ matrix.artifact }}.elf
|
||||
|
||||
- name: Build Ethereum
|
||||
run: |
|
||||
make clean
|
||||
make BOLOS_SDK=${{ matrix.SDK }} DEBUG=1 ALLOW_DATA=1
|
||||
mv bin/app.elf ethereum_${{ matrix.artifact }}.elf
|
||||
|
||||
- name: Upload app binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ethereum_${{ matrix.artifact }}
|
||||
path: ./ethereum_${{ matrix.artifact }}.elf
|
||||
166
.github/workflows/ci-workflow.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Compilation & tests
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -11,79 +11,6 @@ on:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
job_build_debug_nano_s:
|
||||
name: Build debug Nano S
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Build an altcoin
|
||||
run: |
|
||||
make DEBUG=1 ALLOW_DATA=1 CHAIN=ethereum_classic
|
||||
mv bin/app.elf ethereum_classic_nanos.elf
|
||||
|
||||
- name: Upload altcoin binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ethereum_classic_nanos
|
||||
path: ./ethereum_classic_nanos.elf
|
||||
|
||||
- name: Build Ethereum
|
||||
run: |
|
||||
make clean
|
||||
make DEBUG=1 ALLOW_DATA=1
|
||||
mv bin/app.elf ethereum_nanos.elf
|
||||
|
||||
- name: Upload app binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ethereum_nanos
|
||||
path: ./ethereum_nanos.elf
|
||||
|
||||
job_build_debug_nano_x:
|
||||
name: Build debug Nano X
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Build an altcoin Nano X
|
||||
run: |
|
||||
make clean
|
||||
make BOLOS_SDK=$NANOX_SDK DEBUG=1 ALLOW_DATA=1 CHAIN=ethereum_classic
|
||||
mv bin/app.elf ethereum_classic_nanox.elf
|
||||
|
||||
- name: Upload altcoin binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ethereum_classic_nanox
|
||||
path: ./ethereum_classic_nanox.elf
|
||||
|
||||
- name: Build Ethereum Nano X
|
||||
run: |
|
||||
make clean
|
||||
make BOLOS_SDK=$NANOX_SDK DEBUG=1 ALLOW_DATA=1
|
||||
mv bin/app.elf ethereum_nanox.elf
|
||||
|
||||
- name: Upload app binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ethereum_nanox
|
||||
path: ./ethereum_nanox.elf
|
||||
|
||||
scan-build:
|
||||
name: Clang Static Analyzer
|
||||
runs-on: ubuntu-latest
|
||||
@@ -104,8 +31,12 @@ jobs:
|
||||
name: scan-build
|
||||
path: scan-build
|
||||
|
||||
building_for_e2e_tests:
|
||||
name: Building binaries for E2E tests
|
||||
# =====================================================
|
||||
# ZEMU TESTS
|
||||
# =====================================================
|
||||
|
||||
building_for_e2e_zemu_tests:
|
||||
name: Building binaries for E2E Zemu tests
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
|
||||
@@ -114,17 +45,19 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build testing binaries
|
||||
run: cd tests && ./build_local_test_elfs.sh
|
||||
run: |
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
cd tests/zemu/ && ./build_local_test_elfs.sh
|
||||
|
||||
- name: Upload app binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: e2e_elfs
|
||||
path: ./tests/elfs/
|
||||
path: ./tests/zemu/elfs/
|
||||
|
||||
jobs-e2e-tests:
|
||||
name: E2E tests
|
||||
needs: [building_for_e2e_tests]
|
||||
jobs-e2e-zemu-tests:
|
||||
name: E2E Zemu tests
|
||||
needs: [building_for_e2e_zemu_tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Test
|
||||
@@ -146,10 +79,10 @@ jobs:
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Build/Install build js deps
|
||||
run: cd tests && yarn install
|
||||
run: cd tests/zemu/ && yarn install
|
||||
|
||||
- name: Create tmp folder for artifacts
|
||||
run: mkdir tests/elfs
|
||||
run: mkdir tests/zemu/elfs
|
||||
|
||||
- name: Download app binaries
|
||||
uses: actions/download-artifact@v2
|
||||
@@ -157,7 +90,70 @@ jobs:
|
||||
path: tmp/
|
||||
|
||||
- name: Gather elfs
|
||||
run: cp `find tmp/e2e_elfs/ -name "*.elf"` tests/elfs/
|
||||
run: cp `find tmp/e2e_elfs/ -name "*.elf"` tests/zemu/elfs/
|
||||
|
||||
- name: Run zemu tests
|
||||
run: cd tests && yarn test
|
||||
run: cd tests/zemu/ && yarn test
|
||||
|
||||
# =====================================================
|
||||
# SPECULOS TESTS
|
||||
# =====================================================
|
||||
|
||||
|
||||
building_for_e2e_speculos_tests:
|
||||
name: Building binaries for E2E Speculos tests
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build testing binaries
|
||||
run: |
|
||||
mkdir tests/speculos/elfs
|
||||
make clean && make DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOS_SDK && mv bin/app.elf tests/speculos/elfs/nanos.elf
|
||||
make clean && make DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOX_SDK && mv bin/app.elf tests/speculos/elfs/nanox.elf
|
||||
make clean && make DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOSP_SDK && mv bin/app.elf tests/speculos/elfs/nanosp.elf
|
||||
|
||||
- name: Upload app binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: e2e_elfs
|
||||
path: ./tests/speculos/elfs
|
||||
|
||||
|
||||
jobs-e2e-speculos-tests:
|
||||
name: Speculos tests
|
||||
strategy:
|
||||
matrix:
|
||||
model: ["nanosp", "nanos", "nanox"]
|
||||
|
||||
needs: [building_for_e2e_speculos_tests]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create tmp folder for artifacts
|
||||
run: mkdir tests/speculos/elfs
|
||||
|
||||
- name: Download app binaries
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: tmp/
|
||||
|
||||
- name: Gather elfs
|
||||
run: cp `find tmp/e2e_elfs/ -name "*.elf"` tests/speculos/elfs/
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd tests/speculos
|
||||
sudo apt-get update && sudo apt-get install -y qemu-user-static
|
||||
pip install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt
|
||||
|
||||
- name: Run speculos tests
|
||||
run: |
|
||||
cd tests/speculos
|
||||
pytest --model ${{ matrix.model }} --path ./elfs/${{ matrix.model }}.elf --display headless
|
||||
4
.github/workflows/lint-workflow.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Lint
|
||||
uses: DoozyX/clang-format-lint-action@v0.13
|
||||
uses: DoozyX/clang-format-lint-action@v0.14
|
||||
with:
|
||||
source: "./"
|
||||
extensions: "h,c"
|
||||
clangFormatVersion: 12.0.0
|
||||
clangFormatVersion: 12.0.1
|
||||
|
||||
1
.gitignore
vendored
@@ -18,3 +18,4 @@ tests/elfs/*
|
||||
tests/snapshots-tmp
|
||||
|
||||
.vscode
|
||||
.idea
|
||||
28
CHANGELOG.md
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [1.9.20](https://github.com/ledgerhq/app-ethereum/compare/1.9.19...1.9.20) - 2022-10-10
|
||||
|
||||
### Added
|
||||
|
||||
- (clone) XDCNetwork
|
||||
- (clone) Meter
|
||||
- (clone) Multivac
|
||||
- (clone) Tecra
|
||||
- (clone) ApothemNetwork
|
||||
|
||||
### Changed
|
||||
|
||||
- EIP-191 improvements, now lets the user see the entire message one chunk at a time (255 characters for LNX & LNS+, 99 for LNS)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Allow swap with variants
|
||||
|
||||
### Removed
|
||||
|
||||
- Compound support (will become its own plugin)
|
||||
|
||||
## [1.9.19](https://github.com/ledgerhq/app-ethereum/compare/1.9.18...1.9.19) - 2022-06-15
|
||||
|
||||
### Added
|
||||
@@ -16,7 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Changed
|
||||
|
||||
- EIP-191 signatures now show (up to 99 characters on LNS and 255 on LNX & LNS) the actual data contained in the message (clear-signing)
|
||||
- EIP-191 signatures now show (up to 99 characters on LNS and 255 on LNX & LNS+) the actual data contained in the message (clear-signing)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -64,7 +86,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed stark order signature on LNS
|
||||
- Fixed stark order signature on LNS
|
||||
|
||||
## [1.9.13](https://github.com/ledgerhq/app-ethereum/compare/1.9.12...1.9.13) - 2021-11-17
|
||||
|
||||
@@ -76,7 +98,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed stark order signature on LNX
|
||||
- Fixed stark order signature on LNX
|
||||
|
||||
## [1.9.11](https://github.com/ledgerhq/app-ethereum/compare/1.9.10...1.9.11) - 2021-10-12
|
||||
|
||||
|
||||
12
Makefile
@@ -34,9 +34,9 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'"
|
||||
|
||||
APPVERSION_M=1
|
||||
APPVERSION_N=9
|
||||
APPVERSION_P=19
|
||||
APPVERSION_P=20
|
||||
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
|
||||
APP_LOAD_FLAGS= --appFlags 0x240 --dep Ethereum:$(APPVERSION)
|
||||
APP_LOAD_FLAGS= --appFlags 0xa40 --dep Ethereum:$(APPVERSION)
|
||||
|
||||
###########################
|
||||
# Set Chain environnement #
|
||||
@@ -194,6 +194,7 @@ SDK_SOURCE_PATH += lib_ux
|
||||
ifeq ($(TARGET_NAME),TARGET_NANOX)
|
||||
SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl
|
||||
endif
|
||||
APP_SOURCE_PATH += src_bagl
|
||||
|
||||
### initialize plugin SDK submodule if needed, rebuild it, and warn if a difference is noticed
|
||||
ifeq ($(CHAIN),ethereum)
|
||||
@@ -222,13 +223,16 @@ delete:
|
||||
python3 -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS)
|
||||
|
||||
install_tests:
|
||||
cd tests && (yarn install || sudo yarn install)
|
||||
cd tests/zemu/ && (yarn install || sudo yarn install)
|
||||
|
||||
run_tests:
|
||||
cd tests && (yarn test || sudo yarn test)
|
||||
cd tests/zemu/ && (yarn test || sudo yarn test)
|
||||
|
||||
test: install_tests run_tests
|
||||
|
||||
unit-test:
|
||||
make -C tests/unit
|
||||
|
||||
# import generic rules from the sdk
|
||||
include $(BOLOS_SDK)/Makefile.rules
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ The address can be optionally checked on the device before being returned.
|
||||
|
||||
#### Description
|
||||
|
||||
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md
|
||||
|
||||
This command signs an Ethereum transaction after having the user validate the following parameters
|
||||
|
||||
- Gas price
|
||||
|
||||
BIN
icons/nanos_app_apothemnetwork.gif
Normal file
|
After Width: | Height: | Size: 67 B |
BIN
icons/nanos_app_meter.gif
Normal file
|
After Width: | Height: | Size: 84 B |
BIN
icons/nanos_app_multivac.gif
Normal file
|
After Width: | Height: | Size: 75 B |
BIN
icons/nanos_app_tecracoin.gif
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/nanos_app_tecratestnet.gif
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/nanos_app_xdcnetwork.gif
Normal file
|
After Width: | Height: | Size: 67 B |
BIN
icons/nanox_app_apothemnetwork.gif
Normal file
|
After Width: | Height: | Size: 65 B |
BIN
icons/nanox_app_meter.gif
Normal file
|
After Width: | Height: | Size: 79 B |
BIN
icons/nanox_app_multivac.gif
Normal file
|
After Width: | Height: | Size: 74 B |
BIN
icons/nanox_app_tecracoin.gif
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/nanox_app_tecratestnet.gif
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/nanox_app_xdcnetwork.gif
Normal file
|
After Width: | Height: | Size: 65 B |
3
makefile_conf/chain/apothemnetwork.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/550'"
|
||||
DEFINES += CHAINID_UPCASE=\"APOTHEMNETWORK\" CHAINID_COINNAME=\"TXDC\" CHAIN_KIND=CHAIN_KIND_APOTHEMNETWORK CHAIN_ID=51
|
||||
APPNAME = "ApothemNetwork"
|
||||
3
makefile_conf/chain/meter.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/60'"
|
||||
DEFINES += CHAINID_UPCASE=\"METER\" CHAINID_COINNAME=\"MTR\" CHAIN_KIND=CHAIN_KIND_METER CHAIN_ID=82
|
||||
APPNAME = "Meter"
|
||||
3
makefile_conf/chain/multivac.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/60'"
|
||||
DEFINES += CHAINID_UPCASE=\"MULTIVAC\" CHAINID_COINNAME=\"MTV\" CHAIN_KIND=CHAIN_KIND_MULTIVAC CHAIN_ID=62621
|
||||
APPNAME = "MultiVAC"
|
||||
3
makefile_conf/chain/tecracoin.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/554'" --path "44'/60'"
|
||||
DEFINES += CHAINID_UPCASE=\"TECRA\" CHAINID_COINNAME=\"TCR\" CHAIN_KIND=CHAIN_KIND_TECRA CHAIN_ID=20531812
|
||||
APPNAME = "TecraCoin"
|
||||
3
makefile_conf/chain/tecratestnet.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/554'" --path "44'/60'"
|
||||
DEFINES += CHAINID_UPCASE=\"TECRATESTNET\" CHAINID_COINNAME=\"TCR\" CHAIN_KIND=CHAIN_KIND_TECRA CHAIN_ID=20531811
|
||||
APPNAME = "TecraTestnet"
|
||||
3
makefile_conf/chain/xdcnetwork.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/550'"
|
||||
DEFINES += CHAINID_UPCASE=\"XDCNETWORK\" CHAINID_COINNAME=\"XDC\" CHAIN_KIND=CHAIN_KIND_XDCNETWORK CHAIN_ID=50
|
||||
APPNAME = "XDC Network"
|
||||
@@ -58,66 +58,67 @@
|
||||
#define OFFSET_LC 4
|
||||
#define OFFSET_CDATA 5
|
||||
|
||||
#define ERR_APDU_EMPTY 0x6982
|
||||
#define ERR_APDU_SIZE_MISMATCH 0x6983
|
||||
|
||||
void handleGetPublicKey(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleProvideNFTInformation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleSign(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleGetAppConfiguration(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleSignPersonalMessage(uint8_t p1,
|
||||
bool handleSignPersonalMessage(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
const uint8_t *const payload,
|
||||
uint8_t length);
|
||||
void handleSignEIP712Message(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
|
||||
void handleSetExternalPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
|
||||
void handleSetPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
|
||||
void handlePerformPrivacyOperation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
@@ -126,7 +127,7 @@ void handlePerformPrivacyOperation(uint8_t p1,
|
||||
|
||||
void handleGetEth2PublicKey(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
@@ -143,7 +144,7 @@ void handleSetEth2WinthdrawalIndex(uint8_t p1,
|
||||
|
||||
void handleStarkwareGetPublicKey(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
@@ -155,13 +156,13 @@ void handleStarkwareSignMessage(uint8_t p1,
|
||||
unsigned int *tx);
|
||||
void handleStarkwareProvideQuantum(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleStarkwareUnsafeSign(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
|
||||
@@ -67,7 +67,12 @@ typedef enum chain_kind_e {
|
||||
CHAIN_KIND_OKC,
|
||||
CHAIN_KIND_CUBE,
|
||||
CHAIN_KIND_SHIDEN,
|
||||
CHAIN_KIND_ASTAR
|
||||
CHAIN_KIND_ASTAR,
|
||||
CHAIN_KIND_XDCNETWORK,
|
||||
CHAIN_KIND_METER,
|
||||
CHAIN_KIND_MULTIVAC,
|
||||
CHAIN_KIND_TECRA,
|
||||
CHAIN_KIND_APOTHEMNETWORK
|
||||
} chain_kind_t;
|
||||
|
||||
typedef struct chain_config_s {
|
||||
|
||||
31
src/common_ui.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef _COMMON_UI_H_
|
||||
#define _COMMON_UI_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void ui_idle(void);
|
||||
void ui_warning_contract_data(void);
|
||||
void ui_display_public_eth2(void);
|
||||
void ui_display_privacy_public_key(void);
|
||||
void ui_display_privacy_shared_secret(void);
|
||||
void ui_display_public_key(void);
|
||||
void ui_display_sign(void);
|
||||
void ui_sign_712_v0(void);
|
||||
void ui_display_stark_public(void);
|
||||
void ui_confirm_selector(void);
|
||||
void ui_confirm_parameter(void);
|
||||
void ui_stark_limit_order(void);
|
||||
void ui_stark_unsafe_sign(void);
|
||||
void ui_stark_transfer(bool selfTransfer, bool conditional);
|
||||
|
||||
// EIP-191
|
||||
void ui_191_start(void);
|
||||
void ui_191_switch_to_message(void);
|
||||
void ui_191_switch_to_message_end(void);
|
||||
void ui_191_switch_to_sign(void);
|
||||
void ui_191_switch_to_question(void);
|
||||
|
||||
#include "ui_callbacks.h"
|
||||
#include <string.h>
|
||||
|
||||
#endif // _COMMON_UI_H_
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "network.h"
|
||||
#include "ethUtils.h"
|
||||
|
||||
void eth_plugin_prepare_init(ethPluginInitContract_t *init, uint8_t *selector, uint32_t dataSize) {
|
||||
void eth_plugin_prepare_init(ethPluginInitContract_t *init,
|
||||
const uint8_t *selector,
|
||||
uint32_t dataSize) {
|
||||
memset((uint8_t *) init, 0, sizeof(ethPluginInitContract_t));
|
||||
init->selector = selector;
|
||||
init->dataSize = dataSize;
|
||||
@@ -111,7 +113,7 @@ static bool eth_plugin_perform_init_old_internal(uint8_t *contractAddress,
|
||||
j++) {
|
||||
if (memcmp(init->selector, (const void *) PIC(selectors[j]), SELECTOR_SIZE) == 0) {
|
||||
if ((INTERNAL_ETH_PLUGINS[i].availableCheck == NULL) ||
|
||||
((PluginAvailableCheck) PIC(INTERNAL_ETH_PLUGINS[i].availableCheck)) ()) {
|
||||
((PluginAvailableCheck) PIC(INTERNAL_ETH_PLUGINS[i].availableCheck))()) {
|
||||
strlcpy(dataContext.tokenContext.pluginName,
|
||||
INTERNAL_ETH_PLUGINS[i].alias,
|
||||
PLUGIN_ID_LENGTH);
|
||||
@@ -346,13 +348,13 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
|
||||
}
|
||||
break;
|
||||
case ETH_PLUGIN_QUERY_CONTRACT_ID:
|
||||
if (((ethQueryContractID_t *) parameter)->result <= ETH_PLUGIN_RESULT_UNSUCCESSFUL) {
|
||||
return ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
if (((ethQueryContractID_t *) parameter)->result != ETH_PLUGIN_RESULT_OK) {
|
||||
return ETH_PLUGIN_RESULT_ERROR;
|
||||
}
|
||||
break;
|
||||
case ETH_PLUGIN_QUERY_CONTRACT_UI:
|
||||
if (((ethQueryContractUI_t *) parameter)->result <= ETH_PLUGIN_RESULT_UNSUCCESSFUL) {
|
||||
return ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
if (((ethQueryContractUI_t *) parameter)->result != ETH_PLUGIN_RESULT_OK) {
|
||||
return ETH_PLUGIN_RESULT_ERROR;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#define NO_EXTRA_INFO(ctx, idx) \
|
||||
(allzeroes(&(ctx.transactionContext.extraInfo[idx]), sizeof(extraInfo_t)))
|
||||
|
||||
void eth_plugin_prepare_init(ethPluginInitContract_t *init, uint8_t *selector, uint32_t dataSize);
|
||||
void eth_plugin_prepare_init(ethPluginInitContract_t *init,
|
||||
const uint8_t *selector,
|
||||
uint32_t dataSize);
|
||||
void eth_plugin_prepare_provide_parameter(ethPluginProvideParameter_t *provideParameter,
|
||||
uint8_t *parameter,
|
||||
uint32_t parameterOffset);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
bool erc20_plugin_available_check(void);
|
||||
|
||||
void erc20_plugin_call(int message, void* parameters);
|
||||
void compound_plugin_call(int message, void* parameters);
|
||||
|
||||
void copy_address(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size) {
|
||||
uint8_t copy_size = MIN(dst_size, ADDRESS_LENGTH);
|
||||
@@ -29,17 +28,6 @@ static const uint8_t ERC20_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7,
|
||||
const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS] = {ERC20_TRANSFER_SELECTOR,
|
||||
ERC20_APPROVE_SELECTOR};
|
||||
|
||||
static const uint8_t COMPOUND_REDEEM_UNDERLYING_SELECTOR[SELECTOR_SIZE] = {0x85, 0x2a, 0x12, 0xe3};
|
||||
static const uint8_t COMPOUND_REDEEM_SELECTOR[SELECTOR_SIZE] = {0xdb, 0x00, 0x6a, 0x75};
|
||||
static const uint8_t COMPOUND_MINT_SELECTOR[SELECTOR_SIZE] = {0xa0, 0x71, 0x2d, 0x68};
|
||||
static const uint8_t CETH_MINT_SELECTOR[SELECTOR_SIZE] = {0x12, 0x49, 0xc5, 0x8b};
|
||||
|
||||
const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS] = {
|
||||
COMPOUND_REDEEM_UNDERLYING_SELECTOR,
|
||||
COMPOUND_REDEEM_SELECTOR,
|
||||
COMPOUND_MINT_SELECTOR,
|
||||
CETH_MINT_SELECTOR};
|
||||
|
||||
#ifdef HAVE_ETH2
|
||||
|
||||
static const uint8_t ETH2_DEPOSIT_SELECTOR[SELECTOR_SIZE] = {0x22, 0x89, 0x51, 0x18};
|
||||
@@ -111,12 +99,6 @@ const internalEthPlugin_t INTERNAL_ETH_PLUGINS[] = {
|
||||
"-erc20",
|
||||
erc20_plugin_call},
|
||||
|
||||
{NULL,
|
||||
(const uint8_t**) COMPOUND_SELECTORS,
|
||||
NUM_COMPOUND_SELECTORS,
|
||||
"-cmpd",
|
||||
compound_plugin_call},
|
||||
|
||||
#ifdef HAVE_ETH2
|
||||
|
||||
{NULL, (const uint8_t**) ETH2_SELECTORS, NUM_ETH2_SELECTORS, "-eth2", eth2_plugin_call},
|
||||
|
||||
@@ -29,9 +29,6 @@ typedef struct internalEthPlugin_t {
|
||||
#define NUM_ERC20_SELECTORS 2
|
||||
extern const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS];
|
||||
|
||||
#define NUM_COMPOUND_SELECTORS 4
|
||||
extern const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS];
|
||||
|
||||
#ifdef HAVE_ETH2
|
||||
|
||||
#define NUM_ETH2_SELECTORS 1
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
#include "shared_context.h"
|
||||
#ifdef HAVE_UX_FLOW
|
||||
#include "ui_flow.h"
|
||||
#endif
|
||||
#include "ui_callbacks.h"
|
||||
#include "eth_plugin_handler.h"
|
||||
#include "ux.h"
|
||||
#include "feature_signTx.h"
|
||||
|
||||
92
src/main.c
@@ -17,8 +17,7 @@
|
||||
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "ui_flow.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
@@ -84,24 +83,6 @@ void reset_app_context() {
|
||||
memset((uint8_t *) &tmpContent, 0, sizeof(tmpContent));
|
||||
}
|
||||
|
||||
void ui_idle(void) {
|
||||
// reserve a display stack slot if none yet
|
||||
if (G_ux.stack_count == 0) {
|
||||
ux_stack_push();
|
||||
}
|
||||
ux_flow_init(0, ux_idle_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_warning_contract_data(void) {
|
||||
ux_flow_init(0, ux_warning_contract_data_flow, NULL);
|
||||
}
|
||||
|
||||
unsigned int io_seproxyhal_touch_exit(__attribute__((unused)) const bagl_element_t *e) {
|
||||
// Go back to the dashboard
|
||||
os_sched_exit(0);
|
||||
return 0; // do not redraw the widget
|
||||
}
|
||||
|
||||
void io_seproxyhal_send_status(uint32_t sw) {
|
||||
G_io_apdu_buffer[0] = ((sw >> 8) & 0xff);
|
||||
G_io_apdu_buffer[1] = (sw & 0xff);
|
||||
@@ -296,6 +277,21 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
|
||||
case CHAIN_KIND_ASTAR:
|
||||
numTokens = NUM_TOKENS_ASTAR;
|
||||
break;
|
||||
case CHAIN_KIND_XDCNETWORK:
|
||||
numTokens = NUM_TOKENS_XDCNETWORK;
|
||||
break;
|
||||
case CHAIN_KIND_METER:
|
||||
numTokens = NUM_TOKENS_METER;
|
||||
break;
|
||||
case CHAIN_KIND_MULTIVAC:
|
||||
numTokens = NUM_TOKENS_MULTIVAC;
|
||||
break;
|
||||
case CHAIN_KIND_TECRA:
|
||||
numTokens = NUM_TOKENS_TECRA;
|
||||
break;
|
||||
case CHAIN_KIND_APOTHEMNETWORK:
|
||||
numTokens = NUM_TOKENS_APOTHEMNETWORK;
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < numTokens; i++) {
|
||||
switch (chainConfig->kind) {
|
||||
@@ -434,6 +430,21 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
|
||||
case CHAIN_KIND_ASTAR:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_ASTAR[i]);
|
||||
break;
|
||||
case CHAIN_KIND_XDCNETWORK:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_XDCNETWORK[i]);
|
||||
break;
|
||||
case CHAIN_KIND_METER:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_METER[i]);
|
||||
break;
|
||||
case CHAIN_KIND_MULTIVAC:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_MULTIVAC[i]);
|
||||
break;
|
||||
case CHAIN_KIND_TECRA:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_TECRA[i]);
|
||||
break;
|
||||
case CHAIN_KIND_APOTHEMNETWORK:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_APOTHEMNETWORK[i]);
|
||||
break;
|
||||
}
|
||||
if (memcmp(currentToken->address, tmpContent.txContent.destination, ADDRESS_LENGTH) == 0) {
|
||||
return currentToken;
|
||||
@@ -480,6 +491,36 @@ void handleGetWalletId(volatile unsigned int *tx) {
|
||||
|
||||
#endif // HAVE_WALLET_ID_SDK
|
||||
|
||||
const uint8_t *parseBip32(const uint8_t *dataBuffer, uint16_t *dataLength, bip32_path_t *bip32) {
|
||||
if (*dataLength < 1) {
|
||||
PRINTF("Invalid data\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bip32->length = *dataBuffer;
|
||||
|
||||
if (bip32->length < 0x1 || bip32->length > MAX_BIP32_PATH) {
|
||||
PRINTF("Invalid bip32\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dataBuffer++;
|
||||
(*dataLength)--;
|
||||
|
||||
if (*dataLength < sizeof(uint32_t) * (bip32->length)) {
|
||||
PRINTF("Invalid data\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < bip32->length; i++) {
|
||||
bip32->path[i] = U4BE(dataBuffer, 0);
|
||||
dataBuffer += sizeof(uint32_t);
|
||||
*dataLength -= sizeof(uint32_t);
|
||||
}
|
||||
|
||||
return dataBuffer;
|
||||
}
|
||||
|
||||
void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
unsigned short sw = 0;
|
||||
|
||||
@@ -623,12 +664,11 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
|
||||
case INS_SIGN_PERSONAL_MESSAGE:
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
G_io_apdu_buffer[OFFSET_LC],
|
||||
flags,
|
||||
tx);
|
||||
G_io_apdu_buffer[OFFSET_LC]);
|
||||
break;
|
||||
|
||||
case INS_SIGN_EIP_712_MESSAGE:
|
||||
@@ -730,9 +770,11 @@ void app_main(void) {
|
||||
// no apdu received, well, reset the session, and reset the
|
||||
// bootloader configuration
|
||||
if (rx == 0) {
|
||||
THROW(0x6982);
|
||||
THROW(ERR_APDU_EMPTY);
|
||||
}
|
||||
if (rx > OFFSET_LC && rx != (G_io_apdu_buffer[OFFSET_LC] + 5)) {
|
||||
THROW(ERR_APDU_SIZE_MISMATCH);
|
||||
}
|
||||
PRINTF("New APDU received:\n%.*H\n", rx, G_io_apdu_buffer);
|
||||
|
||||
handleApdu(&flags, &tx);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
#define N_storage (*(volatile internalStorage_t *) PIC(&N_storage_real))
|
||||
|
||||
typedef struct bip32_path_t {
|
||||
uint8_t length;
|
||||
uint32_t path[MAX_BIP32_PATH];
|
||||
} bip32_path_t;
|
||||
|
||||
typedef struct internalStorage_t {
|
||||
unsigned char dataAllowed;
|
||||
unsigned char contractDetails;
|
||||
@@ -82,8 +87,7 @@ typedef union extraInfo_t {
|
||||
} extraInfo_t;
|
||||
|
||||
typedef struct transactionContext_t {
|
||||
uint8_t pathLength;
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
bip32_path_t bip32;
|
||||
uint8_t hash[INT256_LENGTH];
|
||||
union extraInfo_t extraInfo[MAX_ITEMS];
|
||||
uint8_t tokenSet[MAX_ITEMS];
|
||||
@@ -91,15 +95,13 @@ typedef struct transactionContext_t {
|
||||
} transactionContext_t;
|
||||
|
||||
typedef struct messageSigningContext_t {
|
||||
uint8_t pathLength;
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
bip32_path_t bip32;
|
||||
uint8_t hash[INT256_LENGTH];
|
||||
uint32_t remainingLength;
|
||||
} messageSigningContext_t;
|
||||
|
||||
typedef struct messageSigningContext712_t {
|
||||
uint8_t pathLength;
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
bip32_path_t bip32;
|
||||
uint8_t domainHash[32];
|
||||
uint8_t messageHash[32];
|
||||
} messageSigningContext712_t;
|
||||
@@ -217,5 +219,6 @@ extern uint32_t eth2WithdrawalIndex;
|
||||
#endif
|
||||
|
||||
void reset_app_context(void);
|
||||
const uint8_t *parseBip32(const uint8_t *, uint16_t *, bip32_path_t *);
|
||||
|
||||
#endif // _SHARED_CONTEXT_H_
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
#include "shared_context.h"
|
||||
#include "stark_utils.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "utils.h"
|
||||
#include "ethUtils.h"
|
||||
|
||||
extraInfo_t *getKnownToken(uint8_t *contractAddress);
|
||||
|
||||
static unsigned char const C_cx_Stark256_n[] = {
|
||||
// n: 0x0800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f
|
||||
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
|
||||
10
src/tokens.c
@@ -212,4 +212,14 @@ const tokenDefinition_t const TOKENS_ASTAR[NUM_TOKENS_ASTAR] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_SHIDEN[NUM_TOKENS_SHIDEN] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_XDCNETWORK[NUM_TOKENS_XDCNETWORK] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_METER[NUM_TOKENS_METER] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_MULTIVAC[NUM_TOKENS_MULTIVAC] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_TECRA[NUM_TOKENS_TECRA] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_APOTHEMNETWORK[NUM_TOKENS_APOTHEMNETWORK] = {};
|
||||
|
||||
#endif
|
||||
|
||||
10
src/tokens.h
@@ -110,6 +110,11 @@ static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
|
||||
#define NUM_TOKENS_CUBE 0
|
||||
#define NUM_TOKENS_ASTAR 0
|
||||
#define NUM_TOKENS_SHIDEN 0
|
||||
#define NUM_TOKENS_XDCNETWORK 0
|
||||
#define NUM_TOKENS_METER 0
|
||||
#define NUM_TOKENS_MULTIVAC 0
|
||||
#define NUM_TOKENS_TECRA 0
|
||||
#define NUM_TOKENS_APOTHEMNETWORK 0
|
||||
|
||||
extern tokenDefinition_t const TOKENS_AKROMA[NUM_TOKENS_AKROMA];
|
||||
extern tokenDefinition_t const TOKENS_ELLAISM[NUM_TOKENS_ELLAISM];
|
||||
@@ -155,6 +160,11 @@ extern tokenDefinition_t const TOKENS_WETHIO[NUM_TOKENS_WETHIO];
|
||||
extern tokenDefinition_t const TOKENS_CUBE[NUM_TOKENS_CUBE];
|
||||
extern tokenDefinition_t const TOKENS_ASTAR[NUM_TOKENS_ASTAR];
|
||||
extern tokenDefinition_t const TOKENS_SHIDEN[NUM_TOKENS_SHIDEN];
|
||||
extern tokenDefinition_t const TOKENS_XDCNETWORK[NUM_TOKENS_XDCNETWORK];
|
||||
extern tokenDefinition_t const TOKENS_METER[NUM_TOKENS_METER];
|
||||
extern tokenDefinition_t const TOKENS_MULTIVAC[NUM_TOKENS_MULTIVAC];
|
||||
extern tokenDefinition_t const TOKENS_TECRA[NUM_TOKENS_TECRA];
|
||||
extern tokenDefinition_t const TOKENS_APOTHEMNETWORK[NUM_TOKENS_APOTHEMNETWORK];
|
||||
|
||||
#endif /* HAVE_TOKENS_LIST */
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ unsigned int io_seproxyhal_touch_tx_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_tx_cancel(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_address_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_address_cancel(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_signMessage_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_signMessage_cancel(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_signMessage_ok(void);
|
||||
unsigned int io_seproxyhal_touch_signMessage_cancel(void);
|
||||
unsigned int io_seproxyhal_touch_data_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_data_cancel(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_signMessage712_v0_ok(const bagl_element_t *e);
|
||||
@@ -19,8 +19,10 @@ unsigned int io_seproxyhal_touch_signMessage712_v0_cancel(const bagl_element_t *
|
||||
unsigned int io_seproxyhal_touch_eth2_address_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_privacy_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_privacy_cancel(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_stark_unsafe_sign_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_stark_pubkey_ok(const bagl_element_t *e);
|
||||
unsigned int io_seproxyhal_touch_stark_ok(const bagl_element_t *e);
|
||||
|
||||
void ui_idle(void);
|
||||
void ui_warning_contract_data(void);
|
||||
|
||||
void io_seproxyhal_send_status(uint32_t sw);
|
||||
|
||||
@@ -54,7 +54,7 @@ int local_strchr(char *string, char ch) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t u64_from_BE(uint8_t *in, uint8_t size) {
|
||||
uint64_t u64_from_BE(const uint8_t *in, uint8_t size) {
|
||||
uint8_t i = 0;
|
||||
uint64_t res = 0;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ void convertUint256BE(uint8_t* data, uint32_t length, uint256_t* target);
|
||||
|
||||
int local_strchr(char* string, char ch);
|
||||
|
||||
uint64_t u64_from_BE(uint8_t* in, uint8_t size);
|
||||
uint64_t u64_from_BE(const uint8_t* in, uint8_t size);
|
||||
|
||||
bool uint256_to_decimal(const uint8_t* value, size_t value_len, char* out, size_t out_len);
|
||||
|
||||
|
||||
73
src_bagl/common_ui.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifdef HAVE_BAGL
|
||||
|
||||
#include "common_ui.h"
|
||||
#include "ux.h"
|
||||
#include "ui_flow.h"
|
||||
|
||||
void ui_idle(void) {
|
||||
// reserve a display stack slot if none yet
|
||||
if (G_ux.stack_count == 0) {
|
||||
ux_stack_push();
|
||||
}
|
||||
ux_flow_init(0, ux_idle_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_warning_contract_data(void) {
|
||||
ux_flow_init(0, ux_warning_contract_data_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_display_public_eth2(void) {
|
||||
ux_flow_init(0, ux_display_public_eth2_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_display_privacy_public_key(void) {
|
||||
ux_flow_init(0, ux_display_privacy_public_key_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_display_privacy_shared_secret(void) {
|
||||
ux_flow_init(0, ux_display_privacy_shared_secret_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_display_public_key(void) {
|
||||
ux_flow_init(0, ux_display_public_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_sign_712_v0(void) {
|
||||
ux_flow_init(0, ux_sign_712_v0_flow, NULL);
|
||||
}
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
void ui_display_stark_public(void) {
|
||||
ux_flow_init(0, ux_display_stark_public_flow, NULL);
|
||||
}
|
||||
void ui_stark_limit_order(void) {
|
||||
ux_flow_init(0, ux_stark_limit_order_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_stark_unsafe_sign(void) {
|
||||
ux_flow_init(0, ux_stark_unsafe_sign_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_stark_transfer(bool selfTransfer, bool conditional) {
|
||||
if (selfTransfer) {
|
||||
ux_flow_init(
|
||||
0,
|
||||
(conditional ? ux_stark_self_transfer_conditional_flow : ux_stark_self_transfer_flow),
|
||||
NULL);
|
||||
} else {
|
||||
ux_flow_init(0,
|
||||
(conditional ? ux_stark_transfer_conditional_flow : ux_stark_transfer_flow),
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
#endif // HAVE_STARKWARE
|
||||
|
||||
void ui_confirm_selector(void) {
|
||||
ux_flow_init(0, ux_confirm_selector_flow, NULL);
|
||||
}
|
||||
|
||||
void ui_confirm_parameter(void) {
|
||||
ux_flow_init(0, ux_confirm_parameter_flow, NULL);
|
||||
}
|
||||
|
||||
#endif // HAVE_BAGL
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "shared_context.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
void display_settings(const ux_flow_step_t* const start_step);
|
||||
void switch_settings_blind_signing(void);
|
||||
@@ -184,4 +184,4 @@ UX_STEP_CB(
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
UX_FLOW(ux_warning_contract_data_flow, &ux_warning_contract_data_step);
|
||||
UX_FLOW(ux_warning_contract_data_flow, &ux_warning_contract_data_step);
|
||||
@@ -20,8 +20,6 @@ extern const ux_flow_step_t* const ux_confirm_parameter_flow[];
|
||||
|
||||
extern const ux_flow_step_t* const ux_approval_allowance_flow[];
|
||||
|
||||
extern const ux_flow_step_t* const ux_sign_flow[];
|
||||
|
||||
extern const ux_flow_step_t* const ux_sign_712_v0_flow[];
|
||||
|
||||
extern const ux_flow_step_t* const ux_display_public_eth2_flow[];
|
||||
@@ -30,6 +28,8 @@ extern const ux_flow_step_t* const ux_display_privacy_public_key_flow[];
|
||||
|
||||
extern const ux_flow_step_t* const ux_display_privacy_shared_secret_flow[];
|
||||
|
||||
extern const ux_flow_step_t* ux_approval_tx_flow[15];
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
|
||||
extern const ux_flow_step_t* const ux_display_stark_public_flow[];
|
||||
128
src_bagl/ui_flow_signMessage.c
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "shared_context.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
#include "sign_message.h"
|
||||
|
||||
typedef enum { UI_191_POS_REVIEW, UI_191_POS_QUESTION, UI_191_POS_END } e_ui_191_position;
|
||||
|
||||
static uint8_t ui_pos;
|
||||
|
||||
static void dummy_pre_cb(void) {
|
||||
if (ui_pos == UI_191_POS_REVIEW) {
|
||||
question_switcher();
|
||||
} else {
|
||||
ux_flow_prev();
|
||||
ui_pos = UI_191_POS_REVIEW;
|
||||
}
|
||||
}
|
||||
|
||||
static void dummy_post_cb(void) {
|
||||
if (ui_pos == UI_191_POS_QUESTION) {
|
||||
continue_displaying_message();
|
||||
} else // UI_191_END
|
||||
{
|
||||
ui_191_switch_to_message_end();
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
UX_STEP_NOCB(
|
||||
ux_191_step_review,
|
||||
pnn,
|
||||
{
|
||||
&C_icon_certificate,
|
||||
"Sign",
|
||||
"message",
|
||||
});
|
||||
UX_STEP_NOCB(
|
||||
ux_191_step_message,
|
||||
bnnn_paging,
|
||||
{
|
||||
.title = "Message",
|
||||
.text = strings.tmp.tmp,
|
||||
});
|
||||
UX_STEP_INIT(
|
||||
ux_191_step_dummy_pre,
|
||||
NULL,
|
||||
NULL,
|
||||
{
|
||||
dummy_pre_cb();
|
||||
});
|
||||
UX_STEP_CB(
|
||||
ux_191_step_theres_more,
|
||||
#ifdef TARGET_NANOS
|
||||
nn,
|
||||
#else
|
||||
nnn,
|
||||
#endif
|
||||
skip_rest_of_message(),
|
||||
{
|
||||
#ifndef TARGET_NANOS
|
||||
"Press right to",
|
||||
"continue message",
|
||||
#else
|
||||
"Press right to read",
|
||||
#endif
|
||||
"Double-press to skip"
|
||||
});
|
||||
UX_STEP_INIT(
|
||||
ux_191_step_dummy_post,
|
||||
NULL,
|
||||
NULL,
|
||||
{
|
||||
dummy_post_cb();
|
||||
});
|
||||
UX_STEP_CB(
|
||||
ux_191_step_sign,
|
||||
pbb,
|
||||
io_seproxyhal_touch_signMessage_ok(),
|
||||
{
|
||||
&C_icon_validate_14,
|
||||
"Sign",
|
||||
"message",
|
||||
});
|
||||
UX_STEP_CB(
|
||||
ux_191_step_cancel,
|
||||
pbb,
|
||||
io_seproxyhal_touch_signMessage_cancel(),
|
||||
{
|
||||
&C_icon_crossmark,
|
||||
"Cancel",
|
||||
"signature",
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
UX_FLOW(ux_191_flow,
|
||||
&ux_191_step_review,
|
||||
&ux_191_step_message,
|
||||
&ux_191_step_dummy_pre,
|
||||
&ux_191_step_theres_more,
|
||||
&ux_191_step_dummy_post,
|
||||
&ux_191_step_sign,
|
||||
&ux_191_step_cancel);
|
||||
|
||||
void ui_191_start(void) {
|
||||
ux_flow_init(0, ux_191_flow, NULL);
|
||||
ui_pos = UI_191_POS_REVIEW;
|
||||
}
|
||||
|
||||
void ui_191_switch_to_message(void) {
|
||||
ux_flow_init(0, ux_191_flow, &ux_191_step_message);
|
||||
ui_pos = UI_191_POS_REVIEW;
|
||||
}
|
||||
|
||||
void ui_191_switch_to_message_end(void) {
|
||||
// Force it to a value that will make it automatically do a prev()
|
||||
ui_pos = UI_191_POS_QUESTION;
|
||||
ux_flow_init(0, ux_191_flow, &ux_191_step_dummy_pre);
|
||||
}
|
||||
|
||||
void ui_191_switch_to_sign(void) {
|
||||
ux_flow_init(0, ux_191_flow, &ux_191_step_sign);
|
||||
ui_pos = UI_191_POS_END;
|
||||
}
|
||||
|
||||
void ui_191_switch_to_question(void) {
|
||||
ux_flow_init(0, ux_191_flow, &ux_191_step_theres_more);
|
||||
ui_pos = UI_191_POS_QUESTION;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "network.h"
|
||||
#include "eth_plugin_handler.h"
|
||||
#include "ui_plugin.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
// clang-format off
|
||||
UX_STEP_NOCB(
|
||||
@@ -3,8 +3,6 @@
|
||||
#include "shared_context.h"
|
||||
#include "ui_callbacks.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_stark_pubkey_ok(const bagl_element_t *e);
|
||||
|
||||
// clang-format off
|
||||
UX_STEP_NOCB(
|
||||
ux_display_stark_public_flow_1_step,
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "ui_callbacks.h"
|
||||
#include "ethUtils.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_stark_ok(const bagl_element_t *e);
|
||||
|
||||
void stark_sign_display_master_account() {
|
||||
snprintf(strings.tmp.tmp,
|
||||
sizeof(strings.tmp.tmp),
|
||||
@@ -296,6 +296,12 @@ static void processV(txContext_t *context) {
|
||||
PRINTF("Invalid type for RLP_V\n");
|
||||
THROW(EXCEPTION);
|
||||
}
|
||||
|
||||
if (context->currentFieldLength > sizeof(context->content->v)) {
|
||||
PRINTF("Invalid length for RLP_V\n");
|
||||
THROW(EXCEPTION);
|
||||
}
|
||||
|
||||
if (context->currentFieldPos < context->currentFieldLength) {
|
||||
uint32_t copySize =
|
||||
MIN(context->commandLength, context->currentFieldLength - context->currentFieldPos);
|
||||
@@ -586,7 +592,7 @@ static parserStatus_e processTxInternal(txContext_t *context) {
|
||||
}
|
||||
|
||||
parserStatus_e processTx(txContext_t *context,
|
||||
uint8_t *buffer,
|
||||
const uint8_t *buffer,
|
||||
uint32_t length,
|
||||
uint32_t processingFlags) {
|
||||
parserStatus_e result;
|
||||
@@ -596,7 +602,7 @@ parserStatus_e processTx(txContext_t *context,
|
||||
context->commandLength = length;
|
||||
context->processingFlags = processingFlags;
|
||||
result = processTxInternal(context);
|
||||
PRINTF("result: %d\n");
|
||||
PRINTF("result: %d\n", result);
|
||||
}
|
||||
CATCH_OTHER(e) {
|
||||
result = USTREAM_FAULT;
|
||||
|
||||
@@ -142,7 +142,7 @@ typedef struct txContext_t {
|
||||
uint32_t dataLength;
|
||||
uint8_t rlpBuffer[5];
|
||||
uint32_t rlpBufferPos;
|
||||
uint8_t *workBuffer;
|
||||
const uint8_t *workBuffer;
|
||||
uint32_t commandLength;
|
||||
uint32_t processingFlags;
|
||||
ustreamProcess_t customProcessor;
|
||||
@@ -157,7 +157,7 @@ void initTx(txContext_t *context,
|
||||
ustreamProcess_t customProcessor,
|
||||
void *extra);
|
||||
parserStatus_e processTx(txContext_t *context,
|
||||
uint8_t *buffer,
|
||||
const uint8_t *buffer,
|
||||
uint32_t length,
|
||||
uint32_t processingFlags);
|
||||
parserStatus_e continueTx(txContext_t *context);
|
||||
|
||||
@@ -27,7 +27,28 @@ const network_info_t NETWORK_MAPPING[] = {
|
||||
{.chain_id = 11297108109, .name = "Palm Network", .ticker = "PALM "},
|
||||
{.chain_id = 1818, .name = "Cube", .ticker = "CUBE "},
|
||||
{.chain_id = 336, .name = "Shiden", .ticker = "SDN "},
|
||||
{.chain_id = 592, .name = "Astar", .ticker = "ASTR "}};
|
||||
{.chain_id = 592, .name = "Astar", .ticker = "ASTR "},
|
||||
{.chain_id = 50, .name = "XDC", .ticker = "XDC "},
|
||||
{.chain_id = 82, .name = "Meter", .ticker = "MTR "},
|
||||
{.chain_id = 62621, .name = "Multivac", .ticker = "MTV "},
|
||||
{.chain_id = 20531812, .name = "Tecra", .ticker = "TCR "},
|
||||
{.chain_id = 20531811, .name = "TecraTestnet", .ticker = "TCR "},
|
||||
{.chain_id = 51, .name = "Apothemnetwork", .ticker = "XDC "},
|
||||
{.chain_id = 199, .name = "BTTC", .ticker = "BTT "},
|
||||
{.chain_id = 1030, .name = "Conflux", .ticker = "CFX "},
|
||||
{.chain_id = 61, .name = "Ethereum Classic", .ticker = "ETC "},
|
||||
{.chain_id = 246, .name = "EnergyWebChain", .ticker = "EWC "},
|
||||
{.chain_id = 14, .name = "Flare", .ticker = "FLR "},
|
||||
{.chain_id = 16, .name = "Flare Coston", .ticker = "FLR "},
|
||||
{.chain_id = 24, .name = "KardiaChain", .ticker = "KAI "},
|
||||
{.chain_id = 1284, .name = "Moonbeam", .ticker = "GLMR "},
|
||||
{.chain_id = 1285, .name = "Moonriver", .ticker = "MOVR "},
|
||||
{.chain_id = 66, .name = "OKXChain", .ticker = "OKT "},
|
||||
{.chain_id = 99, .name = "POA", .ticker = "POA "},
|
||||
{.chain_id = 7341, .name = "Shyft", .ticker = "SHFT "},
|
||||
{.chain_id = 19, .name = "Songbird", .ticker = "SGB "},
|
||||
{.chain_id = 73799, .name = "Volta", .ticker = "VOLTA "},
|
||||
{.chain_id = 25, .name = "Cronos", .ticker = "CRO "}};
|
||||
|
||||
uint64_t get_chain_id(void) {
|
||||
uint64_t chain_id = 0;
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#define MAX_NETWORK_TICKER_LEN 8
|
||||
|
||||
typedef struct network_info_s {
|
||||
const char name[NETWORK_STRING_MAX_SIZE];
|
||||
const char ticker[MAX_NETWORK_TICKER_LEN];
|
||||
const char *name;
|
||||
const char *ticker;
|
||||
uint64_t chain_id;
|
||||
} network_info_t;
|
||||
|
||||
|
||||
@@ -476,6 +476,11 @@ bool tostring256(uint256_t *number, uint32_t baseParam, char *out, uint32_t outL
|
||||
divmod256(&rDiv, &base, &rDiv, &rMod);
|
||||
out[offset++] = HEXDIGITS[(uint8_t) LOWER(LOWER(rMod))];
|
||||
} while (!zero256(&rDiv));
|
||||
|
||||
if (offset > (outLength - 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out[offset] = '\0';
|
||||
reverseString(out, offset);
|
||||
return true;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
|
||||
#include "ui_flow.h"
|
||||
|
||||
void handleGetAppConfiguration(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
|
||||
#include "ui_flow.h"
|
||||
#include "feature_getEth2PublicKey.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
static const uint8_t BLS12_381_FIELD_MODULUS[] = {
|
||||
0x1a, 0x01, 0x11, 0xea, 0x39, 0x7f, 0xe6, 0x9a, 0x4b, 0x1b, 0xa7, 0xb6, 0x43, 0x4b, 0xac, 0xd7,
|
||||
@@ -42,33 +43,29 @@ void getEth2PublicKey(uint32_t *bip32Path, uint8_t bip32PathLength, uint8_t *out
|
||||
|
||||
void handleGetEth2PublicKey(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(dataLength);
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
uint32_t i;
|
||||
uint8_t bip32PathLength = *(dataBuffer++);
|
||||
bip32_path_t bip32;
|
||||
|
||||
if (!called_from_swap) {
|
||||
reset_app_context();
|
||||
}
|
||||
if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
if (p2 != 0) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
for (i = 0; i < bip32PathLength; i++) {
|
||||
bip32Path[i] = U4BE(dataBuffer, 0);
|
||||
dataBuffer += 4;
|
||||
|
||||
dataBuffer = parseBip32(dataBuffer, &dataLength, &bip32);
|
||||
|
||||
if (dataBuffer == NULL) {
|
||||
THROW(0x6a80);
|
||||
}
|
||||
getEth2PublicKey(bip32Path, bip32PathLength, tmpCtx.publicKeyContext.publicKey.W);
|
||||
|
||||
getEth2PublicKey(bip32.path, bip32.length, tmpCtx.publicKeyContext.publicKey.W);
|
||||
|
||||
#ifndef NO_CONSENT
|
||||
if (p1 == P1_NON_CONFIRM)
|
||||
@@ -79,7 +76,7 @@ void handleGetEth2PublicKey(uint8_t p1,
|
||||
}
|
||||
#ifndef NO_CONSENT
|
||||
else {
|
||||
ux_flow_init(0, ux_display_public_eth2_flow, NULL);
|
||||
ui_display_public_eth2();
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "shared_context.h"
|
||||
#include "feature_getEth2PublicKey.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_eth2_address_ok(__attribute__((unused)) const bagl_element_t *e) {
|
||||
uint32_t tx = set_result_get_eth2_publicKey();
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
|
||||
#include "ui_flow.h"
|
||||
#include "feature_getPublicKey.h"
|
||||
#include "ethUtils.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
void handleGetPublicKey(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(dataLength);
|
||||
uint8_t privateKeyData[INT256_LENGTH];
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
uint32_t i;
|
||||
uint8_t bip32PathLength = *(dataBuffer++);
|
||||
bip32_path_t bip32;
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
|
||||
if (!called_from_swap) {
|
||||
reset_app_context();
|
||||
}
|
||||
if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
if ((p2 != P2_CHAINCODE) && (p2 != P2_NO_CHAINCODE)) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
for (i = 0; i < bip32PathLength; i++) {
|
||||
bip32Path[i] = U4BE(dataBuffer, 0);
|
||||
dataBuffer += 4;
|
||||
|
||||
dataBuffer = parseBip32(dataBuffer, &dataLength, &bip32);
|
||||
|
||||
if (dataBuffer == NULL) {
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
tmpCtx.publicKeyContext.getChaincode = (p2 == P2_CHAINCODE);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
os_perso_derive_node_bip32(
|
||||
CX_CURVE_256K1,
|
||||
bip32Path,
|
||||
bip32PathLength,
|
||||
bip32.path,
|
||||
bip32.length,
|
||||
privateKeyData,
|
||||
(tmpCtx.publicKeyContext.getChaincode ? tmpCtx.publicKeyContext.chainCode : NULL));
|
||||
cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey);
|
||||
@@ -66,7 +65,7 @@ void handleGetPublicKey(uint8_t p1,
|
||||
"0x%.*s",
|
||||
40,
|
||||
tmpCtx.publicKeyContext.address);
|
||||
ux_flow_init(0, ux_display_public_flow, NULL);
|
||||
ui_display_public_key();
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "shared_context.h"
|
||||
#include "feature_getPublicKey.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_address_ok(__attribute__((unused)) const bagl_element_t *e) {
|
||||
uint32_t tx = set_result_get_publicKey();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#include "apdu_constants.h"
|
||||
#include "ethUtils.h"
|
||||
|
||||
#include "ui_flow.h"
|
||||
#include "feature_performPrivacyOperation.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
#define P2_PUBLIC_ENCRYPTION_KEY 0x00
|
||||
#define P2_SHARED_SECRET 0x01
|
||||
@@ -25,43 +25,39 @@ void decodeScalar(const uint8_t *scalarIn, uint8_t *scalarOut) {
|
||||
|
||||
void handlePerformPrivacyOperation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(dataLength);
|
||||
uint8_t privateKeyData[INT256_LENGTH];
|
||||
uint8_t privateKeyDataSwapped[INT256_LENGTH];
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
uint8_t bip32PathLength = *(dataBuffer++);
|
||||
bip32_path_t bip32;
|
||||
cx_err_t status = CX_OK;
|
||||
if (p2 == P2_PUBLIC_ENCRYPTION_KEY) {
|
||||
if (dataLength < 1 + 4 * bip32PathLength) {
|
||||
THROW(0x6700);
|
||||
}
|
||||
} else if (p2 == P2_SHARED_SECRET) {
|
||||
if (dataLength < 1 + 4 * bip32PathLength + 32) {
|
||||
THROW(0x6700);
|
||||
}
|
||||
} else {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
for (uint8_t i = 0; i < bip32PathLength; i++) {
|
||||
bip32Path[i] = U4BE(dataBuffer, 0);
|
||||
dataBuffer += 4;
|
||||
|
||||
if ((p2 != P2_PUBLIC_ENCRYPTION_KEY) && (p2 != P2_SHARED_SECRET)) {
|
||||
THROW(0x6700);
|
||||
}
|
||||
|
||||
dataBuffer = parseBip32(dataBuffer, &dataLength, &bip32);
|
||||
|
||||
if (dataBuffer == NULL) {
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
if ((p2 == P2_SHARED_SECRET) && (dataLength < 32)) {
|
||||
THROW(0x6700);
|
||||
}
|
||||
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
|
||||
os_perso_derive_node_bip32(
|
||||
CX_CURVE_256K1,
|
||||
bip32Path,
|
||||
bip32PathLength,
|
||||
bip32.path,
|
||||
bip32.length,
|
||||
privateKeyData,
|
||||
(tmpCtx.publicKeyContext.getChaincode ? tmpCtx.publicKeyContext.chainCode : NULL));
|
||||
cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey);
|
||||
@@ -112,9 +108,9 @@ void handlePerformPrivacyOperation(uint8_t p1,
|
||||
32,
|
||||
privateKeyData);
|
||||
if (p2 == P2_PUBLIC_ENCRYPTION_KEY) {
|
||||
ux_flow_init(0, ux_display_privacy_public_key_flow, NULL);
|
||||
ui_display_privacy_public_key();
|
||||
} else {
|
||||
ux_flow_init(0, ux_display_privacy_shared_secret_flow, NULL);
|
||||
ui_display_privacy_shared_secret();
|
||||
}
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "shared_context.h"
|
||||
#include "feature_getPublicKey.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
#include "feature_performPrivacyOperation.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_privacy_ok(__attribute__((unused)) const bagl_element_t *e) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
#ifdef HAVE_CONTRACT_NAME_IN_DESCRIPTOR
|
||||
|
||||
@@ -101,7 +102,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
|
||||
void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
__attribute__((unused)) unsigned int *tx) {
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
#include "utils.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
#define TYPE_SIZE 1
|
||||
#define VERSION_SIZE 1
|
||||
@@ -53,7 +54,7 @@ typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
|
||||
|
||||
void handleProvideNFTInformation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
@@ -218,7 +219,7 @@ void handleProvideNFTInformation(uint8_t p1,
|
||||
hashId,
|
||||
hash,
|
||||
sizeof(hash),
|
||||
workBuffer + offset,
|
||||
(uint8_t *) workBuffer + offset,
|
||||
signatureLen)) {
|
||||
#ifndef HAVE_BYPASS_SIGNATURES
|
||||
PRINTF("Invalid NFT signature\n");
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
void handleSetEth2WithdrawalIndex(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
__attribute__((unused)) unsigned int *flags,
|
||||
__attribute__((unused)) unsigned int *tx) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
#include "eth_plugin_interface.h"
|
||||
#include "eth_plugin_internal.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
void handleSetExternalPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
#include "eth_plugin_interface.h"
|
||||
#include "eth_plugin_internal.h"
|
||||
#include "utils.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
// Supported internal plugins
|
||||
#define ERC721_STR "ERC721"
|
||||
@@ -86,7 +87,7 @@ static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) {
|
||||
|
||||
void handleSetPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
@@ -248,7 +249,7 @@ void handleSetPlugin(uint8_t p1,
|
||||
hashId,
|
||||
hash,
|
||||
sizeof(hash),
|
||||
workBuffer + offset,
|
||||
(unsigned char *) (workBuffer + offset),
|
||||
signatureLen)) {
|
||||
#ifndef HAVE_BYPASS_SIGNATURES
|
||||
PRINTF("Invalid NFT signature\n");
|
||||
|
||||
@@ -1,212 +1,284 @@
|
||||
#include <stdbool.h>
|
||||
#include "shared_context.h"
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include "apdu_constants.h"
|
||||
#include "utils.h"
|
||||
#include "ui_flow.h"
|
||||
#include "sign_message.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
static uint8_t processed_size;
|
||||
static struct {
|
||||
sign_message_state sign_state : 1;
|
||||
bool ui_started : 1;
|
||||
} states;
|
||||
|
||||
static const char SIGN_MAGIC[] =
|
||||
"\x19"
|
||||
"Ethereum Signed Message:\n";
|
||||
|
||||
/**
|
||||
* Check if a given character is a "special" displayable ASCII character
|
||||
* Send a response APDU with the given Status Word
|
||||
*
|
||||
* @param[in] c character we're checking
|
||||
* @return wether the character is special or not
|
||||
* @param[in] sw status word
|
||||
*/
|
||||
static inline bool is_char_special(char c) {
|
||||
return ((c >= '\b') && (c <= '\r'));
|
||||
static void apdu_reply(uint16_t sw) {
|
||||
G_io_apdu_buffer[0] = (sw >> 8) & 0xff;
|
||||
G_io_apdu_buffer[1] = sw & 0xff;
|
||||
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given data is made of ASCII characters
|
||||
* Get unprocessed data from last received APDU
|
||||
*
|
||||
* @param[in] data the input data
|
||||
* @param[in] the length of the input data
|
||||
* @return wether the data is fully ASCII or not
|
||||
* @return pointer to data in APDU buffer
|
||||
*/
|
||||
static bool is_data_ascii(const uint8_t *const data, size_t length) {
|
||||
for (uint8_t idx = 0; idx < length; ++idx) {
|
||||
if (!is_char_special(data[idx]) && ((data[idx] < 0x20) || (data[idx] > 0x7e))) {
|
||||
static const uint8_t *unprocessed_data(void) {
|
||||
return &G_io_apdu_buffer[OFFSET_CDATA] + processed_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size of unprocessed data from last received APDU
|
||||
*
|
||||
* @return size of data in bytes
|
||||
*/
|
||||
static size_t unprocessed_length(void) {
|
||||
return G_io_apdu_buffer[OFFSET_LC] - processed_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get used space from UI buffer
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
static size_t ui_buffer_length(void) {
|
||||
return strlen(UI_191_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remaining space from UI buffer
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
static size_t remaining_ui_buffer_length(void) {
|
||||
// -1 for the ending NULL byte
|
||||
return (sizeof(UI_191_BUFFER) - 1) - ui_buffer_length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get free space from UI buffer
|
||||
*
|
||||
* @return pointer to the free space
|
||||
*/
|
||||
static char *remaining_ui_buffer(void) {
|
||||
return &UI_191_BUFFER[ui_buffer_length()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the UI buffer
|
||||
*
|
||||
* Simply sets its first byte to a NULL character
|
||||
*/
|
||||
static void reset_ui_buffer(void) {
|
||||
UI_191_BUFFER[0] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the data specific to the first APDU of an EIP-191 signature
|
||||
*
|
||||
* @param[in] data the APDU payload
|
||||
* @param[in] length the payload size
|
||||
* @return pointer to the start of the start of the message; \ref NULL if it failed
|
||||
*/
|
||||
static const uint8_t *first_apdu_data(const uint8_t *data, uint16_t *length) {
|
||||
if (appState != APP_STATE_IDLE) {
|
||||
reset_app_context();
|
||||
}
|
||||
appState = APP_STATE_SIGNING_MESSAGE;
|
||||
data = parseBip32(data, length, &tmpCtx.messageSigningContext.bip32);
|
||||
if (data == NULL) {
|
||||
apdu_reply(0x6a80);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*length < sizeof(uint32_t)) {
|
||||
PRINTF("Invalid data\n");
|
||||
apdu_reply(0x6a80);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tmpCtx.messageSigningContext.remainingLength = U4BE(data, 0);
|
||||
data += sizeof(uint32_t);
|
||||
*length -= sizeof(uint32_t);
|
||||
|
||||
// Initialize message header + length
|
||||
cx_keccak_init(&global_sha3, 256);
|
||||
cx_hash((cx_hash_t *) &global_sha3, 0, (uint8_t *) SIGN_MAGIC, sizeof(SIGN_MAGIC) - 1, NULL, 0);
|
||||
snprintf(strings.tmp.tmp2,
|
||||
sizeof(strings.tmp.tmp2),
|
||||
"%u",
|
||||
tmpCtx.messageSigningContext.remainingLength);
|
||||
cx_hash((cx_hash_t *) &global_sha3,
|
||||
0,
|
||||
(uint8_t *) strings.tmp.tmp2,
|
||||
strlen(strings.tmp.tmp2),
|
||||
NULL,
|
||||
0);
|
||||
reset_ui_buffer();
|
||||
states.sign_state = STATE_191_HASH_DISPLAY;
|
||||
states.ui_started = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed the progressive hash with new data
|
||||
*
|
||||
* @param[in] data the new data
|
||||
* @param[in] length the data length
|
||||
* @return whether it was successful or not
|
||||
*/
|
||||
static bool feed_hash(const uint8_t *const data, const uint8_t length) {
|
||||
if (length > tmpCtx.messageSigningContext.remainingLength) {
|
||||
PRINTF("Error: Length mismatch ! (%u > %u)!\n",
|
||||
length,
|
||||
tmpCtx.messageSigningContext.remainingLength);
|
||||
apdu_reply(0x6a80);
|
||||
return false;
|
||||
}
|
||||
cx_hash((cx_hash_t *) &global_sha3, 0, data, length, NULL, 0);
|
||||
if ((tmpCtx.messageSigningContext.remainingLength -= length) == 0) {
|
||||
// Finalize hash
|
||||
cx_hash((cx_hash_t *) &global_sha3,
|
||||
CX_LAST,
|
||||
NULL,
|
||||
0,
|
||||
tmpCtx.messageSigningContext.hash,
|
||||
32);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed the UI with new data
|
||||
*/
|
||||
static void feed_display(void) {
|
||||
int c;
|
||||
|
||||
while ((unprocessed_length() > 0) && (remaining_ui_buffer_length() > 0)) {
|
||||
c = *(char *) unprocessed_data();
|
||||
if (isspace(c)) // to replace all white-space characters as spaces
|
||||
{
|
||||
c = ' ';
|
||||
}
|
||||
if (isprint(c)) {
|
||||
sprintf(remaining_ui_buffer(), "%c", (char) c);
|
||||
processed_size += 1;
|
||||
} else {
|
||||
if (remaining_ui_buffer_length() >= 4) // 4 being the fixed length of \x00
|
||||
{
|
||||
snprintf(remaining_ui_buffer(), remaining_ui_buffer_length(), "\\x%02x", c);
|
||||
processed_size += 1;
|
||||
} else {
|
||||
// fill the rest of the UI buffer spaces, to consider the buffer full
|
||||
memset(remaining_ui_buffer(), ' ', remaining_ui_buffer_length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((remaining_ui_buffer_length() == 0) ||
|
||||
(tmpCtx.messageSigningContext.remainingLength == 0)) {
|
||||
if (!states.ui_started) {
|
||||
ui_191_start();
|
||||
states.ui_started = true;
|
||||
} else {
|
||||
ui_191_switch_to_message();
|
||||
}
|
||||
}
|
||||
|
||||
if ((unprocessed_length() == 0) && (tmpCtx.messageSigningContext.remainingLength > 0)) {
|
||||
apdu_reply(0x9000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EIP-191 APDU handler
|
||||
*
|
||||
* @param[in] p1 instruction parameter 1
|
||||
* @param[in] p2 instruction parameter 2
|
||||
* @param[in] payload received data
|
||||
* @param[in] length data length
|
||||
* @return whether the handling of the APDU was successful or not
|
||||
*/
|
||||
bool handleSignPersonalMessage(uint8_t p1,
|
||||
uint8_t p2,
|
||||
const uint8_t *const payload,
|
||||
uint8_t length) {
|
||||
const uint8_t *data = payload;
|
||||
uint16_t u16_length = length;
|
||||
|
||||
(void) p2;
|
||||
processed_size = 0;
|
||||
if (p1 == P1_FIRST) {
|
||||
if ((data = first_apdu_data(data, &u16_length)) == NULL) {
|
||||
return false;
|
||||
}
|
||||
processed_size = data - payload;
|
||||
} else if (p1 != P1_MORE) {
|
||||
PRINTF("Error: Unexpected P1 (%u)!\n", p1);
|
||||
apdu_reply(0x6B00);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!feed_hash(data, u16_length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (states.sign_state == STATE_191_HASH_DISPLAY) {
|
||||
feed_display();
|
||||
} else // hash only
|
||||
{
|
||||
if (tmpCtx.messageSigningContext.remainingLength == 0) {
|
||||
#ifdef NO_CONSENT
|
||||
io_seproxyhal_touch_signMessage_ok();
|
||||
#else
|
||||
ui_191_switch_to_sign();
|
||||
#endif
|
||||
} else {
|
||||
apdu_reply(0x9000);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize value string that will be displayed in the UX STEP
|
||||
*
|
||||
* @param[in] if the value is ASCII
|
||||
* Decide whether to show the question to show more of the message or not
|
||||
*/
|
||||
static void init_value_str(bool is_ascii) {
|
||||
if (is_ascii) {
|
||||
strings.tmp.tmp[0] = '\0'; // init string as empty
|
||||
void question_switcher(void) {
|
||||
if ((states.sign_state == STATE_191_HASH_DISPLAY) &&
|
||||
((tmpCtx.messageSigningContext.remainingLength > 0) || (unprocessed_length() > 0))) {
|
||||
ui_191_switch_to_question();
|
||||
} else {
|
||||
strcpy(strings.tmp.tmp, "0x"); // will display the hex bytes instead
|
||||
// Go to Sign / Cancel
|
||||
ui_191_switch_to_sign();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the currently stored data is initialized as ASCII or not
|
||||
* The user has decided to skip the rest of the message
|
||||
*/
|
||||
static bool is_value_str_ascii() {
|
||||
return (memcmp(strings.tmp.tmp, "0x", 2) != 0);
|
||||
void skip_rest_of_message(void) {
|
||||
states.sign_state = STATE_191_HASH_ONLY;
|
||||
if (tmpCtx.messageSigningContext.remainingLength > 0) {
|
||||
apdu_reply(0x9000);
|
||||
} else {
|
||||
ui_191_switch_to_sign();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the global UI string variable by formatting & appending the new data to it
|
||||
*
|
||||
* @param[in] data the input data
|
||||
* @param[in] length the data length
|
||||
* @param[in] is_ascii wether the data is ASCII or not
|
||||
* The user has decided to see the next chunk of the message
|
||||
*/
|
||||
static void feed_value_str(const uint8_t *const data, size_t length, bool is_ascii) {
|
||||
uint16_t value_strlen = strlen(strings.tmp.tmp);
|
||||
|
||||
if ((value_strlen + 1) < sizeof(strings.tmp.tmp)) {
|
||||
if (is_ascii) {
|
||||
uint8_t src_idx = 0;
|
||||
uint16_t dst_idx = value_strlen;
|
||||
bool prev_is_special = false;
|
||||
|
||||
while ((src_idx < length) && (dst_idx < sizeof(strings.tmp.tmp))) {
|
||||
if (prev_is_special) {
|
||||
if (!is_char_special(data[src_idx])) {
|
||||
prev_is_special = false;
|
||||
}
|
||||
} else {
|
||||
if (is_char_special(data[src_idx])) {
|
||||
prev_is_special = true;
|
||||
strings.tmp.tmp[dst_idx] = ' ';
|
||||
dst_idx += 1;
|
||||
}
|
||||
}
|
||||
if (!is_char_special(data[src_idx])) {
|
||||
strings.tmp.tmp[dst_idx] = data[src_idx];
|
||||
dst_idx += 1;
|
||||
}
|
||||
src_idx += 1;
|
||||
}
|
||||
|
||||
if (dst_idx < sizeof(strings.tmp.tmp)) {
|
||||
strings.tmp.tmp[dst_idx] = '\0';
|
||||
} else {
|
||||
const char marker[] = "...";
|
||||
|
||||
memcpy(strings.tmp.tmp + sizeof(strings.tmp.tmp) - sizeof(marker),
|
||||
marker,
|
||||
sizeof(marker));
|
||||
}
|
||||
} else {
|
||||
// truncate to strings.tmp.tmp 's size
|
||||
length = MIN(length, (sizeof(strings.tmp.tmp) - value_strlen) / 2);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
snprintf(strings.tmp.tmp + value_strlen + 2 * i,
|
||||
sizeof(strings.tmp.tmp) - value_strlen - 2 * i,
|
||||
"%02X",
|
||||
data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleSignPersonalMessage(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(tx);
|
||||
uint8_t hashMessage[INT256_LENGTH];
|
||||
if (p1 == P1_FIRST) {
|
||||
char tmp[11] = {0};
|
||||
uint32_t i;
|
||||
if (dataLength < 1) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
if (appState != APP_STATE_IDLE) {
|
||||
reset_app_context();
|
||||
}
|
||||
appState = APP_STATE_SIGNING_MESSAGE;
|
||||
|
||||
tmpCtx.messageSigningContext.pathLength = workBuffer[0];
|
||||
if ((tmpCtx.messageSigningContext.pathLength < 0x01) ||
|
||||
(tmpCtx.messageSigningContext.pathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
workBuffer++;
|
||||
dataLength--;
|
||||
for (i = 0; i < tmpCtx.messageSigningContext.pathLength; i++) {
|
||||
if (dataLength < sizeof(uint32_t)) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
tmpCtx.messageSigningContext.bip32Path[i] = U4BE(workBuffer, 0);
|
||||
workBuffer += sizeof(uint32_t);
|
||||
dataLength -= sizeof(uint32_t);
|
||||
}
|
||||
if (dataLength < sizeof(uint32_t)) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
tmpCtx.messageSigningContext.remainingLength = U4BE(workBuffer, 0);
|
||||
workBuffer += sizeof(uint32_t);
|
||||
dataLength -= sizeof(uint32_t);
|
||||
// Initialize message header + length
|
||||
cx_keccak_init(&global_sha3, 256);
|
||||
cx_hash((cx_hash_t *) &global_sha3,
|
||||
0,
|
||||
(uint8_t *) SIGN_MAGIC,
|
||||
sizeof(SIGN_MAGIC) - 1,
|
||||
NULL,
|
||||
0);
|
||||
snprintf(tmp, sizeof(tmp), "%u", tmpCtx.messageSigningContext.remainingLength);
|
||||
cx_hash((cx_hash_t *) &global_sha3, 0, (uint8_t *) tmp, strlen(tmp), NULL, 0);
|
||||
cx_sha256_init(&tmpContent.sha2);
|
||||
|
||||
init_value_str(is_data_ascii(workBuffer, dataLength));
|
||||
|
||||
} else if (p1 != P1_MORE) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
if (p2 != 0) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
if ((p1 == P1_MORE) && (appState != APP_STATE_SIGNING_MESSAGE)) {
|
||||
PRINTF("Signature not initialized\n");
|
||||
THROW(0x6985);
|
||||
}
|
||||
if (dataLength > tmpCtx.messageSigningContext.remainingLength) {
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
cx_hash((cx_hash_t *) &global_sha3, 0, workBuffer, dataLength, NULL, 0);
|
||||
cx_hash((cx_hash_t *) &tmpContent.sha2, 0, workBuffer, dataLength, NULL, 0);
|
||||
tmpCtx.messageSigningContext.remainingLength -= dataLength;
|
||||
|
||||
feed_value_str(workBuffer, dataLength, is_value_str_ascii());
|
||||
|
||||
if (tmpCtx.messageSigningContext.remainingLength == 0) {
|
||||
cx_hash((cx_hash_t *) &global_sha3,
|
||||
CX_LAST,
|
||||
workBuffer,
|
||||
0,
|
||||
tmpCtx.messageSigningContext.hash,
|
||||
32);
|
||||
cx_hash((cx_hash_t *) &tmpContent.sha2, CX_LAST, workBuffer, 0, hashMessage, 32);
|
||||
|
||||
#ifdef NO_CONSENT
|
||||
io_seproxyhal_touch_signMessage_ok(NULL);
|
||||
#else // NO_CONSENT
|
||||
ux_flow_init(0, ux_sign_flow, NULL);
|
||||
#endif // NO_CONSENT
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
|
||||
} else {
|
||||
THROW(0x9000);
|
||||
void continue_displaying_message(void) {
|
||||
reset_ui_buffer();
|
||||
if (unprocessed_length() > 0) {
|
||||
feed_display();
|
||||
}
|
||||
}
|
||||
|
||||
12
src_features/signMessage/sign_message.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef SIGN_MESSAGE_H_
|
||||
#define SIGN_MESSAGE_H_
|
||||
|
||||
#define UI_191_BUFFER strings.tmp.tmp
|
||||
|
||||
typedef enum { STATE_191_HASH_DISPLAY = 0, STATE_191_HASH_ONLY } sign_message_state;
|
||||
|
||||
void question_switcher(void);
|
||||
void skip_rest_of_message(void);
|
||||
void continue_displaying_message(void);
|
||||
|
||||
#endif // SIGN_MESSAGE_H_
|
||||
@@ -1,16 +1,15 @@
|
||||
#include "os_io_seproxyhal.h"
|
||||
#include "shared_context.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_signMessage_ok(__attribute__((unused)) const bagl_element_t *e) {
|
||||
unsigned int io_seproxyhal_touch_signMessage_ok(void) {
|
||||
uint8_t privateKeyData[INT256_LENGTH];
|
||||
uint8_t signature[100];
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
uint32_t tx = 0;
|
||||
io_seproxyhal_io_heartbeat();
|
||||
os_perso_derive_node_bip32(CX_CURVE_256K1,
|
||||
tmpCtx.messageSigningContext.bip32Path,
|
||||
tmpCtx.messageSigningContext.pathLength,
|
||||
tmpCtx.messageSigningContext.bip32.path,
|
||||
tmpCtx.messageSigningContext.bip32.length,
|
||||
privateKeyData,
|
||||
NULL);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
@@ -46,8 +45,7 @@ unsigned int io_seproxyhal_touch_signMessage_ok(__attribute__((unused)) const ba
|
||||
return 0; // do not redraw the widget
|
||||
}
|
||||
|
||||
unsigned int io_seproxyhal_touch_signMessage_cancel(__attribute__((unused))
|
||||
const bagl_element_t *e) {
|
||||
unsigned int io_seproxyhal_touch_signMessage_cancel(void) {
|
||||
reset_app_context();
|
||||
G_io_apdu_buffer[0] = 0x69;
|
||||
G_io_apdu_buffer[1] = 0x85;
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "shared_context.h"
|
||||
#include "ui_callbacks.h"
|
||||
|
||||
// clang-format off
|
||||
UX_STEP_NOCB(
|
||||
ux_sign_flow_1_step,
|
||||
pnn,
|
||||
{
|
||||
&C_icon_certificate,
|
||||
"Sign",
|
||||
"message",
|
||||
});
|
||||
UX_STEP_NOCB(
|
||||
ux_sign_flow_2_step,
|
||||
bnnn_paging,
|
||||
{
|
||||
.title = "Message",
|
||||
.text = strings.tmp.tmp,
|
||||
});
|
||||
UX_STEP_CB(
|
||||
ux_sign_flow_3_step,
|
||||
pbb,
|
||||
io_seproxyhal_touch_signMessage_ok(NULL),
|
||||
{
|
||||
&C_icon_validate_14,
|
||||
"Sign",
|
||||
"message",
|
||||
});
|
||||
UX_STEP_CB(
|
||||
ux_sign_flow_4_step,
|
||||
pbb,
|
||||
io_seproxyhal_touch_signMessage_cancel(NULL),
|
||||
{
|
||||
&C_icon_crossmark,
|
||||
"Cancel",
|
||||
"signature",
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
UX_FLOW(ux_sign_flow,
|
||||
&ux_sign_flow_1_step,
|
||||
&ux_sign_flow_2_step,
|
||||
&ux_sign_flow_3_step,
|
||||
&ux_sign_flow_4_step);
|
||||
@@ -1,16 +1,14 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "utils.h"
|
||||
#include "ui_flow.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
void handleSignEIP712Message(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
uint8_t i;
|
||||
|
||||
UNUSED(tx);
|
||||
if ((p1 != 00) || (p2 != 00)) {
|
||||
THROW(0x6B00);
|
||||
@@ -18,38 +16,20 @@ void handleSignEIP712Message(uint8_t p1,
|
||||
if (appState != APP_STATE_IDLE) {
|
||||
reset_app_context();
|
||||
}
|
||||
if (dataLength < 1) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
tmpCtx.messageSigningContext712.pathLength = workBuffer[0];
|
||||
if ((tmpCtx.messageSigningContext712.pathLength < 0x01) ||
|
||||
(tmpCtx.messageSigningContext712.pathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
workBuffer++;
|
||||
dataLength--;
|
||||
for (i = 0; i < tmpCtx.messageSigningContext712.pathLength; i++) {
|
||||
if (dataLength < 4) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
tmpCtx.messageSigningContext712.bip32Path[i] = U4BE(workBuffer, 0);
|
||||
workBuffer += 4;
|
||||
dataLength -= 4;
|
||||
}
|
||||
if (dataLength < 32 + 32) {
|
||||
PRINTF("Invalid data\n");
|
||||
|
||||
workBuffer = parseBip32(workBuffer, &dataLength, &tmpCtx.messageSigningContext.bip32);
|
||||
|
||||
if (workBuffer == NULL || dataLength < 32 + 32) {
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
memmove(tmpCtx.messageSigningContext712.domainHash, workBuffer, 32);
|
||||
memmove(tmpCtx.messageSigningContext712.messageHash, workBuffer + 32, 32);
|
||||
|
||||
#ifdef NO_CONSENT
|
||||
io_seproxyhal_touch_signMessage_ok(NULL);
|
||||
io_seproxyhal_touch_signMessage_ok();
|
||||
#else // NO_CONSENT
|
||||
ux_flow_init(0, ux_sign_712_v0_flow, NULL);
|
||||
ui_sign_712_v0();
|
||||
#endif // NO_CONSENT
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "os_io_seproxyhal.h"
|
||||
#include "shared_context.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
static const uint8_t EIP_712_MAGIC[] = {0x19, 0x01};
|
||||
|
||||
@@ -34,8 +34,8 @@ unsigned int io_seproxyhal_touch_signMessage712_v0_ok(__attribute__((unused))
|
||||
PRINTF("EIP712 hash to sign %.*H\n", 32, hash);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
os_perso_derive_node_bip32(CX_CURVE_256K1,
|
||||
tmpCtx.messageSigningContext712.bip32Path,
|
||||
tmpCtx.messageSigningContext712.pathLength,
|
||||
tmpCtx.messageSigningContext712.bip32.path,
|
||||
tmpCtx.messageSigningContext712.bip32.length,
|
||||
privateKeyData,
|
||||
NULL);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
|
||||
@@ -1,54 +1,43 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "feature_signTx.h"
|
||||
#include "eth_plugin_interface.h"
|
||||
|
||||
void handleSign(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
const uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(tx);
|
||||
parserStatus_e txResult;
|
||||
uint32_t i;
|
||||
|
||||
if (os_global_pin_is_validated() != BOLOS_UX_OK) {
|
||||
PRINTF("Device is PIN-locked");
|
||||
THROW(0x6982);
|
||||
}
|
||||
if (p1 == P1_FIRST) {
|
||||
if (dataLength < 1) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
if (appState != APP_STATE_IDLE) {
|
||||
reset_app_context();
|
||||
}
|
||||
appState = APP_STATE_SIGNING_TX;
|
||||
tmpCtx.transactionContext.pathLength = workBuffer[0];
|
||||
if ((tmpCtx.transactionContext.pathLength < 0x01) ||
|
||||
(tmpCtx.transactionContext.pathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
|
||||
workBuffer = parseBip32(workBuffer, &dataLength, &tmpCtx.transactionContext.bip32);
|
||||
|
||||
if (workBuffer == NULL) {
|
||||
THROW(0x6a80);
|
||||
}
|
||||
workBuffer++;
|
||||
dataLength--;
|
||||
for (i = 0; i < tmpCtx.transactionContext.pathLength; i++) {
|
||||
if (dataLength < 4) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
tmpCtx.transactionContext.bip32Path[i] = U4BE(workBuffer, 0);
|
||||
workBuffer += 4;
|
||||
dataLength -= 4;
|
||||
}
|
||||
|
||||
tmpContent.txContent.dataPresent = false;
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
|
||||
initTx(&txContext, &global_sha3, &tmpContent.txContent, customProcessor, NULL);
|
||||
|
||||
if (dataLength < 1) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
// EIP 2718: TransactionType might be present before the TransactionPayload.
|
||||
uint8_t txType = *workBuffer;
|
||||
if (txType >= MIN_TX_TYPE && txType <= MAX_TX_TYPE) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "shared_context.h"
|
||||
#include "utils.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "ui_flow.h"
|
||||
#include "feature_signTx.h"
|
||||
#ifdef HAVE_STARKWARE
|
||||
#include "stark_utils.h"
|
||||
@@ -9,6 +7,8 @@
|
||||
#include "eth_plugin_handler.h"
|
||||
#include "network.h"
|
||||
#include "ethUtils.h"
|
||||
#include "common_ui.h"
|
||||
#include "ui_callbacks.h"
|
||||
|
||||
#define ERR_SILENT_MODE_CHECK_FAILED 0x6001
|
||||
|
||||
@@ -144,7 +144,7 @@ customStatus_e customProcessor(txContext_t *context) {
|
||||
dataContext.tokenContext.fieldOffset = 0;
|
||||
if (fieldPos == 0) {
|
||||
array_hexstr(strings.tmp.tmp, dataContext.tokenContext.data, 4);
|
||||
ux_flow_init(0, ux_confirm_selector_flow, NULL);
|
||||
ui_confirm_selector();
|
||||
} else {
|
||||
uint32_t offset = 0;
|
||||
uint32_t i;
|
||||
@@ -159,7 +159,7 @@ customStatus_e customProcessor(txContext_t *context) {
|
||||
strings.tmp.tmp[offset++] = ':';
|
||||
}
|
||||
}
|
||||
ux_flow_init(0, ux_confirm_parameter_flow, NULL);
|
||||
ui_confirm_parameter();
|
||||
}
|
||||
} else {
|
||||
return CUSTOM_HANDLED;
|
||||
@@ -231,15 +231,26 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
|
||||
i = 0;
|
||||
tickerOffset = 0;
|
||||
memset(displayBuffer, 0, displayBufferSize);
|
||||
|
||||
while (feeTicker[tickerOffset]) {
|
||||
if ((uint32_t) tickerOffset >= displayBufferSize) {
|
||||
break;
|
||||
}
|
||||
|
||||
displayBuffer[tickerOffset] = feeTicker[tickerOffset];
|
||||
tickerOffset++;
|
||||
}
|
||||
while (G_io_apdu_buffer[i]) {
|
||||
if ((uint32_t) (tickerOffset) + i >= displayBufferSize) {
|
||||
break;
|
||||
}
|
||||
displayBuffer[tickerOffset + i] = G_io_apdu_buffer[i];
|
||||
i++;
|
||||
}
|
||||
displayBuffer[tickerOffset + i] = '\0';
|
||||
|
||||
if ((uint32_t) (tickerOffset) + i < displayBufferSize) {
|
||||
displayBuffer[tickerOffset + i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the fees, transform it to a string, prepend a ticker to it and copy everything to
|
||||
@@ -282,8 +293,8 @@ static void get_public_key(uint8_t *out, uint8_t outLength) {
|
||||
}
|
||||
|
||||
os_perso_derive_node_bip32(CX_CURVE_256K1,
|
||||
tmpCtx.transactionContext.bip32Path,
|
||||
tmpCtx.transactionContext.pathLength,
|
||||
tmpCtx.transactionContext.bip32.path,
|
||||
tmpCtx.transactionContext.bip32.length,
|
||||
privateKeyData,
|
||||
NULL);
|
||||
cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "os_io_seproxyhal.h"
|
||||
#include "shared_context.h"
|
||||
#include "utils.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_tx_ok(__attribute__((unused)) const bagl_element_t *e) {
|
||||
uint8_t privateKeyData[INT256_LENGTH];
|
||||
@@ -10,8 +10,8 @@ unsigned int io_seproxyhal_touch_tx_ok(__attribute__((unused)) const bagl_elemen
|
||||
uint32_t tx = 0;
|
||||
io_seproxyhal_io_heartbeat();
|
||||
os_perso_derive_node_bip32(CX_CURVE_256K1,
|
||||
tmpCtx.transactionContext.bip32Path,
|
||||
tmpCtx.transactionContext.pathLength,
|
||||
tmpCtx.transactionContext.bip32.path,
|
||||
tmpCtx.transactionContext.bip32.length,
|
||||
privateKeyData,
|
||||
NULL);
|
||||
cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey);
|
||||
|
||||
@@ -4,37 +4,37 @@
|
||||
#include "apdu_constants.h"
|
||||
#include "stark_utils.h"
|
||||
#include "feature_stark_getPublicKey.h"
|
||||
#include "ui_flow.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
void handleStarkwareGetPublicKey(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(dataLength);
|
||||
uint8_t privateKeyData[32];
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
uint32_t i;
|
||||
uint8_t bip32PathLength = *(dataBuffer++);
|
||||
bip32_path_t bip32;
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
uint8_t privateKeyData[32];
|
||||
|
||||
reset_app_context();
|
||||
if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
|
||||
if (p2 != 0) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
for (i = 0; i < bip32PathLength; i++) {
|
||||
bip32Path[i] = U4BE(dataBuffer, 0);
|
||||
dataBuffer += 4;
|
||||
|
||||
dataBuffer = parseBip32(dataBuffer, &dataLength, &bip32);
|
||||
|
||||
if (dataBuffer == NULL) {
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
io_seproxyhal_io_heartbeat();
|
||||
starkDerivePrivateKey(bip32Path, bip32PathLength, privateKeyData);
|
||||
starkDerivePrivateKey(bip32.path, bip32.length, privateKeyData);
|
||||
cx_ecfp_init_private_key(CX_CURVE_Stark256, privateKeyData, 32, &privateKey);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
cx_ecfp_generate_pair(CX_CURVE_Stark256, &tmpCtx.publicKeyContext.publicKey, &privateKey, 1);
|
||||
@@ -56,7 +56,7 @@ void handleStarkwareGetPublicKey(uint8_t p1,
|
||||
"0x%.*H",
|
||||
32,
|
||||
tmpCtx.publicKeyContext.publicKey.W + 1);
|
||||
ux_flow_init(0, ux_display_stark_public_flow, NULL);
|
||||
ui_display_stark_public();
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifdef HAVE_STARKWARE
|
||||
|
||||
#include "shared_context.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
#include "feature_stark_getPublicKey.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_stark_pubkey_ok(__attribute__((unused)) const bagl_element_t *e) {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "ethUtils.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
void handleStarkwareProvideQuantum(uint8_t p1,
|
||||
__attribute__((unused)) uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
__attribute__((unused)) unsigned int *flags,
|
||||
__attribute__((unused)) unsigned int *tx) {
|
||||
@@ -35,7 +35,7 @@ void handleStarkwareProvideQuantum(uint8_t p1,
|
||||
THROW(0x6700);
|
||||
}
|
||||
if (p1 == STARK_QUANTUM_LEGACY) {
|
||||
addressZero = allzeroes(dataBuffer, 20);
|
||||
addressZero = allzeroes((void *) dataBuffer, 20);
|
||||
}
|
||||
if ((p1 != STARK_QUANTUM_ETH) && !addressZero) {
|
||||
for (i = 0; i < MAX_ITEMS; i++) {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "stark_utils.h"
|
||||
#include "ui_flow.h"
|
||||
#include "poorstream.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "ethUtils.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
#define U8BE(buf, off) \
|
||||
(uint64_t)((((uint64_t) U4BE(buf, off)) << 32) | (((uint64_t) U4BE(buf, off + 4)) & 0xFFFFFFFF))
|
||||
@@ -20,7 +20,7 @@ void handleStarkwareSignMessage(uint8_t p1,
|
||||
__attribute__((unused)) unsigned int *tx) {
|
||||
uint8_t privateKeyData[INT256_LENGTH];
|
||||
uint32_t i;
|
||||
uint8_t bip32PathLength = *(dataBuffer);
|
||||
uint8_t bip32PathLength;
|
||||
uint8_t offset = 1;
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
poorstream_t bitstream;
|
||||
@@ -29,10 +29,19 @@ void handleStarkwareSignMessage(uint8_t p1,
|
||||
uint8_t protocol = 2;
|
||||
uint8_t preOffset, postOffset;
|
||||
uint8_t zeroTest;
|
||||
|
||||
// Initial checks
|
||||
if (appState != APP_STATE_IDLE) {
|
||||
reset_app_context();
|
||||
}
|
||||
|
||||
if (dataLength < 1) {
|
||||
PRINTF("Invalid data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
bip32PathLength = *(dataBuffer);
|
||||
|
||||
if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
@@ -70,10 +79,10 @@ void handleStarkwareSignMessage(uint8_t p1,
|
||||
if (p2 != 0) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
tmpCtx.transactionContext.pathLength = bip32PathLength;
|
||||
tmpCtx.transactionContext.bip32.length = bip32PathLength;
|
||||
for (i = 0; i < bip32PathLength; i++) {
|
||||
tmpCtx.transactionContext.bip32Path[i] = U4BE(dataBuffer, offset);
|
||||
PRINTF("Storing path %d %d\n", i, tmpCtx.transactionContext.bip32Path[i]);
|
||||
tmpCtx.transactionContext.bip32.path[i] = U4BE(dataBuffer, offset);
|
||||
PRINTF("Storing path %d %d\n", i, tmpCtx.transactionContext.bip32.path[i]);
|
||||
offset += 4;
|
||||
}
|
||||
// Discard the path to use part of dataBuffer as a temporary buffer
|
||||
@@ -205,7 +214,9 @@ void handleStarkwareSignMessage(uint8_t p1,
|
||||
cx_ecfp_public_key_t publicKey;
|
||||
// Check if the transfer is a self transfer
|
||||
io_seproxyhal_io_heartbeat();
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32Path, bip32PathLength, privateKeyData);
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32.path,
|
||||
bip32PathLength,
|
||||
privateKeyData);
|
||||
cx_ecfp_init_private_key(CX_CURVE_Stark256, privateKeyData, 32, &privateKey);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
cx_ecfp_generate_pair(CX_CURVE_Stark256, &publicKey, &privateKey, 1);
|
||||
@@ -238,20 +249,9 @@ void handleStarkwareSignMessage(uint8_t p1,
|
||||
}
|
||||
}
|
||||
if (order) {
|
||||
ux_flow_init(0, ux_stark_limit_order_flow, NULL);
|
||||
ui_stark_limit_order();
|
||||
} else {
|
||||
if (selfTransfer) {
|
||||
ux_flow_init(
|
||||
0,
|
||||
(dataContext.starkContext.conditional ? ux_stark_self_transfer_conditional_flow
|
||||
: ux_stark_self_transfer_flow),
|
||||
NULL);
|
||||
} else {
|
||||
ux_flow_init(0,
|
||||
(dataContext.starkContext.conditional ? ux_stark_transfer_conditional_flow
|
||||
: ux_stark_transfer_flow),
|
||||
NULL);
|
||||
}
|
||||
ui_stark_transfer(selfTransfer, dataContext.starkContext.conditional);
|
||||
}
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
#include "os_io_seproxyhal.h"
|
||||
#include "shared_context.h"
|
||||
#include "stark_utils.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_stark_ok(__attribute__((unused)) const bagl_element_t *e) {
|
||||
uint8_t privateKeyData[32];
|
||||
uint8_t signature[72];
|
||||
uint32_t tx = 0;
|
||||
io_seproxyhal_io_heartbeat();
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32Path,
|
||||
tmpCtx.transactionContext.pathLength,
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32.path,
|
||||
tmpCtx.transactionContext.bip32.length,
|
||||
privateKeyData);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
stark_sign(signature,
|
||||
|
||||
@@ -3,46 +3,43 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "stark_utils.h"
|
||||
#include "ui_flow.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
#include "os_io_seproxyhal.h"
|
||||
|
||||
void handleStarkwareUnsafeSign(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
const uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
__attribute__((unused)) unsigned int *tx) {
|
||||
uint32_t i;
|
||||
uint8_t privateKeyData[INT256_LENGTH];
|
||||
cx_ecfp_public_key_t publicKey;
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
uint8_t bip32PathLength = *(dataBuffer);
|
||||
uint8_t offset = 1;
|
||||
|
||||
// Initial checks
|
||||
if (appState != APP_STATE_IDLE) {
|
||||
reset_app_context();
|
||||
}
|
||||
if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) {
|
||||
PRINTF("Invalid path\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
if ((p1 != 0) || (p2 != 0)) {
|
||||
THROW(0x6B00);
|
||||
}
|
||||
|
||||
if (dataLength != 32 + 4 * bip32PathLength + 1) {
|
||||
dataBuffer = parseBip32(dataBuffer, &dataLength, &tmpCtx.transactionContext.bip32);
|
||||
|
||||
if (dataBuffer == NULL) {
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
if (dataLength != 32) {
|
||||
THROW(0x6700);
|
||||
}
|
||||
|
||||
tmpCtx.transactionContext.pathLength = bip32PathLength;
|
||||
for (i = 0; i < bip32PathLength; i++) {
|
||||
tmpCtx.transactionContext.bip32Path[i] = U4BE(dataBuffer, offset);
|
||||
PRINTF("Storing path %d %d\n", i, tmpCtx.transactionContext.bip32Path[i]);
|
||||
offset += 4;
|
||||
}
|
||||
memmove(dataContext.starkContext.w2, dataBuffer + offset, 32);
|
||||
memmove(dataContext.starkContext.w2, dataBuffer, 32);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32Path, bip32PathLength, privateKeyData);
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32.path,
|
||||
tmpCtx.transactionContext.bip32.length,
|
||||
privateKeyData);
|
||||
cx_ecfp_init_private_key(CX_CURVE_Stark256, privateKeyData, 32, &privateKey);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
cx_ecfp_generate_pair(CX_CURVE_Stark256, &publicKey, &privateKey, 1);
|
||||
@@ -50,7 +47,7 @@ void handleStarkwareUnsafeSign(uint8_t p1,
|
||||
explicit_bzero(privateKeyData, sizeof(privateKeyData));
|
||||
io_seproxyhal_io_heartbeat();
|
||||
memmove(dataContext.starkContext.w1, publicKey.W + 1, 32);
|
||||
ux_flow_init(0, ux_stark_unsafe_sign_flow, NULL);
|
||||
ui_stark_unsafe_sign();
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "os_io_seproxyhal.h"
|
||||
#include "shared_context.h"
|
||||
#include "stark_utils.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include "common_ui.h"
|
||||
|
||||
unsigned int io_seproxyhal_touch_stark_unsafe_sign_ok(__attribute__((unused))
|
||||
const bagl_element_t *e) {
|
||||
@@ -13,8 +13,8 @@ unsigned int io_seproxyhal_touch_stark_unsafe_sign_ok(__attribute__((unused))
|
||||
unsigned int info = 0;
|
||||
uint32_t tx = 0;
|
||||
io_seproxyhal_io_heartbeat();
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32Path,
|
||||
tmpCtx.transactionContext.pathLength,
|
||||
starkDerivePrivateKey(tmpCtx.transactionContext.bip32.path,
|
||||
tmpCtx.transactionContext.bip32.length,
|
||||
privateKeyData);
|
||||
io_seproxyhal_io_heartbeat();
|
||||
cx_ecfp_init_private_key(CX_CURVE_Stark256, privateKeyData, 32, &privateKey);
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
#include <string.h>
|
||||
#include "eth_plugin_interface.h"
|
||||
#include "shared_context.h" // TODO : rewrite as independant code
|
||||
#include "eth_plugin_internal.h" // TODO : rewrite as independant code
|
||||
#include "utils.h"
|
||||
#include "ethUtils.h"
|
||||
|
||||
typedef enum {
|
||||
COMPOUND_REDEEM_UNDERLYING = 0,
|
||||
COMPOUND_REDEEM,
|
||||
COMPOUND_MINT,
|
||||
CETH_MINT
|
||||
} compoundSelector_t;
|
||||
|
||||
static const uint8_t COMPOUND_EXPECTED_DATA_SIZE[] = {
|
||||
4 + 32,
|
||||
4 + 32,
|
||||
4 + 32,
|
||||
4,
|
||||
};
|
||||
|
||||
// redeemUnderlying : redeemAmount (32)
|
||||
// redeem underlying token
|
||||
// redeem : redeemTokens (32)
|
||||
// redeem Ctoken
|
||||
// mint : mintAmount (32)
|
||||
// lend some token
|
||||
// mint :
|
||||
// lend some Ether
|
||||
|
||||
typedef struct compound_parameters_t {
|
||||
uint8_t selectorIndex;
|
||||
uint8_t amount[32];
|
||||
char ticker_1[MAX_TICKER_LEN];
|
||||
uint8_t decimals;
|
||||
} compound_parameters_t;
|
||||
|
||||
typedef struct underlying_asset_decimals_t {
|
||||
char c_ticker[MAX_TICKER_LEN];
|
||||
uint8_t decimals;
|
||||
} underlying_asset_decimals_t;
|
||||
|
||||
/* Sadly, we don't have the information about the underlying asset's decimals, which can differ from
|
||||
the cToken decimals. Therefore, we hardcode a binding table. If Compound adds a lot of token in the
|
||||
future, we will have to move to a CAL based architecture instead, as this one doesn't scale well.*/
|
||||
#define NUM_COMPOUND_BINDINGS 9
|
||||
const underlying_asset_decimals_t UNDERLYING_ASSET_DECIMALS[NUM_COMPOUND_BINDINGS] = {
|
||||
{"cDAI", 18},
|
||||
{"CETH", 18},
|
||||
{"CUSDC", 6},
|
||||
{"CZRX", 18},
|
||||
{"CUSDT", 6},
|
||||
{"CBTC", 8},
|
||||
{"CBAT", 18},
|
||||
{"CREP", 18},
|
||||
{"cSAI", 18},
|
||||
};
|
||||
|
||||
bool get_underlying_asset_decimals(char *compound_ticker, uint8_t *out_decimals) {
|
||||
for (size_t i = 0; i < NUM_COMPOUND_BINDINGS; i++) {
|
||||
underlying_asset_decimals_t *binding =
|
||||
(underlying_asset_decimals_t *) PIC(&UNDERLYING_ASSET_DECIMALS[i]);
|
||||
if (strncmp(binding->c_ticker,
|
||||
compound_ticker,
|
||||
strnlen(binding->c_ticker, MAX_TICKER_LEN)) == 0) {
|
||||
*out_decimals = binding->decimals;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void compound_plugin_call(int message, void *parameters) {
|
||||
switch (message) {
|
||||
case ETH_PLUGIN_INIT_CONTRACT: {
|
||||
ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
|
||||
compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
|
||||
size_t i;
|
||||
for (i = 0; i < NUM_COMPOUND_SELECTORS; i++) {
|
||||
if (memcmp((uint8_t *) PIC(COMPOUND_SELECTORS[i]), msg->selector, SELECTOR_SIZE) ==
|
||||
0) {
|
||||
context->selectorIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// enforce that ETH amount should be 0, except in ceth.mint case
|
||||
if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) {
|
||||
if (context->selectorIndex != CETH_MINT) {
|
||||
PRINTF("Eth amount is not zero and token minted is not CETH!\n");
|
||||
msg->result = ETH_PLUGIN_RESULT_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == NUM_COMPOUND_SELECTORS) {
|
||||
PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector);
|
||||
msg->result = ETH_PLUGIN_RESULT_ERROR;
|
||||
break;
|
||||
}
|
||||
if (msg->dataSize != COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex]) {
|
||||
PRINTF("Unexpected data size for command %d expected %d got %d\n",
|
||||
context->selectorIndex,
|
||||
COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex],
|
||||
msg->dataSize);
|
||||
msg->result = ETH_PLUGIN_RESULT_ERROR;
|
||||
break;
|
||||
}
|
||||
if (context->selectorIndex == CETH_MINT) {
|
||||
// ETH amount 0x1234 is stored 0x12340000...000 instead of 0x00....001234, so we
|
||||
// strip the following zeroes when copying
|
||||
memset(context->amount, 0, sizeof(context->amount));
|
||||
memmove(context->amount + sizeof(context->amount) -
|
||||
msg->pluginSharedRO->txContent->value.length,
|
||||
msg->pluginSharedRO->txContent->value.value,
|
||||
msg->pluginSharedRO->txContent->value.length);
|
||||
}
|
||||
PRINTF("compound plugin inititialized\n");
|
||||
msg->result = ETH_PLUGIN_RESULT_OK;
|
||||
} break;
|
||||
|
||||
case ETH_PLUGIN_PROVIDE_PARAMETER: {
|
||||
ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
|
||||
compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
|
||||
PRINTF("compound plugin provide parameter %d %.*H\n",
|
||||
msg->parameterOffset,
|
||||
32,
|
||||
msg->parameter);
|
||||
if (context->selectorIndex != CETH_MINT) {
|
||||
switch (msg->parameterOffset) {
|
||||
case 4:
|
||||
memmove(context->amount, msg->parameter, 32);
|
||||
msg->result = ETH_PLUGIN_RESULT_OK;
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unhandled parameter offset\n");
|
||||
msg->result = ETH_PLUGIN_RESULT_ERROR;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
PRINTF("CETH contract expects no parameters\n");
|
||||
msg->result = ETH_PLUGIN_RESULT_ERROR;
|
||||
}
|
||||
} break;
|
||||
|
||||
case ETH_PLUGIN_FINALIZE: {
|
||||
ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
|
||||
PRINTF("compound plugin finalize\n");
|
||||
msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
|
||||
msg->numScreens = 2;
|
||||
msg->uiType = ETH_UI_TYPE_GENERIC;
|
||||
msg->result = ETH_PLUGIN_RESULT_OK;
|
||||
} break;
|
||||
|
||||
case ETH_PLUGIN_PROVIDE_INFO: {
|
||||
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
|
||||
compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
|
||||
PRINTF("compound plugin provide token: %d\n", (msg->item1 != NULL));
|
||||
if (msg->item1 != NULL) {
|
||||
strlcpy(context->ticker_1, msg->item1->token.ticker, MAX_TICKER_LEN);
|
||||
switch (context->selectorIndex) {
|
||||
case COMPOUND_REDEEM_UNDERLYING:
|
||||
case COMPOUND_MINT:
|
||||
case CETH_MINT:
|
||||
msg->result =
|
||||
get_underlying_asset_decimals(context->ticker_1, &context->decimals)
|
||||
? ETH_PLUGIN_RESULT_OK
|
||||
: ETH_PLUGIN_RESULT_FALLBACK;
|
||||
break;
|
||||
|
||||
// Only case where we use the compound contract decimals
|
||||
case COMPOUND_REDEEM:
|
||||
context->decimals = msg->item1->token.decimals;
|
||||
msg->result = ETH_PLUGIN_RESULT_OK;
|
||||
break;
|
||||
|
||||
default:
|
||||
msg->result = ETH_PLUGIN_RESULT_FALLBACK;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
msg->result = ETH_PLUGIN_RESULT_FALLBACK;
|
||||
}
|
||||
} break;
|
||||
|
||||
case ETH_PLUGIN_QUERY_CONTRACT_ID: {
|
||||
ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
|
||||
compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
|
||||
strlcpy(msg->name, "Type", msg->nameLength);
|
||||
switch (context->selectorIndex) {
|
||||
case COMPOUND_REDEEM_UNDERLYING:
|
||||
case COMPOUND_REDEEM:
|
||||
strlcpy(msg->version, "Redeem", msg->versionLength);
|
||||
break;
|
||||
|
||||
case COMPOUND_MINT:
|
||||
case CETH_MINT:
|
||||
strlcpy(msg->version, "Lend", msg->versionLength);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
strlcat(msg->version, " Assets", msg->versionLength);
|
||||
msg->result = ETH_PLUGIN_RESULT_OK;
|
||||
} break;
|
||||
|
||||
case ETH_PLUGIN_QUERY_CONTRACT_UI: {
|
||||
ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
|
||||
compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
|
||||
switch (msg->screenIndex) {
|
||||
case 0: {
|
||||
strlcpy(msg->title, "Amount", msg->titleLength);
|
||||
char *ticker_ptr = context->ticker_1;
|
||||
/* skip "c" in front of cToken unless we use "redeem", as
|
||||
redeem is the only operation dealing with a cToken amount */
|
||||
if (context->selectorIndex != COMPOUND_REDEEM) {
|
||||
ticker_ptr++;
|
||||
}
|
||||
amountToString(context->amount,
|
||||
sizeof(context->amount),
|
||||
context->decimals,
|
||||
ticker_ptr,
|
||||
msg->msg,
|
||||
100);
|
||||
msg->result = ETH_PLUGIN_RESULT_OK;
|
||||
} break;
|
||||
|
||||
case 1:
|
||||
strlcpy(msg->title, "Contract", msg->titleLength);
|
||||
strlcpy(msg->msg, "Compound ", msg->msgLength);
|
||||
strlcat(msg->msg,
|
||||
context->ticker_1 + 1,
|
||||
msg->msgLength); // remove the 'c' char at beginning of compound ticker
|
||||
msg->result = ETH_PLUGIN_RESULT_OK;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
PRINTF("Unhandled message %d\n", message);
|
||||
}
|
||||
}
|
||||
@@ -367,8 +367,8 @@ void starkware_get_source_address(char *destination) {
|
||||
cx_ecfp_private_key_t privateKey;
|
||||
cx_ecfp_public_key_t publicKey;
|
||||
os_perso_derive_node_bip32(CX_CURVE_256K1,
|
||||
tmpCtx.transactionContext.bip32Path,
|
||||
tmpCtx.transactionContext.pathLength,
|
||||
tmpCtx.transactionContext.bip32.path,
|
||||
tmpCtx.transactionContext.bip32.length,
|
||||
privateKeyData,
|
||||
NULL);
|
||||
cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey);
|
||||
|
||||
25
tests/speculos/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# generated by pip
|
||||
pip-wheel-metadata/
|
||||
|
||||
# pytest debug logs generated via --debug
|
||||
pytestdebug.log
|
||||
.cache
|
||||
.pytest_cache
|
||||
.mypy_cache
|
||||
.coverage
|
||||
.coverage.*
|
||||
coverage.xml
|
||||
50
tests/speculos/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Speculos functional tests
|
||||
|
||||
These tests are implemented in Python with the `SpeculosClient` interface which allows easy execution on the [Speculos](https://github.com/LedgerHQ/speculos) emulator.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [python >= 3.8](https://www.python.org/downloads/)
|
||||
- [pip](https://pip.pypa.io/en/stable/installation/)
|
||||
|
||||
### Dependencies
|
||||
Python dependencies are listed in [requirements.txt](requirements.txt)
|
||||
|
||||
```shell
|
||||
python3 -m pip install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt
|
||||
```
|
||||
> The extra index allows to fetch the latest version of Speculos.
|
||||
|
||||
## Usage
|
||||
|
||||
### Compilation app
|
||||
|
||||
Go to the root of the repository:
|
||||
```sh
|
||||
make DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOX_SDK
|
||||
mv bin/app.elf tests/speculos/<some name>.elf
|
||||
```
|
||||
|
||||
Given the requirements are installed, just do (by default command):
|
||||
|
||||
```
|
||||
cd tests/speculos/
|
||||
pytest
|
||||
```
|
||||
|
||||
### Custom options
|
||||
- **--model:** "nanos", "nanox", "nanosp" | default: "nanos"
|
||||
- **--display:** "qt", "headless" | default: "qt"
|
||||
- **--path:** the path of the binary app | default: path of makefile compilation
|
||||
|
||||
## Example
|
||||
|
||||
With `nanox` binary app:
|
||||
```sh
|
||||
# the --path is variable to where you put your binary
|
||||
|
||||
pytest --model nanox --path ./elfs/nanox.elf
|
||||
|
||||
# Execute specific test:
|
||||
pytest --model nanox --path ./elfs/nanox.elf test_pubkey_cmd.py
|
||||
```
|
||||
41
tests/speculos/conftest.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from speculos.client import SpeculosClient
|
||||
|
||||
from ethereum_client.ethereum_cmd import EthereumCommand
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).absolute().parent
|
||||
API_URL = "http://127.0.0.1:5000"
|
||||
|
||||
VERSION = {"nanos": "2.1", "nanox": "2.0.2", "nanosp": "1.0.3"}
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
# nanos, nanox, nanosp
|
||||
parser.addoption("--model", action="store", default="nanos")
|
||||
# qt: default, requires a X server
|
||||
# headless: nothing is displayed
|
||||
parser.addoption("--display", action="store", default="qt")
|
||||
|
||||
path: str = SCRIPT_DIR.parent.parent / "bin" / "app.elf"
|
||||
parser.addoption("--path", action="store", default=path)
|
||||
|
||||
@pytest.fixture()
|
||||
def client(pytestconfig):
|
||||
file_path = pytestconfig.getoption("path")
|
||||
model = pytestconfig.getoption("model")
|
||||
|
||||
args = ['--log-level', 'speculos:DEBUG','--model', model, '--display', pytestconfig.getoption("display"), '--sdk', VERSION[model]]
|
||||
with SpeculosClient(app=str(file_path), args=args) as client:
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def cmd(client, pytestconfig):
|
||||
yield EthereumCommand(
|
||||
client=client,
|
||||
debug=True,
|
||||
model=pytestconfig.getoption("model"),
|
||||
)
|
||||
133
tests/speculos/docs/README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Documentation of Ethereum's client test
|
||||
|
||||
```sh
|
||||
.
|
||||
├── conftest.py # Configuration for pytest
|
||||
├── ethereum_client # All utils of client test
|
||||
│ ├── ethereum_cmd_builder.py # Creation of apdu to send
|
||||
│ ├── ethereum_cmd.py # Send Apdu and parsing of response
|
||||
│ ├── exception
|
||||
│ │ ├── device_exception.py
|
||||
│ │ └── errors.py
|
||||
│ ├── plugin.py # Creation of content apdu which manage plugin, erc20Information, provide nft information
|
||||
│ ├── transaction.py # Creation of content apdu which manage personal tx, transaction, eip712
|
||||
│ └── utils.py
|
||||
├── requirements.txt
|
||||
├── screenshots # All screenshot of nanoS,X,SP for compare in tests
|
||||
├── setup.cfg
|
||||
|
||||
# ========= All Tests =========
|
||||
├── test_configuration_cmd.py
|
||||
├── test_eip1559.py
|
||||
├── test_eip191.py
|
||||
├── test_eip2930.py
|
||||
├── test_eip712.py
|
||||
├── test_erc1155.py
|
||||
├── test_erc20information.py
|
||||
├── test_erc721.py
|
||||
├── test_pubkey_cmd.py
|
||||
└── test_sign_cmd.py
|
||||
```
|
||||
|
||||
## Ethereum_client
|
||||
|
||||
### Ethereum_cmd_builder
|
||||
```py
|
||||
def chunked(size, source)
|
||||
|
||||
class EthereumCommandBuilder:
|
||||
# Creation of the apdu
|
||||
def get_configuration(self) -> bytes:
|
||||
def set_plugin(self, plugin: Plugin) -> bytes:
|
||||
def provide_nft_information(self, plugin: Plugin) -> bytes:
|
||||
def provide_erc20_token_information(self, info: ERC20Information):
|
||||
def get_public_key(self, bip32_path: str, display: bool = False) -> bytes:
|
||||
def perform_privacy_operation(self, bip32_path: str, display: bool, shared_secret: bool) -> bytes:
|
||||
def simple_sign_tx(self, bip32_path: str, transaction: Transaction) -> bytes:
|
||||
def sign_eip712(self, bip32_path: str, transaction: EIP712) -> bytes:
|
||||
def personal_sign_tx(self, bip32_path: str, transaction: PersonalTransaction) -> Tuple[bool,bytes]:
|
||||
```
|
||||
|
||||
### Ethereum_cmd
|
||||
```py
|
||||
class EthereumCommand:
|
||||
# Sending apdu and parsing the response in the right form
|
||||
def get_configuration(self) -> Tuple[int, int, int, int]:
|
||||
def set_plugin(self, plugin: Plugin):
|
||||
def provide_nft_information(self, plugin: Plugin):
|
||||
def provide_erc20_token_information(self, info: ERC20Information):
|
||||
def get_public_key(self, bip32_path: str, result: List, display: bool = False) -> Tuple[bytes, bytes, bytes]:
|
||||
def perform_privacy_operation(self, bip32_path: str, result: List, display: bool = False, shared_secret: bool = False) -> Tuple[bytes, bytes, bytes]:
|
||||
def simple_sign_tx(self, bip32_path: str, transaction: Transaction, result: List = list()) -> None:
|
||||
def sign_eip712(self, bip32_path: str, transaction: EIP712, result: List = list()) -> None:
|
||||
def personal_sign_tx(self, bip32_path: str, transaction: PersonalTransaction, result: List = list()) -> None:
|
||||
|
||||
|
||||
# Allows to send an apdu without return of speculos
|
||||
def send_apdu(self, apdu: bytes) -> bytes:
|
||||
# Allows to send an apdu with return of speculos
|
||||
def send_apdu_context(self, apdu: bytes, result: List = list()) -> bytes:
|
||||
|
||||
```
|
||||
|
||||
### Utils
|
||||
```py
|
||||
def save_screenshot(cmd, path: str):
|
||||
def compare_screenshot(cmd, path: str):
|
||||
def parse_sign_response(response : bytes) -> Tuple[bytes, bytes, bytes]:
|
||||
def bip32_path_from_string(path: str) -> List[bytes]:
|
||||
def packed_bip32_path_from_string(path: str) -> bytes:
|
||||
def write_varint(n: int) -> bytes:
|
||||
def read_varint(buf: BytesIO, prefix: Optional[bytes] = None) -> int:
|
||||
def read(buf: BytesIO, size: int) -> bytes:
|
||||
def read_uint(buf: BytesIO,
|
||||
```
|
||||
|
||||
## Tests new apdu
|
||||
|
||||
If a new instruction is programmed it will be necessary to create 2 new functions.
|
||||
one in `ethereum_cmd_builder` :
|
||||
- Creation of the raw apdu you can find some examples in this same file
|
||||
|
||||
and one in `ethereum_cmd`:
|
||||
- Send the apdu to speculos and parse the answer in a `list` named result you can find some examples in this same file
|
||||
|
||||
## Example for write new tests
|
||||
|
||||
To send several apdu and get the return
|
||||
|
||||
```py
|
||||
FIRST = bytes.fromhex("{YourAPDU}")
|
||||
SECOND = bytes.fromhex("{YourAPDU}")
|
||||
|
||||
def test_multiple_raw_apdu(cmd):
|
||||
result: list = []
|
||||
|
||||
cmd.send_apdu(FIRST)
|
||||
with cmd.send_apdu_context(SECOND, result) as ex:
|
||||
sleep(0.5)
|
||||
# Here your code for press button and compare screen if you want
|
||||
|
||||
response: bytes = result[0] # response returning
|
||||
# Here you function to parse response of some code
|
||||
v, r, s = parse_sign_response(response)
|
||||
|
||||
# And here assertion of your tests
|
||||
assert v == 0x25 # 37
|
||||
assert r.hex() == "68ba082523584adbfc31d36d68b51d6f209ce0838215026bf1802a8f17dcdff4"
|
||||
assert s.hex() == "7c92908fa05c8bc86507a3d6a1c8b3c2722ee01c836d89a61df60c1ab0b43fff"
|
||||
```
|
||||
|
||||
To test an error
|
||||
|
||||
```py
|
||||
def test_some_error(cmd):
|
||||
result: list = []
|
||||
|
||||
with pytest.raises(ethereum_client.exception.errors.UnknownDeviceError) as error:
|
||||
# With an function in ethereum_cmd
|
||||
with cmd.send_apdu_context(bytes.fromhex("{YourAPDU}"), result) as ex:
|
||||
pass
|
||||
assert error.args[0] == '0x6a80'
|
||||
```
|
||||
|
||||
0
tests/speculos/ethereum_client/__init__.py
Normal file
226
tests/speculos/ethereum_client/ethereum_cmd.py
Normal file
@@ -0,0 +1,226 @@
|
||||
from ast import List
|
||||
from contextlib import contextmanager
|
||||
import struct
|
||||
from time import sleep
|
||||
from typing import Tuple
|
||||
|
||||
from speculos.client import SpeculosClient, ApduException
|
||||
|
||||
from ethereum_client.ethereum_cmd_builder import EthereumCommandBuilder, InsType
|
||||
from ethereum_client.exception import DeviceException
|
||||
from ethereum_client.transaction import EIP712, PersonalTransaction, Transaction
|
||||
from ethereum_client.plugin import ERC20Information, Plugin
|
||||
from ethereum_client.utils import parse_sign_response
|
||||
|
||||
|
||||
class EthereumCommand:
|
||||
def __init__(self,
|
||||
client: SpeculosClient,
|
||||
debug: bool = False,
|
||||
model: str = "nanos") -> None:
|
||||
self.client = client
|
||||
self.builder = EthereumCommandBuilder(debug=debug)
|
||||
self.debug = debug
|
||||
self.model = model
|
||||
|
||||
def get_configuration(self) -> Tuple[int, int, int, int]:
|
||||
try:
|
||||
response = self.client._apdu_exchange(
|
||||
self.builder.get_configuration()
|
||||
) # type: int, bytes
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_GET_VERSION)
|
||||
|
||||
# response = FLAG (1) || MAJOR (1) || MINOR (1) || PATCH (1)
|
||||
assert len(response) == 4
|
||||
|
||||
info, major, minor, patch = struct.unpack(
|
||||
"BBBB",
|
||||
response
|
||||
) # type: int, int, int
|
||||
|
||||
return info, major, minor, patch
|
||||
|
||||
def set_plugin(self, plugin: Plugin):
|
||||
try:
|
||||
self.client._apdu_exchange(
|
||||
self.builder.set_plugin(plugin=plugin)
|
||||
)
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_SET_PLUGIN)
|
||||
|
||||
def provide_nft_information(self, plugin: Plugin):
|
||||
try:
|
||||
self.client._apdu_exchange(
|
||||
self.builder.provide_nft_information(plugin=plugin)
|
||||
)
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_PROVIDE_NFT_INFORMATION)
|
||||
|
||||
def provide_erc20_token_information(self, info: ERC20Information):
|
||||
try:
|
||||
self.client._apdu_exchange(
|
||||
self.builder.provide_erc20_token_information(info=info)
|
||||
)
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_PROVIDE_ERC20)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_public_key(self, bip32_path: str, result: List, display: bool = False) -> Tuple[bytes, bytes, bytes]:
|
||||
try:
|
||||
chunk: bytes = self.builder.get_public_key(bip32_path=bip32_path, display=display)
|
||||
|
||||
with self.client.apdu_exchange_nowait(cla=chunk[0], ins=chunk[1],
|
||||
p1=chunk[2], p2=chunk[3],
|
||||
data=chunk[5:]) as exchange:
|
||||
yield exchange
|
||||
response: bytes = exchange.receive()
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_GET_PUBLIC_KEY)
|
||||
|
||||
# response = pub_key_len (1) ||
|
||||
# pub_key (var) ||
|
||||
# chain_code_len (1) ||
|
||||
# chain_code (var)
|
||||
offset: int = 0
|
||||
|
||||
pub_key_len: int = response[offset]
|
||||
offset += 1
|
||||
|
||||
uncompressed_addr_len: bytes = response[offset:offset + pub_key_len]
|
||||
offset += pub_key_len
|
||||
|
||||
eth_addr_len: int = response[offset]
|
||||
offset += 1
|
||||
|
||||
eth_addr: bytes = response[offset:offset + eth_addr_len]
|
||||
offset += eth_addr_len
|
||||
|
||||
chain_code: bytes = response[offset:]
|
||||
|
||||
assert len(response) == 1 + pub_key_len + 1 + eth_addr_len + 32 # 32 -> chain_code_len
|
||||
|
||||
result.append(uncompressed_addr_len)
|
||||
result.append(eth_addr)
|
||||
result.append(chain_code)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def perform_privacy_operation(self, bip32_path: str, result: List, display: bool = False, shared_secret: bool = False) -> Tuple[bytes, bytes, bytes]:
|
||||
try:
|
||||
chunk: bytes = self.builder.perform_privacy_operation(bip32_path=bip32_path, display=display, shared_secret=shared_secret)
|
||||
|
||||
with self.client.apdu_exchange_nowait(cla=chunk[0], ins=chunk[1],
|
||||
p1=chunk[2], p2=chunk[3],
|
||||
data=chunk[5:]) as exchange:
|
||||
yield exchange
|
||||
response: bytes = exchange.receive()
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_PERFORM_PRIVACY_OPERATION)
|
||||
|
||||
# response = Public encryption key or shared secret (32)
|
||||
assert len(response) == 32
|
||||
|
||||
result.append(response)
|
||||
|
||||
def send_apdu(self, apdu: bytes) -> bytes:
|
||||
try:
|
||||
self.client.apdu_exchange(cla=apdu[0], ins=apdu[1],
|
||||
p1=apdu[2], p2=apdu[3],
|
||||
data=apdu[5:])
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_SIGN_TX)
|
||||
|
||||
@contextmanager
|
||||
def send_apdu_context(self, apdu: bytes, result: List = list()) -> bytes:
|
||||
try:
|
||||
|
||||
with self.client.apdu_exchange_nowait(cla=apdu[0], ins=apdu[1],
|
||||
p1=apdu[2], p2=apdu[3],
|
||||
data=apdu[5:]) as exchange:
|
||||
yield exchange
|
||||
result.append(exchange.receive())
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_SIGN_TX)
|
||||
|
||||
|
||||
|
||||
@contextmanager
|
||||
def simple_sign_tx(self, bip32_path: str, transaction: Transaction, result: List = list()) -> None:
|
||||
try:
|
||||
chunk: bytes = self.builder.simple_sign_tx(bip32_path=bip32_path, transaction=transaction)
|
||||
|
||||
with self.client.apdu_exchange_nowait(cla=chunk[0], ins=chunk[1],
|
||||
p1=chunk[2], p2=chunk[3],
|
||||
data=chunk[5:]) as exchange:
|
||||
yield exchange
|
||||
response: bytes = exchange.receive()
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_SIGN_TX)
|
||||
|
||||
# response = V (1) || R (32) || S (32)
|
||||
assert len(response) == 65
|
||||
v, r, s = parse_sign_response(response)
|
||||
|
||||
result.append(v)
|
||||
result.append(r)
|
||||
result.append(s)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def sign_eip712(self, bip32_path: str, transaction: EIP712, result: List = list()) -> None:
|
||||
try:
|
||||
chunk: bytes = self.builder.sign_eip712(bip32_path=bip32_path, transaction=transaction)
|
||||
|
||||
with self.client.apdu_exchange_nowait(cla=chunk[0], ins=chunk[1],
|
||||
p1=chunk[2], p2=chunk[3],
|
||||
data=chunk[5:]) as exchange:
|
||||
yield exchange
|
||||
response: bytes = exchange.receive()
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_SIGN_EIP712)
|
||||
|
||||
# response = V (1) || R (32) || S (32)
|
||||
assert len(response) == 65
|
||||
v, r, s = parse_sign_response(response)
|
||||
|
||||
result.append(v)
|
||||
result.append(r)
|
||||
result.append(s)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def personal_sign_tx(self, bip32_path: str, transaction: PersonalTransaction, result: List = list()) -> None:
|
||||
try:
|
||||
for islast_apdu, apdu in self.builder.personal_sign_tx(bip32_path=bip32_path, transaction=transaction):
|
||||
if islast_apdu:
|
||||
with self.client.apdu_exchange_nowait(cla=apdu[0], ins=apdu[1],
|
||||
p1=apdu[2], p2=apdu[3],
|
||||
data=apdu[5:]) as exchange:
|
||||
# the "yield" here allows to wait for a button interaction (click right, left, both)
|
||||
yield exchange
|
||||
response: bytes = exchange.receive()
|
||||
else:
|
||||
self.send_apdu(apdu)
|
||||
|
||||
except ApduException as error:
|
||||
raise DeviceException(error_code=error.sw, ins=InsType.INS_SIGN_TX)
|
||||
|
||||
# response = V (1) || R (32) || S (32)
|
||||
v, r, s = parse_sign_response(response)
|
||||
|
||||
result.append(v)
|
||||
result.append(r)
|
||||
result.append(s)
|
||||
|
||||
|
||||
|
||||
293
tests/speculos/ethereum_client/ethereum_cmd_builder.py
Normal file
@@ -0,0 +1,293 @@
|
||||
import enum
|
||||
import logging
|
||||
import struct
|
||||
from typing import List, Tuple, Union, Iterator, cast
|
||||
|
||||
from ethereum_client.transaction import EIP712, PersonalTransaction, Transaction
|
||||
from ethereum_client.plugin import ERC20Information, Plugin
|
||||
from ethereum_client.utils import packed_bip32_path_from_string
|
||||
|
||||
MAX_APDU_LEN: int = 255
|
||||
|
||||
def chunked(size, source):
|
||||
for i in range(0, len(source), size):
|
||||
yield source[i:i+size]
|
||||
|
||||
def chunkify(data: bytes, chunk_len: int) -> Iterator[Tuple[bool, bytes]]:
|
||||
size: int = len(data)
|
||||
|
||||
if size <= chunk_len:
|
||||
yield True, data
|
||||
return
|
||||
|
||||
chunk: int = size // chunk_len
|
||||
remaining: int = size % chunk_len
|
||||
offset: int = 0
|
||||
|
||||
for i in range(chunk):
|
||||
yield False, data[offset:offset + chunk_len]
|
||||
offset += chunk_len
|
||||
|
||||
if remaining:
|
||||
yield True, data[offset:]
|
||||
|
||||
class InsType(enum.IntEnum):
|
||||
INS_GET_PUBLIC_KEY = 0x02
|
||||
INS_SIGN_TX = 0x04
|
||||
INS_GET_CONFIGURATION = 0x06
|
||||
INS_SIGN_PERSONAL_TX = 0x08
|
||||
INS_PROVIDE_ERC20 = 0x0A
|
||||
INS_SIGN_EIP712 = 0x0c
|
||||
INS_ETH2_GET_PUBLIC_KEY = 0x0E
|
||||
INS_SET_ETH2_WITHDRAWAL = 0x10
|
||||
INS_SET_EXTERNAL_PLUGIN = 0x12
|
||||
INS_PROVIDE_NFT_INFORMATION = 0x14
|
||||
INS_SET_PLUGIN = 0x16
|
||||
INS_PERFORM_PRIVACY_OPERATION = 0x18
|
||||
|
||||
|
||||
class EthereumCommandBuilder:
|
||||
"""APDU command builder for the Boilerplate application.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
debug: bool
|
||||
Whether you want to see logging or not.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
debug: bool
|
||||
Whether you want to see logging or not.
|
||||
|
||||
"""
|
||||
CLA: int = 0xE0
|
||||
|
||||
def __init__(self, debug: bool = False):
|
||||
"""Init constructor."""
|
||||
self.debug = debug
|
||||
|
||||
def serialize(self,
|
||||
cla: int,
|
||||
ins: Union[int, enum.IntEnum],
|
||||
p1: int = 0,
|
||||
p2: int = 0,
|
||||
cdata: bytes = b"") -> bytes:
|
||||
"""Serialize the whole APDU command (header + data).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cla : int
|
||||
Instruction class: CLA (1 byte)
|
||||
ins : Union[int, IntEnum]
|
||||
Instruction code: INS (1 byte)
|
||||
p1 : int
|
||||
Instruction parameter 1: P1 (1 byte).
|
||||
p2 : int
|
||||
Instruction parameter 2: P2 (1 byte).
|
||||
cdata : bytes
|
||||
Bytes of command data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
Bytes of a complete APDU command.
|
||||
|
||||
"""
|
||||
ins = cast(int, ins.value) if isinstance(ins, enum.IntEnum) else cast(int, ins)
|
||||
|
||||
header: bytes = struct.pack("BBBBB",
|
||||
cla,
|
||||
ins,
|
||||
p1,
|
||||
p2,
|
||||
len(cdata)) # add Lc to APDU header
|
||||
|
||||
if self.debug:
|
||||
logging.info("header: %s", header.hex())
|
||||
logging.info("cdata: %s", cdata.hex())
|
||||
|
||||
return header + cdata
|
||||
|
||||
def get_configuration(self) -> bytes:
|
||||
"""Command builder for GET_CONFIGURATON
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
APDU command for GET_CONFIGURATON
|
||||
|
||||
"""
|
||||
return self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_GET_CONFIGURATION,
|
||||
p1=0x00,
|
||||
p2=0x00,
|
||||
cdata=b"")
|
||||
|
||||
def _same_header_builder(self, data: Union[Plugin, ERC20Information], ins: int) -> bytes:
|
||||
return self.serialize(cla=self.CLA,
|
||||
ins=ins,
|
||||
p1=0x00,
|
||||
p2=0x00,
|
||||
cdata=data.serialize())
|
||||
|
||||
def set_plugin(self, plugin: Plugin) -> bytes:
|
||||
return self._same_header_builder(plugin, InsType.INS_SET_PLUGIN)
|
||||
|
||||
def provide_nft_information(self, plugin: Plugin) -> bytes:
|
||||
return self._same_header_builder(plugin, InsType.INS_PROVIDE_NFT_INFORMATION)
|
||||
|
||||
def provide_erc20_token_information(self, info: ERC20Information):
|
||||
return self._same_header_builder(info, InsType.INS_PROVIDE_ERC20)
|
||||
|
||||
def get_public_key(self, bip32_path: str, display: bool = False) -> bytes:
|
||||
"""Command builder for GET_PUBLIC_KEY.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bip32_path: str
|
||||
String representation of BIP32 path.
|
||||
display : bool
|
||||
Whether you want to display the address on the device.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
APDU command for GET_PUBLIC_KEY.
|
||||
|
||||
"""
|
||||
cdata = packed_bip32_path_from_string(bip32_path)
|
||||
|
||||
return self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_GET_PUBLIC_KEY,
|
||||
p1=0x01 if display else 0x00,
|
||||
p2=0x01,
|
||||
cdata=cdata)
|
||||
|
||||
def perform_privacy_operation(self, bip32_path: str, display: bool, shared_secret: bool) -> bytes:
|
||||
"""Command builder for INS_PERFORM_PRIVACY_OPERATION.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bip32_path : str
|
||||
String representation of BIP32 path.
|
||||
Third party public key on Curve25519 : 32 bytes
|
||||
Optionnal if returning the shared secret
|
||||
|
||||
"""
|
||||
cdata = packed_bip32_path_from_string(bip32_path)
|
||||
|
||||
return self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_PERFORM_PRIVACY_OPERATION,
|
||||
p1=0x01 if display else 0x00,
|
||||
p2=0x01 if shared_secret else 0x00,
|
||||
cdata=cdata)
|
||||
|
||||
|
||||
def simple_sign_tx(self, bip32_path: str, transaction: Transaction) -> bytes:
|
||||
"""Command builder for INS_SIGN_TX.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bip32_path : str
|
||||
String representation of BIP32 path.
|
||||
transaction : Transaction
|
||||
Representation of the transaction to be signed.
|
||||
|
||||
Yields
|
||||
-------
|
||||
bytes
|
||||
APDU command chunk for INS_SIGN_TX.
|
||||
|
||||
"""
|
||||
cdata = packed_bip32_path_from_string(bip32_path)
|
||||
|
||||
tx: bytes = transaction.serialize()
|
||||
|
||||
cdata = cdata + tx
|
||||
|
||||
return self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_SIGN_TX,
|
||||
p1=0x00,
|
||||
p2=0x00,
|
||||
cdata=cdata)
|
||||
|
||||
def sign_eip712(self, bip32_path: str, transaction: EIP712) -> bytes:
|
||||
"""Command builder for INS_SIGN_EIP712.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bip32_path : str
|
||||
String representation of BIP32 path.
|
||||
transaction : EIP712
|
||||
Domain hash -> 32 bytes
|
||||
Message hash -> 32 bytes
|
||||
|
||||
Yields
|
||||
-------
|
||||
bytes
|
||||
APDU command chunk for INS_SIGN_EIP712.
|
||||
|
||||
"""
|
||||
cdata = packed_bip32_path_from_string(bip32_path)
|
||||
|
||||
|
||||
tx: bytes = transaction.serialize()
|
||||
|
||||
cdata = cdata + tx
|
||||
|
||||
return self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_SIGN_EIP712,
|
||||
p1=0x00,
|
||||
p2=0x00,
|
||||
cdata=cdata)
|
||||
|
||||
def personal_sign_tx(self, bip32_path: str, transaction: PersonalTransaction) -> Tuple[bool,bytes]:
|
||||
"""Command builder for INS_SIGN_PERSONAL_TX.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bip32_path : str
|
||||
String representation of BIP32 path.
|
||||
transaction : Transaction
|
||||
Representation of the transaction to be signed.
|
||||
|
||||
Yields
|
||||
-------
|
||||
bytes
|
||||
APDU command chunk for INS_SIGN_PERSONAL_TX.
|
||||
|
||||
"""
|
||||
|
||||
cdata = packed_bip32_path_from_string(bip32_path)
|
||||
|
||||
tx: bytes = transaction.serialize()
|
||||
|
||||
cdata = cdata + tx
|
||||
last_chunk = len(cdata) // MAX_APDU_LEN
|
||||
|
||||
# The generator allows to send apdu frames because we can't send an apdu > 255
|
||||
for i, (chunk) in enumerate(chunked(MAX_APDU_LEN, cdata)):
|
||||
if i == 0 and i == last_chunk:
|
||||
yield True, self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_SIGN_PERSONAL_TX,
|
||||
p1=0x00,
|
||||
p2=0x00,
|
||||
cdata=chunk)
|
||||
elif i == 0:
|
||||
yield False, self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_SIGN_PERSONAL_TX,
|
||||
p1=0x00,
|
||||
p2=0x00,
|
||||
cdata=chunk)
|
||||
elif i == last_chunk:
|
||||
yield True, self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_SIGN_PERSONAL_TX,
|
||||
p1=0x80,
|
||||
p2=0x00,
|
||||
cdata=chunk)
|
||||
else:
|
||||
yield False, self.serialize(cla=self.CLA,
|
||||
ins=InsType.INS_SIGN_PERSONAL_TX,
|
||||
p1=0x80,
|
||||
p2=0x00,
|
||||
cdata=chunk)
|
||||
35
tests/speculos/ethereum_client/exception/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from .device_exception import DeviceException
|
||||
from .errors import (UnknownDeviceError,
|
||||
DenyError,
|
||||
WrongP1P2Error,
|
||||
WrongDataLengthError,
|
||||
InsNotSupportedError,
|
||||
ClaNotSupportedError,
|
||||
WrongResponseLengthError,
|
||||
DisplayBip32PathFailError,
|
||||
DisplayAddressFailError,
|
||||
DisplayAmountFailError,
|
||||
WrongTxLengthError,
|
||||
TxParsingFailError,
|
||||
TxHashFail,
|
||||
BadStateError,
|
||||
SignatureFailError)
|
||||
|
||||
__all__ = [
|
||||
"DeviceException",
|
||||
"DenyError",
|
||||
"UnknownDeviceError",
|
||||
"WrongP1P2Error",
|
||||
"WrongDataLengthError",
|
||||
"InsNotSupportedError",
|
||||
"ClaNotSupportedError",
|
||||
"WrongResponseLengthError",
|
||||
"DisplayBip32PathFailError",
|
||||
"DisplayAddressFailError",
|
||||
"DisplayAmountFailError",
|
||||
"WrongTxLengthError",
|
||||
"TxParsingFailError",
|
||||
"TxHashFail",
|
||||
"BadStateError",
|
||||
"SignatureFailError"
|
||||
]
|
||||
38
tests/speculos/ethereum_client/exception/device_exception.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import enum
|
||||
from typing import Dict, Any, Union
|
||||
|
||||
from .errors import *
|
||||
|
||||
|
||||
class DeviceException(Exception): # pylint: disable=too-few-public-methods
|
||||
exc: Dict[int, Any] = {
|
||||
0x6985: DenyError,
|
||||
0x6A86: WrongP1P2Error,
|
||||
0x6A87: WrongDataLengthError,
|
||||
0x6D00: InsNotSupportedError,
|
||||
0x6E00: ClaNotSupportedError,
|
||||
0xB000: WrongResponseLengthError,
|
||||
0xB001: DisplayBip32PathFailError,
|
||||
0xB002: DisplayAddressFailError,
|
||||
0xB003: DisplayAmountFailError,
|
||||
0xB004: WrongTxLengthError,
|
||||
0xB005: TxParsingFailError,
|
||||
0xB006: TxHashFail,
|
||||
0xB007: BadStateError,
|
||||
0xB008: SignatureFailError
|
||||
}
|
||||
|
||||
def __new__(cls,
|
||||
error_code: int,
|
||||
ins: Union[int, enum.IntEnum, None] = None,
|
||||
message: str = ""
|
||||
) -> Any:
|
||||
error_message: str = (f"Error in {ins!r} command"
|
||||
if ins else "Error in command")
|
||||
|
||||
if error_code in DeviceException.exc:
|
||||
return DeviceException.exc[error_code](hex(error_code),
|
||||
error_message,
|
||||
message)
|
||||
|
||||
return UnknownDeviceError(hex(error_code), error_message, message)
|
||||