Merge pull request #424 from LedgerHQ/release/1.10.2

App release 1.10.2
This commit is contained in:
apaillier-ledger
2023-05-02 16:57:18 +02:00
committed by GitHub
117 changed files with 1891 additions and 594 deletions

View File

@@ -114,6 +114,6 @@ runs:
uses: ad-m/github-push-action@master
with:
github_token: ${{ inputs.secret }}
branch: ${{ steps.commit.outputs.src_branch }}:${{ steps.commit.outputs.dst_branch }}
branch: ${{ steps.commit.outputs.dst_branch }}
directory: ${{ inputs.directory }}
repository: ${{ inputs.repository }}

View File

@@ -11,4 +11,4 @@ jobs:
assign-author:
runs-on: ubuntu-latest
steps:
- uses: toshimaru/auto-author-assign@v1.4.0
- uses: toshimaru/auto-author-assign@v1.6.1

View File

@@ -22,7 +22,7 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Build Ethereum
run: |

View File

@@ -18,14 +18,14 @@ jobs:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build with Clang Static Analyzer
run: |
make clean
scan-build --use-cc=clang -analyze-headers -enable-checker security -enable-checker unix -enable-checker valist -o scan-build --status-bugs make default
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
if: failure()
with:
name: scan-build
@@ -42,7 +42,7 @@ jobs:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build testing binaries
run: |
@@ -50,7 +50,7 @@ jobs:
cd tests/zemu/ && ./build_local_test_elfs.sh
- name: Upload app binaries
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: e2e_zemu_elfs
path: ./tests/zemu/elfs/
@@ -68,14 +68,14 @@ jobs:
echo $DISPLAY
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- run: sudo apt-get update -y && sudo apt-get install -y libusb-1.0.0 libudev-dev
- name: Install node
uses: actions/setup-node@v2
- name: Install NodeJS
uses: actions/setup-node@v3
with:
node-version: "14.4.0"
node-version: "16"
- name: Install yarn
run: npm install -g yarn
@@ -87,7 +87,7 @@ jobs:
run: mkdir tests/zemu/elfs
- name: Download app binaries
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
path: tmp/
@@ -109,7 +109,7 @@ jobs:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build testing binaries
run: |
@@ -119,7 +119,7 @@ jobs:
make clean && make -j 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
uses: actions/upload-artifact@v3
with:
name: e2e_speculos_elfs
path: ./tests/speculos/elfs
@@ -136,13 +136,13 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Create tmp folder for artifacts
run: mkdir tests/speculos/elfs
- name: Download app binaries
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
path: tmp/
@@ -166,113 +166,18 @@ jobs:
# =====================================================
build_ragger_elfs:
name: Building binaries for Ragger tests
runs-on: ubuntu-latest
container:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- name: Clone
uses: actions/checkout@v2
- name: Build test binaries
run: |
make -j BOLOS_SDK=$NANOS_SDK CAL_TESTING_KEY=1
mv bin/app.elf app-nanos.elf
make clean
make -j BOLOS_SDK=$NANOX_SDK CAL_TESTING_KEY=1
mv bin/app.elf app-nanox.elf
make clean
make -j BOLOS_SDK=$NANOSP_SDK CAL_TESTING_KEY=1
mv bin/app.elf app-nanosp.elf
- name: Upload app binaries
uses: actions/upload-artifact@v2
with:
name: ragger_elfs
path: ./app-*.elf
create_ragger_env:
name: Cache Ragger environment
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v2
- name: APT update
run: |
sudo apt update
- name: Create virtual env with dependencies
run: |
cd tests/ragger
python3 -m venv ./venv
. ./venv/bin/activate
pip3 install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt
# Used for the cache key
echo "py_deps=$(pip freeze | md5sum | cut -d' ' -f1)" >> $GITHUB_ENV
- name: Download QEMU
run: |
sudo apt install --download-only -y qemu-user-static
mkdir -p tests/ragger/packages
cp /var/cache/apt/archives/*.deb tests/ragger/packages/
# Used for the cache key
echo "deb_deps=$(find /var/cache/apt/archives/ -maxdepth 0 -type f -name '*.deb' | md5sum | cut -d' ' -f 1)" >> $GITHUB_ENV
- name: Set up cache
uses: actions/cache@v3
with:
key: ${{ runner.os }}-raggenv-${{ env.py_deps }}-${{ env.deb_deps }}
path: |
tests/ragger/venv/
tests/ragger/packages/
outputs:
py_deps: ${{ env.py_deps }}
deb_deps: ${{ env.deb_deps }}
name: Build app for Ragger tests
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
with:
upload_app_binaries_artifact: "ragger_elfs"
flags: "DEBUG=1 CAL_CI_KEY=1 DOMAIN_NAME_TEST_KEY=1"
run_for_devices: '["nanos", "nanox", "nanosp"]'
jobs-ragger-tests:
name: Ragger tests
strategy:
matrix:
model: ["nanos", "nanox", "nanosp"]
needs: [build_ragger_elfs, create_ragger_env]
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v2
- name: Download previously built artifacts
uses: actions/download-artifact@v2
with:
name: ragger_elfs
path: tmp/
- name: Put them where they belong
run: |
mkdir -p tests/ragger/elfs
find tmp/ -type f -name '*.elf' -exec cp {} tests/ragger/elfs/ \;
- name: Get cached environment
uses: actions/cache@v3
with:
key: ${{ runner.os }}-raggenv-${{ needs.create_ragger_env.outputs.py_deps }}-${{ needs.create_ragger_env.outputs.deb_deps }}
path: |
tests/ragger/venv/
tests/ragger/packages/
- name: Install QEMU
run: |
sudo mv tests/ragger/packages/*.deb /var/cache/apt/archives/
sudo apt install -y qemu-user-static
- name: Run tests
env:
CAL_SIGNATURE_TEST_KEY: ${{ secrets.CAL_SIGNATURE_TEST_KEY }}
run: |
cd tests/ragger
. ./venv/bin/activate
pytest --path ./elfs --model ${{ matrix.model }} -s -v
name: Run Ragger tests
needs: build_ragger_elfs
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1
with:
download_app_binaries_artifact: "ragger_elfs"
test_dir: tests/ragger
run_for_devices: '["nanos", "nanox", "nanosp"]'

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Lint
uses: DoozyX/clang-format-lint-action@v0.14

View File

@@ -14,7 +14,7 @@ jobs:
steps:
- name: Clone
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# by default the action uses fetch-depth = 1, which creates
# shallow repositories from which we can't push
@@ -42,11 +42,10 @@ jobs:
secret: ${{ secrets.CI_BOT_TOKEN }}
repository: LedgerHQ/ethereum-plugin-sdk
- name: Update the SDK submodule in the Ethereum app
uses: ./.github/actions/commit-changes
- name: Create SDK update pull request
uses: peter-evans/create-pull-request@v4
with:
name: 'ldg-github-ci'
files: ethereum-plugin-sdk
message: "[update][SDK] Branch ${{ steps.extract_branch.outputs.branch }} | Commit ${GITHUB_SHA}"
secret: ${{ secrets.CI_BOT_TOKEN }}
repository: LedgerHQ/app-ethereum
branch: sdk/update-submodule
delete-branch: true
title: Update the SDK submodule
reviewers: apailler-ledger

View File

@@ -5,6 +5,39 @@ 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.10.2](https://github.com/ledgerhq/app-ethereum/compare/1.10.1...1.10.2) - 2023-04-24
### Added
- (clone) ID4Good
- (network) Cronos
- (network) Scroll
- (network) KCC
- (network) Rootstock
- (network) Evmos
- (network) Metis Andromeda
- (network) Kava EVM
- (network) Klaytn Cypress
- (network) Syscoin
- (network) Velas EVM
- (network) Boba Network
- (network) Energi
- Domain names support (LNX / LNS+)
### Changed
- Starknet blind signing wording
### Fixed
- Missing 44'/60' derivation path for XDC Network
- Small visual glitch with EIP-712 verbose mode with the "Review struct" page
- Possible overflow with very large transactions
- EnergyWebChain ticker
- Arbitrum ticker
- Error handling on EIP-191 APDUs
- Swap transactions handling
## [1.10.1](https://github.com/ledgerhq/app-ethereum/compare/1.10.0...1.10.1) - 2022-11-09
### Fixed

View File

@@ -34,7 +34,7 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'"
APPVERSION_M=1
APPVERSION_N=10
APPVERSION_P=1
APPVERSION_P=2
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
APP_LOAD_FLAGS= --appFlags 0xa40 --dep Ethereum:$(APPVERSION)
@@ -147,10 +147,23 @@ ifneq ($(TARGET_NAME),TARGET_NANOS)
DEFINES += HAVE_EIP712_FULL_SUPPORT
endif
# CryptoAssetsList testing key
CAL_TESTING_KEY:=0
ifneq ($(CAL_TESTING_KEY),0)
DEFINES += HAVE_CAL_TESTING_KEY
# CryptoAssetsList key
CAL_TEST_KEY:=0
CAL_CI_KEY:=0
ifneq ($(CAL_TEST_KEY),0)
DEFINES += HAVE_CAL_TEST_KEY
endif
ifneq ($(CAL_CI_KEY),0)
DEFINES += HAVE_CAL_CI_KEY
endif
# ENS
ifneq ($(TARGET_NAME),TARGET_NANOS)
DEFINES += HAVE_DOMAIN_NAME
DOMAIN_NAME_TEST_KEY:=0
ifneq ($(DOMAIN_NAME_TEST_KEY),0)
DEFINES += HAVE_DOMAIN_NAME_TEST_KEY
endif
endif
# Enabling debug PRINTF

View File

@@ -38,6 +38,9 @@ Application version 1.9.19 - 2022-05-17
- Add EIP712 STRUCT DEFINITION & EIP712 STRUCT IMPLEMENTATION
- Update to SIGN ETH EIP712
### 1.10.2
- Add domain names support
## About
This application describes the APDU messages interface to communicate with the Ethereum application.
@@ -881,6 +884,82 @@ _Output data_
None
### GET CHALLENGE
#### Description
Sends a random 32-bit long value. Can prevent replay of signed payloads when the challenge
is included in said payload.
#### Coding
_Command_
[width="80%"]
|=============================================================
| *CLA* | *INS* | *P1* | *P2* | *LC*
| E0 | 20 | 00 | 00 | 00
|=============================================================
_Input data_
None
_Output data_
[width="80%"]
|===========================================
| *Description* | *Length*
| Challenge value (BE) | 4
|===========================================
### PROVIDE DOMAIN NAME
#### Description
This command provides a domain name (like ENS) to be displayed during transactions in place of the address it is associated to.
It shall be run just before a transaction involving the associated address that would be displayed on the device.
The signature is computed on the TLV payload (minus the signature obviously).
#### Coding
_Command_
[width="80%"]
|==============================================================
| *CLA* | *INS* | *P1* | *P2* | *LC*
| E0 | 22 | 01 : first chunk
00 : following chunk
| 00 | 00
|==============================================================
_Input data_
##### If P1 == first chunk
[width="80%"]
|==========================================
| *Description* | *Length (byte)*
| Payload length | 2
| TLV payload | variable
|==========================================
##### If P1 == following chunk
[width="80%"]
|==========================================
| *Description* | *Length (byte)*
| TLV payload | variable
|==========================================
_Output data_
None
## Transport protocol
### General transport description

View File

@@ -134,6 +134,17 @@ The following return codes are expected, any other will abort the signing proces
* ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized
* ETH_PLUGIN_RESULT_FALLBACK : if the signing logic should fallback to the generic one
There are already defined functions to extract data from a parameter:
[source,C]
----
void copy_address(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size);
void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size);
// Get the value from the beginning of the parameter (right to left) and check if the rest of it is zero
bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value);
bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value);
----
### ETH_PLUGIN_FINALIZE
[source,C]

View File

@@ -96,6 +96,8 @@ tx = UnsignedTransaction(
)
encodedTx = encode(tx, UnsignedTransaction)
# To test an EIP-1559 transaction, uncomment this line
# encodedTx = bytearray.fromhex(
# "02ef0306843b9aca008504a817c80082520894b2bb2b958afa2e96dab3f3ce7162b87daea39017872386f26fc1000080c0")

BIN
icons/nanos_app_id4good.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 B

BIN
icons/nanos_app_oasys.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

BIN
icons/nanox_app_id4good.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

BIN
icons/nanox_app_oasys.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

View File

@@ -0,0 +1,3 @@
APP_LOAD_PARAMS += --path "44'/246'" --path "44'/60'"
DEFINES += CHAINID_UPCASE=\"ENERGYWEBCHAIN\" CHAINID_COINNAME=\"EWT\" CHAIN_KIND=CHAIN_KIND_ENERGYWEBCHAIN CHAIN_ID=246
APPNAME = "EnergyWebChain"

View File

@@ -1,3 +0,0 @@
APP_LOAD_PARAMS += --path "44'/246'" --path "44'/60'"
DEFINES += CHAINID_UPCASE=\"EWC\" CHAINID_COINNAME=\"EWC\" CHAIN_KIND=CHAIN_KIND_EWC CHAIN_ID=246
APPNAME = "EnergyWebChain"

View File

@@ -0,0 +1,3 @@
APP_LOAD_PARAMS += --path "44'/161803'"
DEFINES += CHAINID_UPCASE=\"ID4GOOD\" CHAINID_COINNAME=\"A4G\" CHAIN_KIND=CHAIN_KIND_ID4GOOD CHAIN_ID=846000
APPNAME = "ID4Good"

View File

@@ -0,0 +1,3 @@
APP_LOAD_PARAMS += --path "44'/685'" --path "44'/60'"
DEFINES += CHAINID_UPCASE=\"OASYS\" CHAINID_COINNAME=\"OAS\" CHAIN_KIND=CHAIN_KIND_OASYS CHAIN_ID=248
APPNAME = "Oasys"

View File

@@ -1,3 +1,3 @@
APP_LOAD_PARAMS += --path "44'/550'"
APP_LOAD_PARAMS += --path "44'/60'" --path "44'/550'"
DEFINES += CHAINID_UPCASE=\"XDCNETWORK\" CHAINID_COINNAME=\"XDC\" CHAIN_KIND=CHAIN_KIND_XDCNETWORK CHAIN_ID=50
APPNAME = "XDC Network"
APPNAME = "XDC Network"

View File

@@ -24,6 +24,8 @@
#define INS_EIP712_STRUCT_DEF 0x1A
#define INS_EIP712_STRUCT_IMPL 0x1C
#define INS_EIP712_FILTERING 0x1E
#define INS_ENS_GET_CHALLENGE 0x20
#define INS_ENS_PROVIDE_INFO 0x22
#define P1_CONFIRM 0x01
#define P1_NON_CONFIRM 0x00
#define P2_NO_CHAINCODE 0x00

View File

@@ -49,7 +49,7 @@ typedef enum chain_kind_e {
CHAIN_KIND_TOBALABA,
CHAIN_KIND_DEXON,
CHAIN_KIND_VOLTA,
CHAIN_KIND_EWC,
CHAIN_KIND_ENERGYWEBCHAIN,
CHAIN_KIND_ARTIS_SIGMA1,
CHAIN_KIND_ARTIS_TAU1,
CHAIN_KIND_WEBCHAIN,
@@ -72,7 +72,9 @@ typedef enum chain_kind_e {
CHAIN_KIND_METER,
CHAIN_KIND_MULTIVAC,
CHAIN_KIND_TECRA,
CHAIN_KIND_APOTHEMNETWORK
CHAIN_KIND_APOTHEMNETWORK,
CHAIN_KIND_ID4GOOD,
CHAIN_KIND_OASYS
} chain_kind_t;
typedef struct chain_config_s {

View File

@@ -63,12 +63,8 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI,
queryContractUI->item2 = &tmpCtx.transactionContext.extraInfo[0];
}
strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN);
queryContractUI->screenIndex = screenIndex;
strlcpy(queryContractUI->network_ticker,
get_network_ticker(),
sizeof(queryContractUI->network_ticker));
strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN);
queryContractUI->title = title;
queryContractUI->titleLength = titleLength;
queryContractUI->msg = msg;

View File

@@ -31,6 +31,4 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
// NULL for cached address, or base contract address
eth_plugin_result_t eth_plugin_call(int method, void *parameter);
void plugin_ui_start(void);
#endif // _ETH_PLUGIN_HANDLER_H_

View File

@@ -13,7 +13,8 @@ typedef enum {
ETH_PLUGIN_INTERFACE_VERSION_2 = 2,
ETH_PLUGIN_INTERFACE_VERSION_3 = 3,
ETH_PLUGIN_INTERFACE_VERSION_4 = 4,
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 5,
ETH_PLUGIN_INTERFACE_VERSION_5 = 5,
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 6
} eth_plugin_interface_version_t;
typedef enum {

View File

@@ -1,5 +1,6 @@
#include <string.h>
#include "eth_plugin_internal.h"
#include "ethUtils.h" // allzeroes
bool erc20_plugin_available_check(void);
@@ -15,6 +16,24 @@ void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size) {
memmove(dst, parameter, copy_size);
}
bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value) {
if (allzeroes(parameter, PARAMETER_LENGTH - sizeof(uint16_t))) {
*value = U2BE(parameter, PARAMETER_LENGTH - sizeof(uint16_t));
return true;
}
return false;
}
bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value) {
if (allzeroes(parameter, PARAMETER_LENGTH - sizeof(uint32_t))) {
*value = U4BE(parameter, PARAMETER_LENGTH - sizeof(uint32_t));
return true;
}
return false;
}
#ifdef HAVE_STARKWARE
void starkware_plugin_call(int message, void* parameters);
#endif

View File

@@ -16,6 +16,11 @@ void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size);
void erc721_plugin_call(int message, void* parameters);
void erc1155_plugin_call(int message, void* parameters);
// Get the value from the beginning of the parameter (right to left) and check if the rest of it is
// zero
bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value);
bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value);
typedef bool (*PluginAvailableCheck)(void);
typedef struct internalEthPlugin_t {

View File

@@ -1,11 +0,0 @@
#include "shared_context.h"
#include "eth_plugin_handler.h"
#include "ux.h"
#include "feature_signTx.h"
void plugin_ui_start() {
dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE;
dataContext.tokenContext.pluginUiCurrentItem = 0;
ux_approve_tx(true);
}

View File

@@ -20,8 +20,7 @@ int handle_get_printable_amount(get_printable_amount_parameters_t* params, chain
if (params->is_fee) {
uint8_t ticker_len = strnlen(config->coinName, sizeof(config->coinName));
memcpy(ticker, config->coinName, ticker_len);
ticker[ticker_len] = ' ';
ticker[ticker_len + 1] = '\0';
ticker[ticker_len] = '\0';
decimals = WEI_TO_ETHER;
} else {
// If the amount is *not* a fee, decimals and ticker are built from the given config

View File

@@ -29,6 +29,8 @@
#include "handle_get_printable_amount.h"
#include "handle_check_address.h"
#include "commands_712.h"
#include "challenge.h"
#include "domain_name.h"
#ifdef HAVE_STARKWARE
#include "stark_crypto.h"
@@ -228,8 +230,8 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
case CHAIN_KIND_VOLTA:
numTokens = NUM_TOKENS_VOLTA;
break;
case CHAIN_KIND_EWC:
numTokens = NUM_TOKENS_EWC;
case CHAIN_KIND_ENERGYWEBCHAIN:
numTokens = NUM_TOKENS_ENERGYWEBCHAIN;
break;
case CHAIN_KIND_WEBCHAIN:
numTokens = NUM_TOKENS_WEBCHAIN;
@@ -294,6 +296,12 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
case CHAIN_KIND_APOTHEMNETWORK:
numTokens = NUM_TOKENS_APOTHEMNETWORK;
break;
case CHAIN_KIND_ID4GOOD:
numTokens = NUM_TOKENS_ID4GOOD;
break;
case CHAIN_KIND_OASYS:
numTokens = NUM_TOKENS_OASYS;
break;
}
for (i = 0; i < numTokens; i++) {
switch (chainConfig->kind) {
@@ -381,8 +389,8 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
case CHAIN_KIND_VOLTA:
currentToken = (tokenDefinition_t *) PIC(&TOKENS_VOLTA[i]);
break;
case CHAIN_KIND_EWC:
currentToken = (tokenDefinition_t *) PIC(&TOKENS_EWC[i]);
case CHAIN_KIND_ENERGYWEBCHAIN:
currentToken = (tokenDefinition_t *) PIC(&TOKENS_ENERGYWEBCHAIN[i]);
break;
case CHAIN_KIND_WEBCHAIN:
currentToken = (tokenDefinition_t *) PIC(&TOKENS_WEBCHAIN[i]);
@@ -447,6 +455,12 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
case CHAIN_KIND_APOTHEMNETWORK:
currentToken = (tokenDefinition_t *) PIC(&TOKENS_APOTHEMNETWORK[i]);
break;
case CHAIN_KIND_ID4GOOD:
currentToken = (tokenDefinition_t *) PIC(&TOKENS_ID4GOOD[i]);
break;
case CHAIN_KIND_OASYS:
currentToken = (tokenDefinition_t *) PIC(&TOKENS_OASYS[i]);
break;
}
if (memcmp(currentToken->address, tmpContent.txContent.destination, ADDRESS_LENGTH) == 0) {
return currentToken;
@@ -667,10 +681,12 @@ 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]);
if (!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])) {
reset_app_context();
}
break;
case INS_SIGN_EIP_712_MESSAGE:
@@ -735,6 +751,19 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
break;
#endif // HAVE_EIP712_FULL_SUPPORT
#ifdef HAVE_DOMAIN_NAME
case INS_ENS_GET_CHALLENGE:
handle_get_challenge();
break;
case INS_ENS_PROVIDE_INFO:
handle_provide_domain_name(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC]);
break;
#endif // HAVE_DOMAIN_NAME
#if 0
case 0xFF: // return to dashboard
goto return_to_dashboard;
@@ -912,7 +941,7 @@ void app_exit() {
void init_coin_config(chain_config_t *coin_config) {
memset(coin_config, 0, sizeof(chain_config_t));
strcpy(coin_config->coinName, CHAINID_COINNAME " ");
strcpy(coin_config->coinName, CHAINID_COINNAME);
coin_config->chainId = CHAIN_ID;
coin_config->kind = CHAIN_KIND;
}
@@ -940,16 +969,22 @@ void coin_main(chain_config_t *coin_config) {
G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0);
#endif // TARGET_NANOX
if (N_storage.initialized != 0x01) {
if (!N_storage.initialized) {
internalStorage_t storage;
#ifdef HAVE_ALLOW_DATA
storage.dataAllowed = 0x01;
storage.dataAllowed = true;
#else
storage.dataAllowed = 0x00;
storage.dataAllowed = false;
#endif
storage.contractDetails = 0x00;
storage.displayNonce = 0x00;
storage.initialized = 0x01;
storage.contractDetails = false;
storage.displayNonce = false;
#ifdef HAVE_EIP712_FULL_SUPPORT
storage.verbose_eip712 = false;
#endif
#ifdef HAVE_DOMAIN_NAME
storage.verbose_domain_name = false;
#endif
storage.initialized = true;
nvm_write((void *) &N_storage, (void *) &storage, sizeof(internalStorage_t));
}
@@ -963,6 +998,11 @@ void coin_main(chain_config_t *coin_config) {
BLE_power(1, "Nano X");
#endif // HAVE_BLE
#ifdef HAVE_DOMAIN_NAME
// to prevent it from having a fixed value at boot
roll_challenge();
#endif // HAVE_DOMAIN_NAME
app_main();
}
CATCH(EXCEPTION_IO_RESET) {

View File

@@ -24,13 +24,16 @@ typedef struct bip32_path_t {
} bip32_path_t;
typedef struct internalStorage_t {
unsigned char dataAllowed;
unsigned char contractDetails;
unsigned char displayNonce;
bool dataAllowed;
bool contractDetails;
bool displayNonce;
#ifdef HAVE_EIP712_FULL_SUPPORT
bool verbose_eip712;
#endif // HAVE_EIP712_FULL_SUPPORT
uint8_t initialized;
#ifdef HAVE_DOMAIN_NAME
bool verbose_domain_name;
#endif // HAVE_DOMAIN_NAME
bool initialized;
} internalStorage_t;
#ifdef HAVE_STARKWARE
@@ -50,10 +53,9 @@ typedef enum starkQuantumType_e {
typedef struct tokenContext_t {
char pluginName[PLUGIN_ID_LENGTH];
uint8_t pluginStatus;
uint8_t data[INT256_LENGTH];
uint8_t fieldIndex;
uint16_t fieldIndex;
uint8_t fieldOffset;
uint8_t pluginUiMaxItems;
@@ -65,9 +67,13 @@ typedef struct tokenContext_t {
uint8_t contractAddress[ADDRESS_LENGTH];
uint8_t methodSelector[SELECTOR_LENGTH];
};
uint8_t pluginContext[5 * INT256_LENGTH];
// This needs to be strictly 4 bytes aligned since pointers to it will be casted as
// plugin context struct pointers (structs that contain up to 4 bytes wide elements)
uint8_t pluginContext[5 * INT256_LENGTH] __attribute__((aligned(4)));
};
uint8_t pluginStatus;
#ifdef HAVE_STARKWARE
uint8_t quantum[32];
uint8_t mintingBlob[32];
@@ -77,6 +83,8 @@ typedef struct tokenContext_t {
} tokenContext_t;
_Static_assert((offsetof(tokenContext_t, pluginContext) % 4) == 0, "Plugin context not aligned");
typedef struct publicKeyContext_t {
cx_ecfp_public_key_t publicKey;
char address[41];
@@ -167,7 +175,7 @@ typedef enum {
#define NETWORK_STRING_MAX_SIZE 16
typedef struct txStringProperties_t {
typedef struct txStringProperties_s {
char fullAddress[43];
char fullAmount[79]; // 2^256 is 78 digits long
char maxFee[50];
@@ -182,7 +190,7 @@ typedef struct txStringProperties_t {
#endif
#define SHARED_CTX_FIELD_2_SIZE 40
typedef struct strDataTmp_t {
typedef struct strDataTmp_s {
char tmp[SHARED_CTX_FIELD_1_SIZE];
char tmp2[SHARED_CTX_FIELD_2_SIZE];
} strDataTmp_t;

View File

@@ -172,7 +172,7 @@ const tokenDefinition_t const TOKENS_DEXON[NUM_TOKENS_DEXON] = {};
const tokenDefinition_t const TOKENS_VOLTA[NUM_TOKENS_VOLTA] = {};
const tokenDefinition_t const TOKENS_EWC[NUM_TOKENS_EWC] = {};
const tokenDefinition_t const TOKENS_ENERGYWEBCHAIN[NUM_TOKENS_ENERGYWEBCHAIN] = {};
const tokenDefinition_t const TOKENS_ARTIS_SIGMA1[NUM_TOKENS_ARTIS_SIGMA1] = {};
@@ -222,4 +222,6 @@ const tokenDefinition_t const TOKENS_TECRA[NUM_TOKENS_TECRA] = {};
const tokenDefinition_t const TOKENS_APOTHEMNETWORK[NUM_TOKENS_APOTHEMNETWORK] = {};
const tokenDefinition_t const TOKENS_OASYS[NUM_TOKENS_OASYS] = {};
#endif

View File

@@ -21,7 +21,7 @@
#include <stdint.h>
#include "ethUstream.h"
#define MAX_TICKER_LEN 12 // 10 characters + ' ' + '\0'
#define MAX_TICKER_LEN 11 // 10 characters + '\0'
#define MAX_ITEMS 2
typedef struct tokenDefinition_t {
@@ -43,21 +43,31 @@ extern tokenDefinition_t const TOKENS_EXTRA[NUM_TOKENS_EXTRA];
#ifndef HAVE_TOKENS_LIST
#if defined(HAVE_CAL_TEST_KEY) && defined(HAVE_CAL_CI_KEY)
#error "CAL key contradiction, two alternative keys selected at once"
#endif
static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
#ifndef HAVE_CAL_TESTING_KEY
// production key 2019-01-11 03:07PM (erc20signer)
0x04, 0x5e, 0x6c, 0x10, 0x20, 0xc1, 0x4d, 0xc4, 0x64, 0x42, 0xfe, 0x89, 0xf9, 0x7c,
0x0b, 0x68, 0xcd, 0xb1, 0x59, 0x76, 0xdc, 0x24, 0xf2, 0x4c, 0x31, 0x6e, 0x7b, 0x30,
0xfe, 0x4e, 0x8c, 0xc7, 0x6b, 0x14, 0x89, 0x15, 0x0c, 0x21, 0x51, 0x4e, 0xbf, 0x44,
0x0f, 0xf5, 0xde, 0xa5, 0x39, 0x3d, 0x83, 0xde, 0x53, 0x58, 0xcd, 0x09, 0x8f, 0xce,
0x8f, 0xd0, 0xf8, 0x1d, 0xaa, 0x94, 0x97, 0x91, 0x83
#else
#if defined(HAVE_CAL_TEST_KEY)
// test key 2019-01-11 03:07PM (erc20signer)
0x04, 0x20, 0xda, 0x62, 0x00, 0x3c, 0x0c, 0xe0, 0x97, 0xe3, 0x36, 0x44, 0xa1, 0x0f,
0xe4, 0xc3, 0x04, 0x54, 0x06, 0x9a, 0x44, 0x54, 0xf0, 0xfa, 0x9d, 0x4e, 0x84, 0xf4,
0x50, 0x91, 0x42, 0x9b, 0x52, 0x20, 0xaf, 0x9e, 0x35, 0xc0, 0xb2, 0xd9, 0x28, 0x93,
0x80, 0x13, 0x73, 0x07, 0xde, 0x4d, 0xd1, 0xd4, 0x18, 0x42, 0x8c, 0xf2, 0x1a, 0x93,
0xb3, 0x35, 0x61, 0xbb, 0x09, 0xd8, 0x8f, 0xe5, 0x79
#elif defined(HAVE_CAL_CI_KEY)
0x04, 0x4c, 0xca, 0x8f, 0xad, 0x49, 0x6a, 0xa5, 0x04, 0x0a, 0x00, 0xa7, 0xeb, 0x2f,
0x5c, 0xc3, 0xb8, 0x53, 0x76, 0xd8, 0x8b, 0xa1, 0x47, 0xa7, 0xd7, 0x05, 0x4a, 0x99,
0xc6, 0x40, 0x56, 0x18, 0x87, 0xfe, 0x17, 0xa0, 0x96, 0xe3, 0x6c, 0x3b, 0x52, 0x3b,
0x24, 0x4f, 0x3e, 0x2f, 0xf7, 0xf8, 0x40, 0xae, 0x26, 0xc4, 0xe7, 0x7a, 0xd3, 0xbc,
0x73, 0x9a, 0xf5, 0xde, 0x6f, 0x2d, 0x77, 0xa7, 0xb6
#else
// production key 2019-01-11 03:07PM (erc20signer)
0x04, 0x5e, 0x6c, 0x10, 0x20, 0xc1, 0x4d, 0xc4, 0x64, 0x42, 0xfe, 0x89, 0xf9, 0x7c,
0x0b, 0x68, 0xcd, 0xb1, 0x59, 0x76, 0xdc, 0x24, 0xf2, 0x4c, 0x31, 0x6e, 0x7b, 0x30,
0xfe, 0x4e, 0x8c, 0xc7, 0x6b, 0x14, 0x89, 0x15, 0x0c, 0x21, 0x51, 0x4e, 0xbf, 0x44,
0x0f, 0xf5, 0xde, 0xa5, 0x39, 0x3d, 0x83, 0xde, 0x53, 0x58, 0xcd, 0x09, 0x8f, 0xce,
0x8f, 0xd0, 0xf8, 0x1d, 0xaa, 0x94, 0x97, 0x91, 0x83
#endif
};
@@ -90,7 +100,7 @@ static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
#define NUM_TOKENS_TOBALABA 0
#define NUM_TOKENS_DEXON 0
#define NUM_TOKENS_VOLTA 0
#define NUM_TOKENS_EWC 0
#define NUM_TOKENS_ENERGYWEBCHAIN 0
#define NUM_TOKENS_ARTIS_SIGMA1 0
#define NUM_TOKENS_ARTIS_TAU1 0
#define NUM_TOKENS_WEBCHAIN 0
@@ -115,6 +125,7 @@ static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
#define NUM_TOKENS_MULTIVAC 0
#define NUM_TOKENS_TECRA 0
#define NUM_TOKENS_APOTHEMNETWORK 0
#define NUM_TOKENS_OASYS 0
extern tokenDefinition_t const TOKENS_AKROMA[NUM_TOKENS_AKROMA];
extern tokenDefinition_t const TOKENS_ELLAISM[NUM_TOKENS_ELLAISM];
@@ -141,7 +152,7 @@ extern tokenDefinition_t const TOKENS_TOMOCHAIN[NUM_TOKENS_TOMOCHAIN];
extern tokenDefinition_t const TOKENS_TOBALABA[NUM_TOKENS_TOBALABA];
extern tokenDefinition_t const TOKENS_DEXON[NUM_TOKENS_DEXON];
extern tokenDefinition_t const TOKENS_VOLTA[NUM_TOKENS_VOLTA];
extern tokenDefinition_t const TOKENS_EWC[NUM_TOKENS_EWC];
extern tokenDefinition_t const TOKENS_ENERGYWEBCHAIN[NUM_TOKENS_ENERGYWEBCHAIN];
extern tokenDefinition_t const TOKENS_ARTIS_SIGMA1[NUM_TOKENS_ARTIS_SIGMA1];
extern tokenDefinition_t const TOKENS_ARTIS_TAU1[NUM_TOKENS_ARTIS_TAU1];
extern tokenDefinition_t const TOKENS_WEBCHAIN[NUM_TOKENS_WEBCHAIN];
@@ -165,6 +176,7 @@ 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];
extern tokenDefinition_t const TOKENS_OASYS[NUM_TOKENS_OASYS];
#endif /* HAVE_TOKENS_LIST */

