Merge pull request #351 from LedgerHQ/develop

App release 1.9.20
This commit is contained in:
apaillier-ledger
2022-10-11 10:24:40 +02:00
committed by GitHub
779 changed files with 4257 additions and 904 deletions

56
.github/workflows/build-workflow.yml vendored Normal file
View 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

View File

@@ -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

View File

@@ -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
View File

@@ -18,3 +18,4 @@ tests/elfs/*
tests/snapshots-tmp
.vscode
.idea

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

BIN
icons/nanos_app_meter.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

BIN
icons/nanos_app_tecracoin.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/nanos_app_tecratestnet.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 B

BIN
icons/nanox_app_meter.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 B

BIN
icons/nanox_app_tecracoin.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/nanox_app_tecratestnet.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 B

View 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"

View 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"

View 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"

View 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"

View 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"

View 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"

View File

@@ -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);

View File

@@ -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
View 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_

View File

@@ -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:

View File

@@ -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);

View File

@@ -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},

View File

@@ -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

View File

@@ -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"

View File

@@ -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);
}

View File

@@ -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_

View File

@@ -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,

View File

@@ -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

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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;

View File

@@ -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
View 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

View File

@@ -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);

View File

@@ -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[];

View 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;
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -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),

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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");

View File

@@ -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();
}
}

View 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_

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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++) {

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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
View 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
View 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
```

View 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"),
)

View 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'
```

View 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)

View 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)

View 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"
]

View 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)

Some files were not shown because too many files have changed in this diff Show More