View File

@@ -132,6 +132,9 @@ void amountToString(const uint8_t *amount,
uint8_t ticker_len = strnlen(ticker, MAX_TICKER_LEN);
memcpy(out_buffer, ticker, MIN(out_buffer_size, ticker_len));
if (ticker_len > 0) {
out_buffer[ticker_len++] = ' ';
}
if (adjustDecimals(tmp_buffer,
amount_len,
@@ -155,8 +158,7 @@ bool parse_swap_config(const uint8_t *config, uint8_t config_len, char *ticker,
}
memcpy(ticker, config + offset, ticker_len);
offset += ticker_len;
ticker[ticker_len] = ' ';
ticker[ticker_len + 1] = '\0';
ticker[ticker_len] = '\0';
if (config_len - offset < 1) {
return false;

17
src_bagl/ui_domain_name.c Normal file
View File

@@ -0,0 +1,17 @@
#ifdef HAVE_DOMAIN_NAME
#include "ux.h"
#include "domain_name.h"
//////////////////////////////////////////////////////////////////////
// clang-format off
UX_STEP_NOCB(
ux_domain_name_step,
bnnn_paging,
{
.title = "Domain",
.text = g_domain_name
});
// clang-format on
#endif // HAVE_DOMAIN_NAME

12
src_bagl/ui_domain_name.h Normal file
View File

@@ -0,0 +1,12 @@
#ifdef HAVE_DOMAIN_NAME
#ifndef UI_DOMAIN_NAME_H_
#define UI_DOMAIN_NAME_H_
#include "ux.h"
extern const ux_flow_step_t ux_domain_name_step;
#endif // UI_DOMAIN_NAME_H_
#endif // HAVE_DOMAIN_NAME

View File

@@ -7,13 +7,26 @@
#define DISABLED_STR "Disabled"
#define BUF_INCREMENT (MAX(strlen(ENABLED_STR), strlen(DISABLED_STR)) + 1)
void display_settings(const ux_flow_step_t* const start_step);
void switch_settings_blind_signing(void);
void switch_settings_display_data(void);
void switch_settings_display_nonce(void);
// Reuse the strings.common.fullAmount buffer for settings displaying.
// No risk of collision as this buffer is unused in the settings menu
#define SETTING_BLIND_SIGNING_STATE (strings.common.fullAmount)
#define SETTING_DISPLAY_DATA_STATE (strings.common.fullAmount + (BUF_INCREMENT * 1))
#define SETTING_DISPLAY_NONCE_STATE (strings.common.fullAmount + (BUF_INCREMENT * 2))
#define SETTING_VERBOSE_EIP712_STATE (strings.common.fullAmount + (BUF_INCREMENT * 3))
#define SETTING_VERBOSE_DOMAIN_NAME_STATE (strings.common.fullAmount + (BUF_INCREMENT * 4))
#define BOOL_TO_STATE_STR(b) (b ? ENABLED_STR : DISABLED_STR)
static void display_settings(const ux_flow_step_t* const start_step);
static void switch_settings_blind_signing(void);
static void switch_settings_display_data(void);
static void switch_settings_display_nonce(void);
#ifdef HAVE_EIP712_FULL_SUPPORT
void switch_settings_verbose_eip712(void);
static void switch_settings_verbose_eip712(void);
#endif // HAVE_EIP712_FULL_SUPPORT
#ifdef HAVE_DOMAIN_NAME
static void switch_settings_verbose_domain_name(void);
#endif // HAVE_DOMAIN_NAME
//////////////////////////////////////////////////////////////////////
// clang-format off
@@ -75,7 +88,7 @@ UX_STEP_CB(
"Transaction",
"blind signing",
#endif
strings.common.fullAddress
SETTING_BLIND_SIGNING_STATE
});
UX_STEP_CB(
@@ -95,7 +108,7 @@ UX_STEP_CB(
"Show contract data",
"details",
#endif
strings.common.fullAddress + BUF_INCREMENT
SETTING_DISPLAY_DATA_STATE
});
UX_STEP_CB(
@@ -115,7 +128,7 @@ UX_STEP_CB(
"Show account nonce",
"in transactions",
#endif
strings.common.fullAddress + (BUF_INCREMENT * 2)
SETTING_DISPLAY_NONCE_STATE
});
#ifdef HAVE_EIP712_FULL_SUPPORT
@@ -127,10 +140,23 @@ UX_STEP_CB(
"Verbose EIP-712",
"Ignore filtering &",
"display raw content",
strings.common.fullAddress + (BUF_INCREMENT * 3)
SETTING_VERBOSE_EIP712_STATE
});
#endif // HAVE_EIP712_FULL_SUPPORT
#ifdef HAVE_DOMAIN_NAME
UX_STEP_CB(
ux_settings_flow_verbose_domain_name_step,
bnnn,
switch_settings_verbose_domain_name(),
{
"Verbose domains",
"Show",
"resolved address",
SETTING_VERBOSE_DOMAIN_NAME_STATE
});
#endif // HAVE_DOMAIN_NAME
UX_STEP_CB(
ux_settings_flow_back_step,
@@ -149,54 +175,61 @@ UX_FLOW(ux_settings_flow,
#ifdef HAVE_EIP712_FULL_SUPPORT
&ux_settings_flow_verbose_eip712_step,
#endif // HAVE_EIP712_FULL_SUPPORT
#ifdef HAVE_DOMAIN_NAME
&ux_settings_flow_verbose_domain_name_step,
#endif // HAVE_DOMAIN_NAME
&ux_settings_flow_back_step);
void display_settings(const ux_flow_step_t* const start_step) {
bool settings[] = {N_storage.dataAllowed,
N_storage.contractDetails,
N_storage.displayNonce,
static void display_settings(const ux_flow_step_t* const start_step) {
strlcpy(SETTING_BLIND_SIGNING_STATE, BOOL_TO_STATE_STR(N_storage.dataAllowed), BUF_INCREMENT);
strlcpy(SETTING_DISPLAY_DATA_STATE,
BOOL_TO_STATE_STR(N_storage.contractDetails),
BUF_INCREMENT);
strlcpy(SETTING_DISPLAY_NONCE_STATE, BOOL_TO_STATE_STR(N_storage.displayNonce), BUF_INCREMENT);
#ifdef HAVE_EIP712_FULL_SUPPORT
N_storage.verbose_eip712
strlcpy(SETTING_VERBOSE_EIP712_STATE,
BOOL_TO_STATE_STR(N_storage.verbose_eip712),
BUF_INCREMENT);
#endif // HAVE_EIP712_FULL_SUPPORT
};
uint8_t offset = 0;
for (unsigned int i = 0; i < ARRAY_SIZE(settings); ++i) {
strlcpy(strings.common.fullAddress + offset,
(settings[i] ? ENABLED_STR : DISABLED_STR),
sizeof(strings.common.fullAddress) - offset);
offset += BUF_INCREMENT;
}
#ifdef HAVE_DOMAIN_NAME
strlcpy(SETTING_VERBOSE_DOMAIN_NAME_STATE,
BOOL_TO_STATE_STR(N_storage.verbose_domain_name),
BUF_INCREMENT);
#endif // HAVE_DOMAIN_NAME
ux_flow_init(0, ux_settings_flow, start_step);
}
void switch_settings_blind_signing(void) {
uint8_t value = (N_storage.dataAllowed ? 0 : 1);
nvm_write((void*) &N_storage.dataAllowed, (void*) &value, sizeof(uint8_t));
display_settings(&ux_settings_flow_blind_signing_step);
static void toggle_setting(volatile bool* setting, const ux_flow_step_t* ui_step) {
bool value = !*setting;
nvm_write((void*) setting, (void*) &value, sizeof(value));
display_settings(ui_step);
}
void switch_settings_display_data(void) {
uint8_t value = (N_storage.contractDetails ? 0 : 1);
nvm_write((void*) &N_storage.contractDetails, (void*) &value, sizeof(uint8_t));
display_settings(&ux_settings_flow_display_data_step);
static void switch_settings_blind_signing(void) {
toggle_setting(&N_storage.dataAllowed, &ux_settings_flow_blind_signing_step);
}
void switch_settings_display_nonce(void) {
uint8_t value = (N_storage.displayNonce ? 0 : 1);
nvm_write((void*) &N_storage.displayNonce, (void*) &value, sizeof(uint8_t));
display_settings(&ux_settings_flow_display_nonce_step);
static void switch_settings_display_data(void) {
toggle_setting(&N_storage.contractDetails, &ux_settings_flow_display_data_step);
}
static void switch_settings_display_nonce(void) {
toggle_setting(&N_storage.displayNonce, &ux_settings_flow_display_nonce_step);
}
#ifdef HAVE_EIP712_FULL_SUPPORT
void switch_settings_verbose_eip712(void) {
bool value = !N_storage.verbose_eip712;
nvm_write((void*) &N_storage.verbose_eip712, (void*) &value, sizeof(value));
display_settings(&ux_settings_flow_verbose_eip712_step);
static void switch_settings_verbose_eip712(void) {
toggle_setting(&N_storage.verbose_eip712, &ux_settings_flow_verbose_eip712_step);
}
#endif // HAVE_EIP712_FULL_SUPPORT
#ifdef HAVE_DOMAIN_NAME
static void switch_settings_verbose_domain_name(void) {
toggle_setting(&N_storage.verbose_domain_name, &ux_settings_flow_verbose_domain_name_step);
}
#endif // HAVE_DOMAIN_NAME
//////////////////////////////////////////////////////////////////////
// clang-format off
#ifdef TARGET_NANOS

View File

@@ -18,6 +18,9 @@ static void dummy_pre_cb(void) {
static void dummy_post_cb(void) {
if (ui_pos == UI_191_POS_QUESTION) {
// temporarily disable button clicks, they will be re-enabled as soon as new data
// is received and the page is redrawn with ux_flow_init()
G_ux.stack[0].button_push_callback = NULL;
continue_displaying_message();
} else // UI_191_END
{
@@ -55,6 +58,7 @@ UX_STEP_CB(
#else
nnn,
#endif
G_ux.stack[0].button_push_callback = NULL; // disable button clicks
skip_rest_of_message(),
{
#ifndef TARGET_NANOS

View File

@@ -7,19 +7,25 @@ enum { UI_712_POS_REVIEW, UI_712_POS_END };
static uint8_t ui_pos;
static void dummy_cb(void) {
if (!ui_712_next_field()) {
if (ui_pos == UI_712_POS_REVIEW) {
ux_flow_next();
ui_pos = UI_712_POS_END;
} else // UI_712_POS_END
{
ux_flow_prev();
ui_pos = UI_712_POS_REVIEW;
}
} else {
// temporarily disable button clicks, they will be re-enabled as soon as new data
// is received and the page is redrawn with ux_flow_init()
G_ux.stack[0].button_push_callback = NULL;
switch (ui_712_next_field()) {
case EIP712_NO_MORE_FIELD:
if (ui_pos == UI_712_POS_REVIEW) {
ux_flow_next();
ui_pos = UI_712_POS_END;
} else // UI_712_POS_END
{
ux_flow_prev();
ui_pos = UI_712_POS_REVIEW;
}
break;
case EIP712_FIELD_INCOMING:
// temporarily disable button clicks, they will be re-enabled as soon as new data
// is received and the page is redrawn with ux_flow_init()
G_ux.stack[0].button_push_callback = NULL;
break;
case EIP712_FIELD_LATER:
default:
break;
}
}

View File

@@ -8,6 +8,8 @@
#include "ui_plugin.h"
#include "common_ui.h"
#include "plugins.h"
#include "domain_name.h"
#include "ui_domain_name.h"
// clang-format off
UX_STEP_NOCB(
@@ -217,7 +219,19 @@ void ux_approve_tx(bool fromPlugin) {
} else {
// We're in a regular transaction, just show the amount and the address
ux_approval_tx_flow[step++] = &ux_approval_amount_step;
ux_approval_tx_flow[step++] = &ux_approval_address_step;
#ifdef HAVE_DOMAIN_NAME
uint64_t chain_id = get_chain_id();
if (has_domain_name(&chain_id, tmpContent.txContent.destination)) {
ux_approval_tx_flow[step++] = &ux_domain_name_step;
if (N_storage.verbose_domain_name) {
ux_approval_tx_flow[step++] = &ux_approval_address_step;
}
} else {
#endif // HAVE_DOMAIN_NAME
ux_approval_tx_flow[step++] = &ux_approval_address_step;
#ifdef HAVE_DOMAIN_NAME
}
#endif // HAVE_DOMAIN_NAME
}
if (N_storage.displayNonce) {
@@ -235,4 +249,4 @@ void ux_approve_tx(bool fromPlugin) {
ux_approval_tx_flow[step++] = FLOW_END_STEP;
ux_flow_init(0, ux_approval_tx_flow, NULL);
}
}

View File

@@ -18,8 +18,8 @@ UX_STEP_NOCB(ux_stark_unsafe_sign_1_step,
pnn,
{
&C_icon_warning,
"Unsafe",
"Stark Sign",
"StarkNet",
"Blind Sign",
});
UX_STEP_NOCB_INIT(
@@ -36,7 +36,7 @@ UX_STEP_NOCB_INIT(
bnnn_paging,
stark_unsafe_sign_display_hash(),
{
.title = "Hash",
.title = "Tx Hash",
.text = strings.tmp.tmp
});

View File

@@ -66,7 +66,7 @@ bool adjustDecimals(const char *src,
size_t targetLength,
uint8_t decimals);
static __attribute__((no_instrument_function)) inline int allzeroes(void *buf, size_t n) {
static __attribute__((no_instrument_function)) inline int allzeroes(const void *buf, size_t n) {
uint8_t *p = (uint8_t *) buf;
for (size_t i = 0; i < n; ++i) {
if (p[i]) {

View File

@@ -1,5 +1,3 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include "hash_bytes.h"
/**
@@ -9,7 +7,7 @@
* @param[in] n number of bytes to hash
* @param[in] hash_ctx pointer to the hashing context
*/
void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *const hash_ctx) {
void hash_nbytes(const uint8_t *bytes_ptr, size_t n, cx_hash_t *hash_ctx) {
cx_hash(hash_ctx, 0, bytes_ptr, n, NULL, 0);
}
@@ -19,8 +17,6 @@ void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *const has
* @param[in] byte byte to hash
* @param[in] hash_ctx pointer to the hashing context
*/
void hash_byte(uint8_t byte, cx_hash_t *const hash_ctx) {
void hash_byte(uint8_t byte, cx_hash_t *hash_ctx) {
hash_nbytes(&byte, 1, hash_ctx);
}
#endif // HAVE_EIP712_FULL_SUPPORT

10
src_common/hash_bytes.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef HASH_BYTES_H_
#define HASH_BYTES_H_
#include <stdint.h>
#include "cx.h"
void hash_nbytes(const uint8_t *const bytes_ptr, size_t n, cx_hash_t *hash_ctx);
void hash_byte(uint8_t byte, cx_hash_t *hash_ctx);
#endif // HASH_BYTES_H_

View File

@@ -8,47 +8,61 @@
#include "utils.h"
// Mappping of chain ids to networks.
const network_info_t NETWORK_MAPPING[] = {
{.chain_id = 1, .name = "Ethereum", .ticker = "ETH "},
{.chain_id = 3, .name = "Ropsten", .ticker = "ETH "},
{.chain_id = 4, .name = "Rinkeby", .ticker = "ETH "},
{.chain_id = 5, .name = "Goerli", .ticker = "ETH "},
{.chain_id = 10, .name = "Optimism", .ticker = "ETH "},
{.chain_id = 42, .name = "Kovan", .ticker = "ETH "},
{.chain_id = 56, .name = "BSC", .ticker = "BNB "},
{.chain_id = 100, .name = "xDai", .ticker = "xDAI "},
{.chain_id = 137, .name = "Polygon", .ticker = "MATIC "},
{.chain_id = 250, .name = "Fantom", .ticker = "FTM "},
{.chain_id = 42161, .name = "Arbitrum", .ticker = "AETH "},
{.chain_id = 42220, .name = "Celo", .ticker = "CELO "},
{.chain_id = 43114, .name = "Avalanche", .ticker = "AVAX "},
{.chain_id = 44787, .name = "Celo Alfajores", .ticker = "aCELO "},
{.chain_id = 62320, .name = "Celo Baklava", .ticker = "bCELO "},
{.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 = 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 "}};
static const network_info_t NETWORK_MAPPING[] = {
{.chain_id = 1, .name = "Ethereum", .ticker = "ETH"},
{.chain_id = 3, .name = "Ropsten", .ticker = "ETH"},
{.chain_id = 4, .name = "Rinkeby", .ticker = "ETH"},
{.chain_id = 5, .name = "Goerli", .ticker = "ETH"},
{.chain_id = 10, .name = "Optimism", .ticker = "ETH"},
{.chain_id = 42, .name = "Kovan", .ticker = "ETH"},
{.chain_id = 56, .name = "BSC", .ticker = "BNB"},
{.chain_id = 100, .name = "xDai", .ticker = "xDAI"},
{.chain_id = 137, .name = "Polygon", .ticker = "MATIC"},
{.chain_id = 250, .name = "Fantom", .ticker = "FTM"},
{.chain_id = 42161, .name = "Arbitrum", .ticker = "ETH"},
{.chain_id = 42220, .name = "Celo", .ticker = "CELO"},
{.chain_id = 43114, .name = "Avalanche", .ticker = "AVAX"},
{.chain_id = 44787, .name = "Celo Alfajores", .ticker = "aCELO"},
{.chain_id = 62320, .name = "Celo Baklava", .ticker = "bCELO"},
{.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 = 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 = "EWT"},
{.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"},
{.chain_id = 534354, .name = "Scroll (Pre-Alpha)", .ticker = "SCR"},
{.chain_id = 534353, .name = "Scroll (Goerli)", .ticker = "SCR"},
{.chain_id = 534352, .name = "Scroll", .ticker = "SCR"},
{.chain_id = 321, .name = "KCC", .ticker = "KCS"},
{.chain_id = 30, .name = "Rootstock", .ticker = "RBTC"},
{.chain_id = 9001, .name = "Evmos", .ticker = "EVMOS"},
{.chain_id = 1088, .name = "Metis Andromeda", .ticker = "METIS"},
{.chain_id = 2222, .name = "Kava EVM", .ticker = "KAVA"},
{.chain_id = 8217, .name = "Klaytn Cypress", .ticker = "KLAY"},
{.chain_id = 57, .name = "Syscoin", .ticker = "SYS"},
{.chain_id = 106, .name = "Velas EVM", .ticker = "VLX"},
{.chain_id = 288, .name = "Boba Network", .ticker = "ETH"},
{.chain_id = 39797, .name = "Energi", .ticker = "NRG"},
{.chain_id = 248, .name = "Oasys", .ticker = "OAS"}};
uint64_t get_chain_id(void) {
uint64_t chain_id = 0;

View File

@@ -5,8 +5,6 @@
#include "tokens.h"
#include "shared_context.h"
#define MAX_NETWORK_TICKER_LEN 8
typedef struct network_info_s {
const char *name;
const char *ticker;

View File

@@ -230,18 +230,20 @@ bool tostring256(const uint256_t *const number,
UPPER(LOWER(base)) = 0;
LOWER(LOWER(base)) = baseParam;
uint32_t offset = 0;
if ((baseParam < 2) || (baseParam > 16)) {
if ((outLength == 0) || (baseParam < 2) || (baseParam > 16)) {
return false;
}
do {
if (offset > (outLength - 1)) {
return false;
}
divmod256(&rDiv, &base, &rDiv, &rMod);
out[offset++] = HEXDIGITS[(uint8_t) LOWER(LOWER(rMod))];
} while (!zero256(&rDiv));
} while (!zero256(&rDiv) && (offset < outLength));
if (offset > (outLength - 1)) {
if (offset == outLength) { // destination buffer too small
if (outLength > 3) {
strlcpy(out, "...", outLength);
} else {
out[0] = '\0';
}
return false;
}

View File

@@ -0,0 +1,14 @@
#ifdef HAVE_DOMAIN_NAME
#ifndef CHALLENGE_H_
#define CHALLENGE_H_
#include <stdint.h>
void roll_challenge(void);
uint32_t get_challenge(void);
void handle_get_challenge(void);
#endif // CHALLENGE_H_
#endif // HAVE_DOMAIN_NAME

View File

@@ -0,0 +1,38 @@
#ifdef HAVE_DOMAIN_NAME
#include <os.h>
#include <os_io.h>
#include <cx.h>
#include "apdu_constants.h"
#include "challenge.h"
static uint32_t challenge;
/**
* Generate a new challenge from the Random Number Generator
*/
void roll_challenge(void) {
challenge = cx_rng_u32();
}
/**
* Get the current challenge
*
* @return challenge
*/
uint32_t get_challenge(void) {
return challenge;
}
/**
* Send back the current challenge
*/
void handle_get_challenge(void) {
PRINTF("New challenge -> %u\n", get_challenge());
U4BE_ENCODE(G_io_apdu_buffer, 0, get_challenge());
U2BE_ENCODE(G_io_apdu_buffer, 4, APDU_RESPONSE_OK);
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 6);
}
#endif // HAVE_DOMAIN_NAME

View File

@@ -0,0 +1,710 @@
#ifdef HAVE_DOMAIN_NAME
#include <os.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include "utils.h" // ARRAY_SIZE
#include "apdu_constants.h"
#include "domain_name.h"
#include "challenge.h"
#include "mem.h"
#include "hash_bytes.h"
static const uint8_t DOMAIN_NAME_PUB_KEY[] = {
#ifdef HAVE_DOMAIN_NAME_TEST_KEY
0x04, 0xb9, 0x1f, 0xbe, 0xc1, 0x73, 0xe3, 0xba, 0x4a, 0x71, 0x4e, 0x01, 0x4e, 0xbc,
0x82, 0x7b, 0x6f, 0x89, 0x9a, 0x9f, 0xa7, 0xf4, 0xac, 0x76, 0x9c, 0xde, 0x28, 0x43,
0x17, 0xa0, 0x0f, 0x4f, 0x65, 0x0f, 0x09, 0xf0, 0x9a, 0xa4, 0xff, 0x5a, 0x31, 0x76,
0x02, 0x55, 0xfe, 0x5d, 0xfc, 0x81, 0x13, 0x29, 0xb3, 0xb5, 0x0b, 0xe9, 0x91, 0x94,
0xfc, 0xa1, 0x16, 0x19, 0xe6, 0x5f, 0x2e, 0xdf, 0xea
#else
0x04, 0x6a, 0x94, 0xe7, 0xa4, 0x2c, 0xd0, 0xc3, 0x3f, 0xdf, 0x44, 0x0c, 0x8e, 0x2a,
0xb2, 0x54, 0x2c, 0xef, 0xbe, 0x5d, 0xb7, 0xaa, 0x0b, 0x93, 0xa9, 0xfc, 0x81, 0x4b,
0x9a, 0xcf, 0xa7, 0x5e, 0xb4, 0xe5, 0x3d, 0x6f, 0x00, 0x25, 0x94, 0xbd, 0xb6, 0x05,
0xd9, 0xb5, 0xbd, 0xa9, 0xfa, 0x4b, 0x4b, 0xf3, 0xa5, 0x49, 0x6f, 0xd3, 0x16, 0x4b,
0xae, 0xf5, 0xaf, 0xcf, 0x90, 0xe8, 0x40, 0x88, 0x71
#endif
};
#define P1_FIRST_CHUNK 0x01
#define P1_FOLLOWING_CHUNK 0x00
#define ALGO_SECP256K1 1
#define SLIP_44_ETHEREUM 60
#define DER_LONG_FORM_FLAG 0x80 // 8th bit set
#define DER_FIRST_BYTE_VALUE_MASK 0x7f
typedef enum { TLV_TAG, TLV_LENGTH, TLV_VALUE } e_tlv_step;
typedef enum {
STRUCTURE_TYPE = 0x01,
STRUCTURE_VERSION = 0x02,
CHALLENGE = 0x12,
SIGNER_KEY_ID = 0x13,
SIGNER_ALGO = 0x14,
SIGNATURE = 0x15,
DOMAIN_NAME = 0x20,
COIN_TYPE = 0x21,
ADDRESS = 0x22
} e_tlv_tag;
typedef enum { KEY_ID_TEST = 0x00, KEY_ID_PROD = 0x03 } e_key_id;
typedef struct {
uint8_t *buf;
uint16_t size;
uint16_t expected_size;
} s_tlv_payload;
typedef struct {
e_tlv_tag tag;
uint8_t length;
const uint8_t *value;
} s_tlv_data;
typedef struct {
bool valid;
char *name;
uint8_t addr[ADDRESS_LENGTH];
} s_domain_name_info;
typedef struct {
e_key_id key_id;
uint8_t input_sig_size;
const uint8_t *input_sig;
cx_sha256_t hash_ctx;
} s_sig_ctx;
typedef bool(t_tlv_handler)(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx);
typedef struct {
uint8_t tag;
t_tlv_handler *func;
uint8_t found;
} s_tlv_handler;
static s_tlv_payload g_tlv_payload = {0};
static s_domain_name_info g_domain_name_info;
char g_domain_name[DOMAIN_NAME_MAX_LENGTH + 1];
/**
* Send a response APDU
*
* @param[in] success whether it should use \ref APDU_RESPONSE_OK
* @param[in] off payload offset (0 if no data other than status word)
*/
static void response_to_domain_name(bool success, uint8_t off) {
uint16_t sw;
if (success) {
sw = APDU_RESPONSE_OK;
} else {
sw = apdu_response_code;
}
U2BE_ENCODE(G_io_apdu_buffer, off, sw);
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, off + 2);
}
/**
* Checks if a domain name for the given chain ID and address is known
*
* Always wipes the content of \ref g_domain_name_info
*
* @param[in] chain_id given chain ID
* @param[in] addr given address
* @return whether there is or not
*/
bool has_domain_name(const uint64_t *chain_id, const uint8_t *addr) {
bool ret = false;
if (g_domain_name_info.valid) {
// TODO: Remove once other domain name providers are supported
if ((*chain_id == ETHEREUM_MAINNET_CHAINID) &&
(memcmp(addr, g_domain_name_info.addr, ADDRESS_LENGTH) == 0)) {
ret = true;
}
}
memset(&g_domain_name_info, 0, sizeof(g_domain_name_info));
return ret;
}
/**
* Get uint from tlv data
*
* Get an unsigned integer from variable length tlv data (up to 4 bytes)
*
* @param[in] data tlv data
* @param[out] value the returned value
* @return whether it was successful
*/
static bool get_uint_from_data(const s_tlv_data *data, uint32_t *value) {
uint8_t size_diff;
uint8_t buffer[sizeof(uint32_t)];
if (data->length > sizeof(buffer)) {
PRINTF("Unexpectedly long value (%u bytes) for tag 0x%x\n", data->length, data->tag);
return false;
}
size_diff = sizeof(buffer) - data->length;
memset(buffer, 0, size_diff);
memcpy(buffer + size_diff, data->value, data->length);
*value = U4BE(buffer, 0);
return true;
}
/**
* Handler for tag \ref STRUCTURE_TYPE
*
* @param[] data the tlv data
* @param[] domain_name_info the domain name information
* @param[] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_structure_type(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
(void) data;
(void) domain_name_info;
(void) sig_ctx;
return true; // unhandled for now
}
/**
* Handler for tag \ref STRUCTURE_VERSION
*
* @param[] data the tlv data
* @param[] domain_name_info the domain name information
* @param[] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_structure_version(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
(void) data;
(void) domain_name_info;
(void) sig_ctx;
return true; // unhandled for now
}
/**
* Handler for tag \ref CHALLENGE
*
* @param[in] data the tlv data
* @param[] domain_name_info the domain name information
* @param[] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_challenge(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
uint32_t value;
(void) domain_name_info;
(void) sig_ctx;
return get_uint_from_data(data, &value) && (value == get_challenge());
}
/**
* Handler for tag \ref SIGNER_KEY_ID
*
* @param[in] data the tlv data
* @param[] domain_name_info the domain name information
* @param[out] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_sign_key_id(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
uint32_t value;
(void) domain_name_info;
if (!get_uint_from_data(data, &value) || (value > UINT8_MAX)) {
return false;
}
sig_ctx->key_id = value;
return true;
}
/**
* Handler for tag \ref SIGNER_ALGO
*
* @param[in] data the tlv data
* @param[] domain_name_info the domain name information
* @param[] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_sign_algo(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
uint32_t value;
(void) domain_name_info;
(void) sig_ctx;
return get_uint_from_data(data, &value) && (value == ALGO_SECP256K1);
}
/**
* Handler for tag \ref SIGNATURE
*
* @param[in] data the tlv data
* @param[] domain_name_info the domain name information
* @param[out] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_signature(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
(void) domain_name_info;
sig_ctx->input_sig_size = data->length;
sig_ctx->input_sig = data->value;
return true;
}
/**
* Tests if the given domain name character is valid (in our subset of allowed characters)
*
* @param[in] c given character
* @return whether the character is valid
*/
static bool is_valid_domain_character(char c) {
if (isalpha((int) c)) {
if (!islower((int) c)) {
return false;
}
} else if (!isdigit((int) c)) {
switch (c) {
case '.':
case '-':
case '_':
break;
default:
return false;
}
}
return true;
}
/**
* Handler for tag \ref DOMAIN_NAME
*
* @param[in] data the tlv data
* @param[out] domain_name_info the domain name information
* @param[] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_domain_name(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
(void) sig_ctx;
if (data->length > DOMAIN_NAME_MAX_LENGTH) {
PRINTF("Domain name too long! (%u)\n", data->length);
return false;
}
// TODO: Remove once other domain name providers are supported
if ((data->length < 5) || (strncmp(".eth", (char *) &data->value[data->length - 4], 4) != 0)) {
PRINTF("Unexpected TLD!\n");
return false;
}
for (int idx = 0; idx < data->length; ++idx) {
if (!is_valid_domain_character(data->value[idx])) {
PRINTF("Domain name contains non-allowed character! (0x%x)\n", data->value[idx]);
return false;
}
domain_name_info->name[idx] = data->value[idx];
}
domain_name_info->name[data->length] = '\0';
return true;
}
/**
* Handler for tag \ref COIN_TYPE
*
* @param[in] data the tlv data
* @param[] domain_name_info the domain name information
* @param[] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_coin_type(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
uint32_t value;
(void) domain_name_info;
(void) sig_ctx;
return get_uint_from_data(data, &value) && (value == SLIP_44_ETHEREUM);
}
/**
* Handler for tag \ref ADDRESS
*
* @param[in] data the tlv data
* @param[out] domain_name_info the domain name information
* @param[] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_address(const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
(void) sig_ctx;
if (data->length != ADDRESS_LENGTH) {
return false;
}
memcpy(domain_name_info->addr, data->value, ADDRESS_LENGTH);
return true;
}
/**
* Verify the signature context
*
* Verify the SHA-256 hash of the payload against the public key
*
* @param[in] sig_ctx the signature context
* @return whether it was successful
*/
static bool verify_signature(const s_sig_ctx *sig_ctx) {
uint8_t hash[INT256_LENGTH];
cx_ecfp_public_key_t verif_key;
cx_hash((cx_hash_t *) &sig_ctx->hash_ctx, CX_LAST, NULL, 0, hash, INT256_LENGTH);
switch (sig_ctx->key_id) {
#ifdef HAVE_DOMAIN_NAME_TEST_KEY
case KEY_ID_TEST:
#else
case KEY_ID_PROD:
#endif
cx_ecfp_init_public_key(CX_CURVE_256K1,
DOMAIN_NAME_PUB_KEY,
sizeof(DOMAIN_NAME_PUB_KEY),
&verif_key);
break;
default:
PRINTF("Error: Unknown metadata key ID %u\n", sig_ctx->key_id);
return false;
}
if (!cx_ecdsa_verify(&verif_key,
CX_LAST,
CX_SHA256,
hash,
sizeof(hash),
sig_ctx->input_sig,
sig_ctx->input_sig_size)) {
PRINTF("Domain name signature verification failed!\n");
#ifndef HAVE_BYPASS_SIGNATURES
return false;
#endif
}
return true;
}
/**
* Calls the proper handler for the given TLV data
*
* Checks if there is a proper handler function for the given TLV tag and then calls it
*
* @param[in] handlers list of tag / handler function pairs
* @param[in] handler_count number of handlers
* @param[in] data the TLV data
* @param[out] domain_name_info the domain name information
* @param[out] sig_ctx the signature context
* @return whether it was successful
*/
static bool handle_tlv_data(s_tlv_handler *handlers,
int handler_count,
const s_tlv_data *data,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
t_tlv_handler *fptr;
// check if a handler exists for this tag
for (int idx = 0; idx < handler_count; ++idx) {
if (handlers[idx].tag == data->tag) {
handlers[idx].found += 1;
fptr = PIC(handlers[idx].func);
if (!(*fptr)(data, domain_name_info, sig_ctx)) {
PRINTF("Error while handling tag 0x%x\n", handlers[idx].tag);
return false;
}
break;
}
}
return true;
}
/**
* Checks if all the TLV tags were found during parsing
*
* @param[in,out] handlers list of tag / handler function pairs
* @param[in] handler_count number of handlers
* @return whether all tags were found
*/
static bool check_found_tlv_tags(s_tlv_handler *handlers, int handler_count) {
// prevent missing or duplicated tags
for (int idx = 0; idx < handler_count; ++idx) {
if (handlers[idx].found != 1) {
PRINTF("Found %u occurence(s) of tag 0x%x in TLV!\n",
handlers[idx].found,
handlers[idx].tag);
return false;
}
}
return true;
}
/** Parse DER-encoded value
*
* Parses a DER-encoded value (up to 4 bytes long)
* https://en.wikipedia.org/wiki/X.690
*
* @param[in] payload the TLV payload
* @param[in,out] offset the payload offset
* @param[out] value the parsed value
* @return whether it was successful
*/
static bool parse_der_value(const s_tlv_payload *payload, size_t *offset, uint32_t *value) {
bool ret = false;
uint8_t byte_length;
uint8_t buf[sizeof(*value)];
if (value != NULL) {
if (payload->buf[*offset] & DER_LONG_FORM_FLAG) { // long form
byte_length = payload->buf[*offset] & DER_FIRST_BYTE_VALUE_MASK;
*offset += 1;
if ((*offset + byte_length) > payload->size) {
PRINTF("TLV payload too small for DER encoded value\n");
} else {
if (byte_length > sizeof(buf)) {
PRINTF("Unexpectedly long DER-encoded value (%u bytes)\n", byte_length);
} else {
memset(buf, 0, (sizeof(buf) - byte_length));
memcpy(buf + (sizeof(buf) - byte_length), &payload->buf[*offset], byte_length);
*value = U4BE(buf, 0);
*offset += byte_length;
ret = true;
}
}
} else { // short form
*value = payload->buf[*offset];
*offset += 1;
ret = true;
}
}
return ret;
}
/**
* Get DER-encoded value as an uint8
*
* Parses the value and checks if it fits in the given \ref uint8_t value
*
* @param[in] payload the TLV payload
* @param[in,out] offset
* @param[out] value the parsed value
* @return whether it was successful
*/
static bool get_der_value_as_uint8(const s_tlv_payload *payload, size_t *offset, uint8_t *value) {
bool ret = false;
uint32_t tmp_value;
if (value != NULL) {
if (!parse_der_value(payload, offset, &tmp_value)) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
} else {
if (tmp_value <= UINT8_MAX) {
*value = tmp_value;
ret = true;
} else {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
PRINTF("TLV DER-encoded value larger than 8 bits\n");
}
}
}
return ret;
}
/**
* Parse the TLV payload
*
* Does the TLV parsing but also the SHA-256 hash of the payload.
*
* @param[in] payload the raw TLV payload
* @param[out] domain_name_info the domain name information
* @param[out] sig_ctx the signature context
* @return whether it was successful
*/
static bool parse_tlv(const s_tlv_payload *payload,
s_domain_name_info *domain_name_info,
s_sig_ctx *sig_ctx) {
s_tlv_handler handlers[] = {
{.tag = STRUCTURE_TYPE, .func = &handle_structure_type, .found = 0},
{.tag = STRUCTURE_VERSION, .func = &handle_structure_version, .found = 0},
{.tag = CHALLENGE, .func = &handle_challenge, .found = 0},
{.tag = SIGNER_KEY_ID, .func = &handle_sign_key_id, .found = 0},
{.tag = SIGNER_ALGO, .func = &handle_sign_algo, .found = 0},
{.tag = SIGNATURE, .func = &handle_signature, .found = 0},
{.tag = DOMAIN_NAME, .func = &handle_domain_name, .found = 0},
{.tag = COIN_TYPE, .func = &handle_coin_type, .found = 0},
{.tag = ADDRESS, .func = &handle_address, .found = 0}};
e_tlv_step step = TLV_TAG;
s_tlv_data data;
size_t offset = 0;
size_t tag_start_off;
cx_sha256_init(&sig_ctx->hash_ctx);
// handle TLV payload
while (offset < payload->size) {
switch (step) {
case TLV_TAG:
tag_start_off = offset;
if (!get_der_value_as_uint8(payload, &offset, &data.tag)) {
return false;
}
step = TLV_LENGTH;
break;
case TLV_LENGTH:
if (!get_der_value_as_uint8(payload, &offset, &data.length)) {
return false;
}
step = TLV_VALUE;
break;
case TLV_VALUE:
if (offset >= payload->size) {
return false;
}
data.value = &payload->buf[offset];
if (!handle_tlv_data(handlers,
ARRAY_SIZE(handlers),
&data,
domain_name_info,
sig_ctx)) {
return false;
}
offset += data.length;
if (data.tag != SIGNATURE) { // the signature wasn't computed on itself
hash_nbytes(&payload->buf[tag_start_off],
(offset - tag_start_off),
(cx_hash_t *) &sig_ctx->hash_ctx);
}
step = TLV_TAG;
break;
default:
return false;
}
}
return check_found_tlv_tags(handlers, ARRAY_SIZE(handlers));
}
/**
* Allocate and assign TLV payload
*
* @param[in] payload payload structure
* @param[in] size size of the payload
* @return whether it was successful
*/
static bool alloc_payload(s_tlv_payload *payload, uint16_t size) {
if ((payload->buf = mem_alloc(size)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
payload->expected_size = size;
return true;
}
/**
* Deallocate and unassign TLV payload
*
* @param[in] payload payload structure
*/
static void free_payload(s_tlv_payload *payload) {
mem_dealloc(payload->expected_size);
memset(payload, 0, sizeof(*payload));
}
static bool handle_first_chunk(const uint8_t **data, uint8_t *length, s_tlv_payload *payload) {
// check if no payload is already in memory
if (payload->buf != NULL) {
free_payload(payload);
apdu_response_code = APDU_RESPONSE_INVALID_P1_P2;
return false;
}
// check if we at least get the size
if (*length < sizeof(payload->expected_size)) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if (!alloc_payload(payload, U2BE(*data, 0))) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
// skip the size so we can process it like a following chunk
*data += sizeof(payload->expected_size);
*length -= sizeof(payload->expected_size);
return true;
}
/**
* Handle domain name APDU
*
* @param[in] p1 first APDU instruction parameter
* @param[in] p2 second APDU instruction parameter
* @param[in] data APDU payload
* @param[in] length payload size
*/
void handle_provide_domain_name(uint8_t p1, uint8_t p2, const uint8_t *data, uint8_t length) {
s_sig_ctx sig_ctx;
(void) p2;
if (p1 == P1_FIRST_CHUNK) {
if (!handle_first_chunk(&data, &length, &g_tlv_payload)) {
return response_to_domain_name(false, 0);
}
} else {
// check if a payload is already in memory
if (g_tlv_payload.buf == NULL) {
apdu_response_code = APDU_RESPONSE_INVALID_P1_P2;
return response_to_domain_name(false, 0);
}
}
if ((g_tlv_payload.size + length) > g_tlv_payload.expected_size) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
free_payload(&g_tlv_payload);
PRINTF("TLV payload size mismatch!\n");
return response_to_domain_name(false, 0);
}
// feed into tlv payload
memcpy(g_tlv_payload.buf + g_tlv_payload.size, data, length);
g_tlv_payload.size += length;
// everything has been received
if (g_tlv_payload.size == g_tlv_payload.expected_size) {
g_domain_name_info.name = g_domain_name;
if (!parse_tlv(&g_tlv_payload, &g_domain_name_info, &sig_ctx) ||
!verify_signature(&sig_ctx)) {
free_payload(&g_tlv_payload);
roll_challenge(); // prevent brute-force guesses
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return response_to_domain_name(false, 0);
}
g_domain_name_info.valid = true;
PRINTF("Registered : %s => %.*h\n",
g_domain_name_info.name,
ADDRESS_LENGTH,
g_domain_name_info.addr);
free_payload(&g_tlv_payload);
roll_challenge(); // prevent replays
}
return response_to_domain_name(true, 0);
}
#endif // HAVE_DOMAIN_NAME

View File

@@ -0,0 +1,18 @@
#ifdef HAVE_DOMAIN_NAME
#ifndef DOMAIN_NAME_H_
#define DOMAIN_NAME_H_
#include <stdint.h>
#include <stdbool.h>
#define DOMAIN_NAME_MAX_LENGTH 30
bool has_domain_name(const uint64_t *chain_id, const uint8_t *addr);
void handle_provide_domain_name(uint8_t p1, uint8_t p2, const uint8_t *data, uint8_t length);
extern char g_domain_name[DOMAIN_NAME_MAX_LENGTH + 1];
#endif // DOMAIN_NAME_H_
#endif // HAVE_DOMAIN_NAME

View File

@@ -42,8 +42,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
}
cx_hash((cx_hash_t *) &sha256, 0, workBuffer + offset, tickerLength, NULL, 0);
memmove(token->ticker, workBuffer + offset, tickerLength);
token->ticker[tickerLength] = ' ';
token->ticker[tickerLength + 1] = '\0';
token->ticker[tickerLength] = '\0';
offset += tickerLength;
dataLength -= tickerLength;
@@ -136,8 +135,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
}
cx_hash_sha256(workBuffer + offset, tickerLength + 20 + 4 + 4, hash, 32);
memmove(token->ticker, workBuffer + offset, tickerLength);
token->ticker[tickerLength] = ' ';
token->ticker[tickerLength + 1] = '\0';
token->ticker[tickerLength] = '\0';
offset += tickerLength;
dataLength -= tickerLength;
memmove(token->address, workBuffer + offset, 20);

View File

@@ -21,6 +21,9 @@ static const char SIGN_MAGIC[] =
* @param[in] sw status word
*/
static void apdu_reply(uint16_t sw) {
if ((sw != APDU_RESPONSE_OK) && states.ui_started) {
ui_idle();
}
G_io_apdu_buffer[0] = (sw >> 8) & 0xff;
G_io_apdu_buffer[1] = sw & 0xff;
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
@@ -90,18 +93,18 @@ static void reset_ui_buffer(void) {
*/
static const uint8_t *first_apdu_data(const uint8_t *data, uint8_t *length) {
if (appState != APP_STATE_IDLE) {
reset_app_context();
apdu_reply(APDU_RESPONSE_CONDITION_NOT_SATISFIED);
}
appState = APP_STATE_SIGNING_MESSAGE;
data = parseBip32(data, length, &tmpCtx.messageSigningContext.bip32);
if (data == NULL) {
apdu_reply(0x6a80);
apdu_reply(APDU_RESPONSE_INVALID_DATA);
return NULL;
}
if (*length < sizeof(uint32_t)) {
PRINTF("Invalid data\n");
apdu_reply(0x6a80);
apdu_reply(APDU_RESPONSE_INVALID_DATA);
return NULL;
}
@@ -140,7 +143,7 @@ static bool feed_hash(const uint8_t *const data, const uint8_t length) {
PRINTF("Error: Length mismatch ! (%u > %u)!\n",
length,
tmpCtx.messageSigningContext.remainingLength);
apdu_reply(0x6a80);
apdu_reply(APDU_RESPONSE_INVALID_DATA);
return false;
}
cx_hash((cx_hash_t *) &global_sha3, 0, data, length, NULL, 0);
@@ -194,7 +197,7 @@ static void feed_display(void) {
}
if ((unprocessed_length() == 0) && (tmpCtx.messageSigningContext.remainingLength > 0)) {
apdu_reply(0x9000);
apdu_reply(APDU_RESPONSE_OK);
}
}
@@ -222,7 +225,11 @@ bool handleSignPersonalMessage(uint8_t p1,
processed_size = data - payload;
} else if (p1 != P1_MORE) {
PRINTF("Error: Unexpected P1 (%u)!\n", p1);
apdu_reply(0x6B00);
apdu_reply(APDU_RESPONSE_INVALID_P1_P2);
return false;
} else if (appState != APP_STATE_SIGNING_MESSAGE) {
PRINTF("Error: App not already in signing state!\n");
apdu_reply(APDU_RESPONSE_INVALID_DATA);
return false;
}
@@ -241,7 +248,7 @@ bool handleSignPersonalMessage(uint8_t p1,
ui_191_switch_to_sign();
#endif
} else {
apdu_reply(0x9000);
apdu_reply(APDU_RESPONSE_OK);
}
}
return true;
@@ -266,7 +273,7 @@ void question_switcher(void) {
void skip_rest_of_message(void) {
states.sign_state = STATE_191_HASH_ONLY;
if (tmpCtx.messageSigningContext.remainingLength > 0) {
apdu_reply(0x9000);
apdu_reply(APDU_RESPONSE_OK);
} else {
ui_191_switch_to_sign();
}

View File

@@ -1,13 +0,0 @@
#ifndef HASH_BYTES_H_
#define HASH_BYTES_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include "cx.h"
void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *hash_ctx);
void hash_byte(uint8_t byte, cx_hash_t *hash_ctx);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // HASH_BYTES_H_

View File

@@ -115,10 +115,12 @@ void ui_712_redraw_generic_step(void) {
/**
* Called to fetch the next field if they have not all been processed yet
*
* @return whether there will be a next field
* Also handles the special "Review struct" screen of the verbose mode
*
* @return the next field state
*/
bool ui_712_next_field(void) {
bool next = false;
e_eip712_nfs ui_712_next_field(void) {
e_eip712_nfs state = EIP712_NO_MORE_FIELD;
if (ui_ctx == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
@@ -126,13 +128,13 @@ bool ui_712_next_field(void) {
if (ui_ctx->structs_to_review > 0) {
ui_712_review_struct(path_get_nth_field_to_last(ui_ctx->structs_to_review));
ui_ctx->structs_to_review -= 1;
}
if (!ui_ctx->end_reached) {
state = EIP712_FIELD_LATER;
} else if (!ui_ctx->end_reached) {
handle_eip712_return_code(true);
next = true;
state = EIP712_FIELD_INCOMING;
}
}
return next;
return state;
}
/**

View File

@@ -10,6 +10,11 @@
#define UI_712_FIELD_NAME_PROVIDED (1 << 1)
typedef enum { EIP712_FILTERING_BASIC, EIP712_FILTERING_FULL } e_eip712_filtering_mode;
typedef enum {
EIP712_FIELD_LATER,
EIP712_FIELD_INCOMING,
EIP712_NO_MORE_FIELD
} e_eip712_nfs; // next field state
typedef struct {
bool shown;
@@ -22,7 +27,7 @@ typedef struct {
bool ui_712_init(void);
void ui_712_deinit(void);
bool ui_712_next_field(void);
e_eip712_nfs ui_712_next_field(void);
void ui_712_review_struct(const void *const struct_ptr);
bool ui_712_new_field(const void *const field_ptr, const uint8_t *const data, uint8_t length);
void ui_712_end_sign(void);

View File

@@ -1,7 +1,7 @@
#include "shared_context.h"
#include "apdu_constants.h"
#include "utils.h"
#include "ui_flow.h"
#include "common_ui.h"
#include "common_712.h"
#include "ethUtils.h"
@@ -33,7 +33,7 @@ void handleSignEIP712Message_v0(uint8_t p1,
#ifdef NO_CONSENT
io_seproxyhal_touch_signMessage_ok(NULL);
#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

@@ -9,6 +9,7 @@
#include "ethUtils.h"
#include "common_ui.h"
#include "ui_callbacks.h"
#include <ctype.h>
#define ERR_SILENT_MODE_CHECK_FAILED 0x6001
@@ -170,26 +171,6 @@ customStatus_e customProcessor(txContext_t *context) {
return CUSTOM_NOT_HANDLED;
}
void to_uppercase(char *str, unsigned char size) {
for (unsigned char i = 0; i < size && str[i] != 0; i++) {
str[i] = str[i] >= 'a' ? str[i] - ('a' - 'A') : str[i];
}
}
void compareOrCopy(char *preapproved_string, size_t size, char *parsed_string, bool silent_mode) {
if (silent_mode) {
/* ETH address are not fundamentally case sensitive but might
have some for checksum purpose, so let's get rid of these diffs */
to_uppercase(preapproved_string, strlen(preapproved_string));
to_uppercase(parsed_string, strlen(parsed_string));
if (memcmp(preapproved_string, parsed_string, strlen(preapproved_string))) {
THROW(ERR_SILENT_MODE_CHECK_FAILED);
}
} else {
strlcpy(preapproved_string, parsed_string, size);
}
}
void reportFinalizeError(bool direct) {
reset_app_context();
if (direct) {
@@ -200,20 +181,20 @@ void reportFinalizeError(bool direct) {
}
}
// Convert `BEgasPrice` and `BEgasLimit` to Uint256 and then stores the multiplication of both in
// `output`.
static void computeFees(txInt256_t *BEgasPrice, txInt256_t *BEgasLimit, uint256_t *output) {
uint256_t gasPrice = {0};
uint256_t gasLimit = {0};
PRINTF("Gas price %.*H\n", BEgasPrice->length, BEgasPrice->value);
PRINTF("Gas limit %.*H\n", BEgasLimit->length, BEgasLimit->value);
convertUint256BE(BEgasPrice->value, BEgasPrice->length, &gasPrice);
convertUint256BE(BEgasLimit->value, BEgasLimit->length, &gasLimit);
mul256(&gasPrice, &gasLimit, output);
static void address_to_string(uint8_t *in,
size_t in_len,
char *out,
size_t out_len,
cx_sha3_t *sha3,
uint64_t chainId) {
if (in_len != 0) {
getEthDisplayableAddress(in, out, out_len, sha3, chainId);
} else {
strlcpy(out, "Contract", out_len);
}
}
static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) {
static void raw_fee_to_string(uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) {
const char *feeTicker = get_network_ticker();
uint8_t tickerOffset = 0;
uint32_t i;
@@ -240,6 +221,7 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
displayBuffer[tickerOffset] = feeTicker[tickerOffset];
tickerOffset++;
}
if ((uint32_t) tickerOffset < displayBufferSize) displayBuffer[tickerOffset++] = ' ';
while (G_io_apdu_buffer[i]) {
if ((uint32_t) (tickerOffset) + i >= displayBufferSize) {
break;
@@ -254,32 +236,40 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
}
// Compute the fees, transform it to a string, prepend a ticker to it and copy everything to
// `displayBuffer`.
void prepareAndCopyFees(txInt256_t *BEGasPrice,
txInt256_t *BEGasLimit,
char *displayBuffer,
uint32_t displayBufferSize) {
// `displayBuffer` output
static void max_transaction_fee_to_string(const txInt256_t *BEGasPrice,
const txInt256_t *BEGasLimit,
char *displayBuffer,
uint32_t displayBufferSize) {
// Use temporary variables to convert values to uint256_t
uint256_t gasPrice = {0};
uint256_t gasLimit = {0};
// Use temporary variable to store the result of the operation in uint256_t
uint256_t rawFee = {0};
computeFees(BEGasPrice, BEGasLimit, &rawFee);
feesToString(&rawFee, displayBuffer, displayBufferSize);
PRINTF("Gas price %.*H\n", BEGasPrice->length, BEGasPrice->value);
PRINTF("Gas limit %.*H\n", BEGasLimit->length, BEGasLimit->value);
convertUint256BE(BEGasPrice->value, BEGasPrice->length, &gasPrice);
convertUint256BE(BEGasLimit->value, BEGasLimit->length, &gasLimit);
mul256(&gasPrice, &gasLimit, &rawFee);
raw_fee_to_string(&rawFee, displayBuffer, displayBufferSize);
}
void prepareFeeDisplay() {
prepareAndCopyFees(&tmpContent.txContent.gasprice,
&tmpContent.txContent.startgas,
strings.common.maxFee,
sizeof(strings.common.maxFee));
static void nonce_to_string(const txInt256_t *nonce, char *out, size_t out_size) {
uint256_t nonce_uint256;
convertUint256BE(nonce->value, nonce->length, &nonce_uint256);
tostring256(&nonce_uint256, 10, out, out_size);
}
void prepareNetworkDisplay() {
static void get_network_as_string(char *out, size_t out_size) {
const char *name = get_network_name();
if (name == NULL) {
// No network name found so simply copy the chain ID as the network name.
uint64_t chain_id = get_chain_id();
u64_to_string(chain_id, strings.common.network_name, sizeof(strings.common.network_name));
u64_to_string(chain_id, out, out_size);
} else {
// Network name found, simply copy it.
strlcpy(strings.common.network_name, name, sizeof(strings.common.network_name));
strlcpy(out, name, out_size);
}
}
@@ -304,12 +294,27 @@ static void get_public_key(uint8_t *out, uint8_t outLength) {
getEthAddressFromKey(&publicKey, out, &global_sha3);
}
/* Local implmentation of strncasecmp, workaround of the segfaulting base implem
* Remove once strncasecmp is fixed
*/
static int strcasecmp_workaround(const char *str1, const char *str2) {
unsigned char c1, c2;
do {
c1 = *str1++;
c2 = *str2++;
if (toupper(c1) != toupper(c2)) {
return toupper(c1) - toupper(c2);
}
} while (c1 != '\0');
return 0;
}
void finalizeParsing(bool direct) {
char displayBuffer[50];
uint8_t decimals = WEI_TO_ETHER;
const char *ticker = get_network_ticker();
ethPluginFinalize_t pluginFinalize;
bool genericUI = true;
bool use_standard_UI = true;
// Verify the chain
if (chainConfig->chainId != ETHEREUM_MAINNET_CHAINID) {
@@ -334,7 +339,6 @@ void finalizeParsing(bool direct) {
// Finalize the plugin handling
if (dataContext.tokenContext.pluginStatus >= ETH_PLUGIN_RESULT_SUCCESSFUL) {
genericUI = false;
eth_plugin_prepare_finalize(&pluginFinalize);
uint8_t msg_sender[ADDRESS_LENGTH] = {0};
@@ -380,6 +384,8 @@ void finalizeParsing(bool direct) {
// Handle the right interface
switch (pluginFinalize.uiType) {
case ETH_UI_TYPE_GENERIC:
// Use the dedicated ETH plugin UI
use_standard_UI = false;
tmpContent.txContent.dataPresent = false;
// Add the number of screens + the number of additional screens to get the total
// number of screens needed.
@@ -387,7 +393,8 @@ void finalizeParsing(bool direct) {
pluginFinalize.numScreens + pluginProvideInfo.additionalScreens;
break;
case ETH_UI_TYPE_AMOUNT_ADDRESS:
genericUI = true;
// Use the standard ETH UI as this plugin uses the amount/address UI
use_standard_UI = true;
tmpContent.txContent.dataPresent = false;
if ((pluginFinalize.amount == NULL) || (pluginFinalize.address == NULL)) {
PRINTF("Incorrect amount/address set by plugin\n");
@@ -412,11 +419,15 @@ void finalizeParsing(bool direct) {
return;
}
}
} else {
genericUI = true;
}
}
// User has just validated a swap but ETH received apdus about a non standard plugin / contract
if (called_from_swap && !use_standard_UI) {
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, called_from_swap\n");
THROW(ERR_SILENT_MODE_CHECK_FAILED);
}
if (tmpContent.txContent.dataPresent && !N_storage.dataAllowed) {
reportFinalizeError(direct);
ui_warning_contract_data();
@@ -425,66 +436,94 @@ void finalizeParsing(bool direct) {
}
}
// Prepare destination address to display
if (genericUI) {
if (tmpContent.txContent.destinationLength != 0) {
getEthDisplayableAddress(tmpContent.txContent.destination,
displayBuffer,
sizeof(displayBuffer),
&global_sha3,
chainConfig->chainId);
compareOrCopy(strings.common.fullAddress,
sizeof(strings.common.fullAddress),
// Prepare destination address and amount to display
if (use_standard_UI) {
// Format the address in a temporary buffer, if in swap case compare it with validated
// address, else commit it
address_to_string(tmpContent.txContent.destination,
tmpContent.txContent.destinationLength,
displayBuffer,
called_from_swap);
sizeof(displayBuffer),
&global_sha3,
chainConfig->chainId);
if (called_from_swap) {
// Ensure the values are the same that the ones that have been previously validated
if (strcasecmp_workaround(strings.common.fullAddress, displayBuffer) != 0) {
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, address check failed\n");
THROW(ERR_SILENT_MODE_CHECK_FAILED);
}
} else {
strcpy(strings.common.fullAddress, "Contract");
strlcpy(strings.common.fullAddress, displayBuffer, sizeof(strings.common.fullAddress));
}
}
PRINTF("Address displayed: %s\n", strings.common.fullAddress);
// Prepare amount to display
if (genericUI) {
// Format the amount in a temporary buffer, if in swap case compare it with validated
// amount, else commit it
amountToString(tmpContent.txContent.value.value,
tmpContent.txContent.value.length,
decimals,
ticker,
displayBuffer,
sizeof(displayBuffer));
compareOrCopy(strings.common.fullAmount,
sizeof(strings.common.fullAmount),
displayBuffer,
called_from_swap);
if (called_from_swap) {
// Ensure the values are the same that the ones that have been previously validated
if (strcmp(strings.common.fullAmount, displayBuffer) != 0) {
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, amount check failed\n");
THROW(ERR_SILENT_MODE_CHECK_FAILED);
}
} else {
strlcpy(strings.common.fullAmount, displayBuffer, sizeof(strings.common.fullAmount));
}
PRINTF("Amount displayed: %s\n", strings.common.fullAmount);
}
// Prepare nonce to display
uint256_t nonce;
convertUint256BE(tmpContent.txContent.nonce.value, tmpContent.txContent.nonce.length, &nonce);
tostring256(&nonce, 10, displayBuffer, sizeof(displayBuffer));
strlcpy(strings.common.nonce, displayBuffer, sizeof(strings.common.nonce));
// Compute the max fee in a temporary buffer, if in swap case compare it with validated max fee,
// else commit it
max_transaction_fee_to_string(&tmpContent.txContent.gasprice,
&tmpContent.txContent.startgas,
displayBuffer,
sizeof(displayBuffer));
if (called_from_swap) {
// Ensure the values are the same that the ones that have been previously validated
if (strcmp(strings.common.maxFee, displayBuffer) != 0) {
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, fees check failed\n");
THROW(ERR_SILENT_MODE_CHECK_FAILED);
}
} else {
strlcpy(strings.common.maxFee, displayBuffer, sizeof(strings.common.maxFee));
}
// Compute maximum fee
prepareFeeDisplay();
PRINTF("Fees displayed: %s\n", strings.common.maxFee);
// Prepare nonce to display
nonce_to_string(&tmpContent.txContent.nonce,
strings.common.nonce,
sizeof(strings.common.nonce));
PRINTF("Nonce: %s\n", strings.common.nonce);
// Prepare chainID field
prepareNetworkDisplay();
get_network_as_string(strings.common.network_name, sizeof(strings.common.network_name));
PRINTF("Network: %s\n", strings.common.network_name);
bool no_consent;
bool no_consent_check;
no_consent = called_from_swap;
// If called from swap, the user as already validated a standard transaction
// We have already checked the fields of this transaction above
no_consent_check = called_from_swap && use_standard_UI;
#ifdef NO_CONSENT
no_consent = true;
no_consent_check = true;
#endif // NO_CONSENT
if (no_consent) {
if (no_consent_check) {
io_seproxyhal_touch_tx_ok(NULL);
} else {
if (genericUI) {
if (use_standard_UI) {
ux_approve_tx(false);
} else {
plugin_ui_start();
dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE;
dataContext.tokenContext.pluginUiCurrentItem = 0;
ux_approve_tx(true);
}
}
}

View File

@@ -326,10 +326,11 @@ void starkware_print_amount(uint8_t *amountData,
}
tostring256(&amount, 10, (char *) (G_io_apdu_buffer + 100), 100);
strlcpy(destination, ticker, destinationLength);
strlcat(destination, " ", destinationLength);
adjustDecimals((char *) (G_io_apdu_buffer + 100),
strlen((char *) (G_io_apdu_buffer + 100)),
destination + strlen(ticker),
destinationLength - strlen(ticker),
destination + strlen(ticker) + 1,
destinationLength - strlen(ticker) - 1,
decimals);
}

View File

@@ -1,2 +1,4 @@
venv/
__pycache__/
snapshots-tmp/
elfs/

View File

@@ -1,17 +1,48 @@
from enum import IntEnum, auto
from typing import Iterator, Dict, List
from typing import Optional
from ragger.backend import BackendInterface
from ragger.utils import RAPDU
from ethereum_client.command_builder import EthereumCmdBuilder
from ethereum_client.setting import SettingType, SettingImpl
from ethereum_client.eip712 import EIP712FieldType
from ethereum_client.response_parser import EthereumRespParser
from ragger.navigator import NavInsID, NavIns, NanoNavigator
from .command_builder import EthereumCmdBuilder
from .setting import SettingType, SettingImpl
from .eip712 import EIP712FieldType
from .response_parser import EthereumRespParser
from .tlv import format_tlv
import signal
import time
from pathlib import Path
import keychain
import rlp
class EthereumClient:
_settings: Dict[SettingType, SettingImpl] = {
ROOT_SCREENSHOT_PATH = Path(__file__).parent.parent
WEI_IN_ETH = 1e+18
class StatusWord(IntEnum):
OK = 0x9000
ERROR_NO_INFO = 0x6a00
INVALID_DATA = 0x6a80
INSUFFICIENT_MEMORY = 0x6a84
INVALID_INS = 0x6d00
INVALID_P1_P2 = 0x6b00
CONDITION_NOT_SATISFIED = 0x6985
REF_DATA_NOT_FOUND = 0x6a88
class DOMAIN_NAME_TAG(IntEnum):
STRUCTURE_TYPE = 0x01
STRUCTURE_VERSION = 0x02
CHALLENGE = 0x12
SIGNER_KEY_ID = 0x13
SIGNER_ALGO = 0x14
SIGNATURE = 0x15
DOMAIN_NAME = 0x20
COIN_TYPE = 0x21
ADDRESS = 0x22
class EthereumClient:
_settings: dict[SettingType, SettingImpl] = {
SettingType.BLIND_SIGNING: SettingImpl(
[ "nanos", "nanox", "nanosp" ]
),
@@ -23,15 +54,20 @@ class EthereumClient:
),
SettingType.VERBOSE_EIP712: SettingImpl(
[ "nanox", "nanosp" ]
),
SettingType.VERBOSE_ENS: SettingImpl(
[ "nanox", "nanosp" ]
)
}
_click_delay = 1/4
_eip712_filtering = False
def __init__(self, client: BackendInterface):
def __init__(self, client: BackendInterface, golden_run: bool):
self._client = client
self._chain_id = 1
self._cmd_builder = EthereumCmdBuilder()
self._resp_parser = EthereumRespParser()
self._nav = NanoNavigator(client, client.firmware, golden_run)
signal.signal(signal.SIGALRM, self._click_signal_timeout)
for setting in self._settings.values():
setting.value = False
@@ -65,11 +101,11 @@ class EthereumClient:
array_levels: [],
key_name: str):
with self._send(self._cmd_builder.eip712_send_struct_def_struct_field(
field_type,
type_name,
type_size,
array_levels,
key_name)):
field_type,
type_name,
type_size,
array_levels,
key_name)):
pass
return self._recv()
@@ -91,8 +127,8 @@ class EthereumClient:
self._disable_click_until_response()
assert self._recv().status == 0x9000
def eip712_sign_new(self, bip32):
with self._send(self._cmd_builder.eip712_sign_new(bip32)):
def eip712_sign_new(self, bip32_path: str):
with self._send(self._cmd_builder.eip712_sign_new(bip32_path)):
time.sleep(0.5) # tight on timing, needed by the CI otherwise might fail sometimes
if not self._settings[SettingType.VERBOSE_EIP712].value and \
not self._eip712_filtering: # need to skip the message hash
@@ -104,10 +140,10 @@ class EthereumClient:
return self._resp_parser.sign(resp.data)
def eip712_sign_legacy(self,
bip32,
bip32_path: str,
domain_hash: bytes,
message_hash: bytes):
with self._send(self._cmd_builder.eip712_sign_legacy(bip32,
with self._send(self._cmd_builder.eip712_sign_legacy(bip32_path,
domain_hash,
message_hash)):
self._client.right_click() # sign typed message screen
@@ -125,7 +161,7 @@ class EthereumClient:
assert resp.status == 0x9000
return self._resp_parser.sign(resp.data)
def settings_set(self, new_values: Dict[SettingType, bool]):
def settings_set(self, new_values: dict[SettingType, bool]):
# Go to settings
for _ in range(2):
self._client.right_click()
@@ -156,3 +192,61 @@ class EthereumClient:
with self._send(self._cmd_builder.eip712_filtering_show_field(name, sig)):
pass
assert self._recv().status == 0x9000
def send_fund(self,
bip32_path: str,
nonce: int,
gas_price: int,
gas_limit: int,
to: bytes,
amount: float,
chain_id: int,
screenshot_collection: str = None):
data = list()
data.append(nonce)
data.append(gas_price)
data.append(gas_limit)
data.append(to)
data.append(int(amount * WEI_IN_ETH))
data.append(bytes())
data.append(chain_id)
data.append(bytes())
data.append(bytes())
for chunk in self._cmd_builder.sign(bip32_path, rlp.encode(data)):
with self._send(chunk):
nav_ins = NavIns(NavInsID.RIGHT_CLICK)
final_ins = [ NavIns(NavInsID.BOTH_CLICK) ]
target_text = "and send"
if screenshot_collection:
self._nav.navigate_until_text_and_compare(nav_ins,
final_ins,
target_text,
ROOT_SCREENSHOT_PATH,
screenshot_collection)
else:
self._nav.navigate_until_text(nav_ins,
final_ins,
target_text)
def get_challenge(self) -> int:
with self._send(self._cmd_builder.get_challenge()):
pass
resp = self._recv()
return self._resp_parser.challenge(resp.data)
def provide_domain_name(self, challenge: int, name: str, addr: bytes):
payload = format_tlv(DOMAIN_NAME_TAG.STRUCTURE_TYPE, 3) # TrustedDomainName
payload += format_tlv(DOMAIN_NAME_TAG.STRUCTURE_VERSION, 1)
payload += format_tlv(DOMAIN_NAME_TAG.SIGNER_KEY_ID, 0) # test key
payload += format_tlv(DOMAIN_NAME_TAG.SIGNER_ALGO, 1) # secp256k1
payload += format_tlv(DOMAIN_NAME_TAG.CHALLENGE, challenge)
payload += format_tlv(DOMAIN_NAME_TAG.COIN_TYPE, 0x3c) # ETH in slip-44
payload += format_tlv(DOMAIN_NAME_TAG.DOMAIN_NAME, name)
payload += format_tlv(DOMAIN_NAME_TAG.ADDRESS, addr)
payload += format_tlv(DOMAIN_NAME_TAG.SIGNATURE,
keychain.sign_data(keychain.Key.DOMAIN_NAME, payload))
for chunk in self._cmd_builder.provide_domain_name(payload):
with self._send(chunk):
pass

View File

@@ -1,18 +1,25 @@
from enum import IntEnum, auto
from typing import Iterator
from ethereum_client.eip712 import EIP712FieldType
from typing import Iterator, Optional
from .eip712 import EIP712FieldType
from ragger.bip import pack_derivation_path
import struct
class InsType(IntEnum):
class InsType(IntEnum):
SIGN = 0x04
EIP712_SEND_STRUCT_DEF = 0x1a
EIP712_SEND_STRUCT_IMPL = 0x1c
EIP712_SEND_FILTERING = 0x1e
EIP712_SIGN = 0x0c
GET_CHALLENGE = 0x20
PROVIDE_DOMAIN_NAME = 0x22
class P1Type(IntEnum):
class P1Type(IntEnum):
COMPLETE_SEND = 0x00
PARTIAL_SEND = 0x01
SIGN_FIRST_CHUNK = 0x00
SIGN_SUBSQT_CHUNK = 0x80
class P2Type(IntEnum):
class P2Type(IntEnum):
STRUCT_NAME = 0x00
STRUCT_FIELD = 0xff
ARRAY = 0x0f
@@ -22,14 +29,14 @@ class P2Type(IntEnum):
FILTERING_CONTRACT_NAME = 0x0f
FILTERING_FIELD_NAME = 0xff
class EthereumCmdBuilder:
class EthereumCmdBuilder:
_CLA: int = 0xE0
def _serialize(self,
ins: InsType,
p1: int,
p2: int,
cdata: bytearray = bytearray()) -> bytes:
cdata: bytearray = bytes()) -> bytes:
header = bytearray()
header.append(self._CLA)
@@ -109,27 +116,18 @@ class EthereumCmdBuilder:
data_w_length[:0xff])
data_w_length = data_w_length[0xff:]
def _format_bip32(self, bip32, data: bytearray) -> bytearray:
data.append(len(bip32))
for idx in bip32:
data.append((idx & 0xff000000) >> 24)
data.append((idx & 0x00ff0000) >> 16)
data.append((idx & 0x0000ff00) >> 8)
data.append((idx & 0x000000ff))
return data
def eip712_sign_new(self, bip32) -> bytes:
data = self._format_bip32(bip32, bytearray())
def eip712_sign_new(self, bip32_path: str) -> bytes:
data = pack_derivation_path(bip32_path)
return self._serialize(InsType.EIP712_SIGN,
P1Type.COMPLETE_SEND,
P2Type.NEW_IMPLEM,
data)
def eip712_sign_legacy(self,
bip32,
bip32_path: str,
domain_hash: bytes,
message_hash: bytes) -> bytes:
data = self._format_bip32(bip32, bytearray())
data = pack_derivation_path(bip32_path)
data += domain_hash
data += message_hash
return self._serialize(InsType.EIP712_SIGN,
@@ -168,3 +166,30 @@ class EthereumCmdBuilder:
P1Type.COMPLETE_SEND,
P2Type.FILTERING_FIELD_NAME,
self._eip712_filtering_send_name(name, sig))
def sign(self, bip32_path: str, rlp_data: bytes) -> Iterator[bytes]:
payload = pack_derivation_path(bip32_path)
payload += rlp_data
p1 = P1Type.SIGN_FIRST_CHUNK
while len(payload) > 0:
yield self._serialize(InsType.SIGN,
p1,
0x00,
payload[:0xff])
payload = payload[0xff:]
p1 = P1Type.SIGN_SUBSQT_CHUNK
def get_challenge(self) -> bytes:
return self._serialize(InsType.GET_CHALLENGE, 0x00, 0x00)
def provide_domain_name(self, tlv_payload: bytes) -> bytes:
payload = struct.pack(">H", len(tlv_payload))
payload += tlv_payload
p1 = 1
while len(payload) > 0:
yield self._serialize(InsType.PROVIDE_DOMAIN_NAME,
p1,
0x00,
payload[:0xff])
payload = payload[0xff:]
p1 = 0

View File

@@ -1,6 +1,6 @@
from enum import IntEnum, auto
class EIP712FieldType(IntEnum):
class EIP712FieldType(IntEnum):
CUSTOM = 0,
INT = auto()
UINT = auto()

View File

@@ -1,4 +1,4 @@
class EthereumRespParser:
class EthereumRespParser:
def sign(self, data: bytes):
assert len(data) == (1 + 32 + 32)
@@ -12,3 +12,7 @@ class EthereumRespParser:
data = data[32:]
return v, r, s
def challenge(self, data: bytes) -> int:
assert len(data) == 4
return int.from_bytes(data, "big")

View File

@@ -1,13 +1,14 @@
from enum import IntEnum, auto
from typing import List
class SettingType(IntEnum):
class SettingType(IntEnum):
BLIND_SIGNING = 0,
DEBUG_DATA = auto()
NONCE = auto()
VERBOSE_EIP712 = auto()
VERBOSE_ENS = auto()
class SettingImpl:
class SettingImpl:
devices: List[str]
value: bool

25
tests/ragger/app/tlv.py Normal file
View File

@@ -0,0 +1,25 @@
from typing import Any
def der_encode(value: int) -> bytes:
# max() to have minimum length of 1
value_bytes = value.to_bytes(max(1, (value.bit_length() + 7) // 8), 'big')
if value >= 0x80:
value_bytes = (0x80 | len(value_bytes)).to_bytes(1, 'big') + value_bytes
return value_bytes
def format_tlv(tag: int, value: Any) -> bytes:
if isinstance(value, int):
# max() to have minimum length of 1
value = value.to_bytes(max(1, (value.bit_length() + 7) // 8), 'big')
elif isinstance(value, str):
value = value.encode()
if not isinstance(value, bytes):
print("Unhandled TLV formatting for type : %s" % (type(value)))
return None
tlv = bytearray()
tlv += der_encode(tag)
tlv += der_encode(len(value))
tlv += value
return tlv

View File

@@ -1,68 +1,12 @@
import pytest
from pathlib import Path
from ragger import Firmware
from ragger.backend import SpeculosBackend, LedgerCommBackend, LedgerWalletBackend, BackendInterface
from ethereum_client.client import EthereumClient
FWS = [
Firmware("nanos", "2.1"),
Firmware("nanox", "2.0.2"),
Firmware("nanosp", "1.0.3")
]
def pytest_addoption(parser):
parser.addoption("--backend", action="store", default="speculos")
parser.addoption("--path", action="store", default="./elfs")
parser.addoption("--model", action="store", required=True)
# accessing the value of the "--backend" option as a fixture
@pytest.fixture
def arg_backend(pytestconfig) -> str:
return pytestconfig.getoption("backend")
@pytest.fixture
def arg_path(pytestconfig) -> str:
return pytestconfig.getoption("path")
@pytest.fixture
def arg_model(pytestconfig) -> str:
return pytestconfig.getoption("model")
# Providing the firmware as a fixture
@pytest.fixture
def firmware(arg_model: str) -> Firmware:
for fw in FWS:
if fw.device == arg_model:
return fw
raise ValueError("Unknown device model \"%s\"" % (arg_model))
def get_elf_path(arg_path: str, firmware: Firmware) -> Path:
elf_dir = Path(arg_path).resolve()
assert elf_dir.is_dir(), ("%s is not a directory" % (arg_path))
app = elf_dir / ("app-%s.elf" % firmware.device)
assert app.is_file(), ("Firmware %s does not exist !" % (app))
return app
# Depending on the "--backend" option value, a different backend is
# instantiated, and the tests will either run on Speculos or on a physical
# device depending on the backend
def create_backend(backend: str, arg_path: str, firmware: Firmware) -> BackendInterface:
if backend.lower() == "ledgercomm":
return LedgerCommBackend(firmware, interface="hid")
elif backend.lower() == "ledgerwallet":
return LedgerWalletBackend(firmware)
elif backend.lower() == "speculos":
return SpeculosBackend(get_elf_path(arg_path, firmware), firmware)
else:
raise ValueError(f"Backend '{backend}' is unknown. Valid backends are: {BACKENDS}")
# This fixture will create and return the backend client
@pytest.fixture
def backend_client(arg_backend: str, arg_path: str, firmware: Firmware) -> BackendInterface:
with create_backend(arg_backend, arg_path, firmware) as b:
yield b
from ragger.conftest import configuration
from ragger.backend import BackendInterface
from app.client import EthereumClient
# This final fixture will return the properly configured app client, to be used in tests
@pytest.fixture
def app_client(backend_client: BackendInterface) -> EthereumClient:
return EthereumClient(backend_client)
def app_client(backend: BackendInterface, golden_run: bool) -> EthereumClient:
return EthereumClient(backend, golden_run)
# Pull all features from the base ragger conftest using the overridden configuration
pytest_plugins = ("ragger.conftest.base_conftest", )

View File

@@ -1,14 +1,11 @@
#!/usr/bin/env python3
import os
import json
import sys
import re
import hashlib
from ecdsa import SigningKey
from ecdsa.util import sigencode_der
from ethereum_client.client import EthereumClient, EIP712FieldType
import base64
from app.client import EthereumClient, EIP712FieldType
import keychain
# global variables
app_client: EthereumClient = None
@@ -24,7 +21,7 @@ sig_ctx = {}
# Output = ('uint8', [2, None, 4]) | ('bool', [])
def get_array_levels(typename):
array_lvls = list()
regex = re.compile("(.*)\[([0-9]*)\]$")
regex = re.compile(r"(.*)\[([0-9]*)\]$")
while True:
result = regex.search(typename)
@@ -45,7 +42,7 @@ def get_array_levels(typename):
# Input = "uint64" | "string"
# Output = ('uint', 64) | ('string', None)
def get_typesize(typename):
regex = re.compile("^(\w+?)(\d*)$")
regex = re.compile(r"^(\w+?)(\d*)$")
result = regex.search(typename)
typename = result.group(1)
typesize = result.group(2)
@@ -254,7 +251,7 @@ def send_filtering_message_info(display_name: str, filters_count: int):
for char in display_name:
to_sign.append(ord(char))
sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der)
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
app_client.eip712_filtering_message_info(display_name, filters_count, sig)
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
@@ -272,7 +269,7 @@ def send_filtering_show_field(display_name):
to_sign.append(ord(char))
for char in display_name:
to_sign.append(ord(char))
sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der)
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
app_client.eip712_filtering_show_field(display_name, sig)
def read_filtering_file(domain, message, filtering_file_path):
@@ -299,9 +296,6 @@ def init_signature_context(types, domain):
global sig_ctx
handle_optional_domain_values(domain)
env_key = os.environ["CAL_SIGNATURE_TEST_KEY"]
key = base64.b64decode(env_key).decode() # base 64 string -> decode bytes -> string
sig_ctx["key"] = SigningKey.from_pem(key, hashlib.sha256)
caddr = domain["verifyingContract"]
if caddr.startswith("0x"):
caddr = caddr[2:]

27
tests/ragger/keychain.py Normal file
View File

@@ -0,0 +1,27 @@
import os
import hashlib
from ecdsa.util import sigencode_der
from ecdsa import SigningKey
from enum import Enum, auto
# Private key PEM files have to be named the same (lowercase) as their corresponding enum entries
# Example: for an entry in the Enum named DEV, its PEM file must be at keychain/dev.pem
class Key(Enum):
CAL = auto()
DOMAIN_NAME = auto()
_keys: dict[Key, SigningKey] = dict()
# Open the corresponding PEM file and load its key in the global dict
def _init_key(key: Key):
global _keys
with open("%s/keychain/%s.pem" % (os.path.dirname(__file__), key.name.lower())) as pem_file:
_keys[key] = SigningKey.from_pem(pem_file.read(), hashlib.sha256)
assert (key in _keys) and (_keys[key] != None)
# Generate a SECP256K1 signature of the given data with the given key
def sign_data(key: Key, data: bytes) -> bytes:
global _keys
if key not in _keys:
_init_key(key)
return _keys[key].sign_deterministic(data, sigencode=sigencode_der)

View File

@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIHoMkoRaNq0neb1TxRBor4WouV8PQqJf02sg4eh768LpoAcGBSuBBAAK
oUQDQgAETMqPrUlqpQQKAKfrL1zDuFN22IuhR6fXBUqZxkBWGIf+F6CW42w7Ujsk
Tz4v9/hAribE53rTvHOa9d5vLXentg==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIHfwyko1dEHTTQ7es7EUy2ajZo1IRRcEC8/9b+MDOzUaoAcGBSuBBAAK
oUQDQgAEuR++wXPjukpxTgFOvIJ7b4man6f0rHac3ihDF6APT2UPCfCapP9aMXYC
Vf5d/IETKbO1C+mRlPyhFhnmXy7f6g==
-----END EC PRIVATE KEY-----

View File

@@ -1,6 +1,4 @@
requests>=2.28,<3.0
click>=8.0,<9.0 # needed by the CI as it installs an older version and breaks dependencies
protobuf==3.20.1 # To fix the protobuf dependency bug
ragger[speculos]
ragger[speculos]>=1.6.0,<1.7.0
pytest
ecdsa
simple-rlp

View File

@@ -0,0 +1 @@
nanox/

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

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