Merge pull request #412 from LedgerHQ/feature/apa/ens
Domain names support
119
.github/workflows/ci-workflow.yml
vendored
@@ -166,111 +166,18 @@ jobs:
|
|||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
||||||
build_ragger_elfs:
|
build_ragger_elfs:
|
||||||
name: Building binaries for Ragger tests
|
name: Build app for Ragger tests
|
||||||
runs-on: ubuntu-latest
|
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
|
||||||
container:
|
with:
|
||||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
|
upload_app_binaries_artifact: "ragger_elfs"
|
||||||
|
flags: "DEBUG=1 CAL_CI_KEY=1 DOMAIN_NAME_TEST_KEY=1"
|
||||||
steps:
|
run_for_devices: '["nanos", "nanox", "nanosp"]'
|
||||||
- name: Clone
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Build test binaries
|
|
||||||
run: |
|
|
||||||
make -j BOLOS_SDK=$NANOS_SDK CAL_CI_KEY=1
|
|
||||||
mv bin/app.elf app-nanos.elf
|
|
||||||
make clean
|
|
||||||
make -j BOLOS_SDK=$NANOX_SDK CAL_CI_KEY=1
|
|
||||||
mv bin/app.elf app-nanox.elf
|
|
||||||
make clean
|
|
||||||
make -j BOLOS_SDK=$NANOSP_SDK CAL_CI_KEY=1
|
|
||||||
mv bin/app.elf app-nanosp.elf
|
|
||||||
|
|
||||||
- name: Upload app binaries
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: ragger_elfs
|
|
||||||
path: ./app-*.elf
|
|
||||||
|
|
||||||
create_ragger_env:
|
|
||||||
name: Cache Ragger environment
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- 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 }}
|
|
||||||
|
|
||||||
|
|
||||||
jobs-ragger-tests:
|
jobs-ragger-tests:
|
||||||
name: Ragger tests
|
name: Run Ragger tests
|
||||||
strategy:
|
needs: build_ragger_elfs
|
||||||
matrix:
|
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1
|
||||||
model: ["nanos", "nanox", "nanosp"]
|
with:
|
||||||
needs: [build_ragger_elfs, create_ragger_env]
|
download_app_binaries_artifact: "ragger_elfs"
|
||||||
runs-on: ubuntu-latest
|
test_dir: tests/ragger
|
||||||
|
run_for_devices: '["nanos", "nanox", "nanosp"]'
|
||||||
steps:
|
|
||||||
- name: Clone
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Download previously built artifacts
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
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
|
|
||||||
run: |
|
|
||||||
cd tests/ragger
|
|
||||||
. ./venv/bin/activate
|
|
||||||
pytest --path ./elfs --model ${{ matrix.model }} -s -v --tb=short
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
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) - 2022-02-09
|
## [1.10.2](https://github.com/ledgerhq/app-ethereum/compare/1.10.1...1.10.2) - 2023-XX-XX
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- (network) Velas EVM
|
- (network) Velas EVM
|
||||||
- (network) Boba Network
|
- (network) Boba Network
|
||||||
- (network) Energi
|
- (network) Energi
|
||||||
|
- Domain names support (LNX / LNS+)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
11
Makefile
@@ -35,7 +35,7 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'"
|
|||||||
APPVERSION_M=1
|
APPVERSION_M=1
|
||||||
APPVERSION_N=10
|
APPVERSION_N=10
|
||||||
APPVERSION_P=2
|
APPVERSION_P=2
|
||||||
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
|
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)-dev
|
||||||
APP_LOAD_FLAGS= --appFlags 0xa40 --dep Ethereum:$(APPVERSION)
|
APP_LOAD_FLAGS= --appFlags 0xa40 --dep Ethereum:$(APPVERSION)
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
@@ -157,6 +157,15 @@ ifneq ($(CAL_CI_KEY),0)
|
|||||||
DEFINES += HAVE_CAL_CI_KEY
|
DEFINES += HAVE_CAL_CI_KEY
|
||||||
endif
|
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
|
# Enabling debug PRINTF
|
||||||
DEBUG:=0
|
DEBUG:=0
|
||||||
ifneq ($(DEBUG),0)
|
ifneq ($(DEBUG),0)
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ Application version 1.9.19 - 2022-05-17
|
|||||||
- Add EIP712 STRUCT DEFINITION & EIP712 STRUCT IMPLEMENTATION
|
- Add EIP712 STRUCT DEFINITION & EIP712 STRUCT IMPLEMENTATION
|
||||||
- Update to SIGN ETH EIP712
|
- Update to SIGN ETH EIP712
|
||||||
|
|
||||||
|
### 1.10.2
|
||||||
|
- Add domain names support
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
This application describes the APDU messages interface to communicate with the Ethereum application.
|
This application describes the APDU messages interface to communicate with the Ethereum application.
|
||||||
@@ -881,6 +884,82 @@ _Output data_
|
|||||||
None
|
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
|
## Transport protocol
|
||||||
|
|
||||||
### General transport description
|
### General transport description
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#define INS_EIP712_STRUCT_DEF 0x1A
|
#define INS_EIP712_STRUCT_DEF 0x1A
|
||||||
#define INS_EIP712_STRUCT_IMPL 0x1C
|
#define INS_EIP712_STRUCT_IMPL 0x1C
|
||||||
#define INS_EIP712_FILTERING 0x1E
|
#define INS_EIP712_FILTERING 0x1E
|
||||||
|
#define INS_ENS_GET_CHALLENGE 0x20
|
||||||
|
#define INS_ENS_PROVIDE_INFO 0x22
|
||||||
#define P1_CONFIRM 0x01
|
#define P1_CONFIRM 0x01
|
||||||
#define P1_NON_CONFIRM 0x00
|
#define P1_NON_CONFIRM 0x00
|
||||||
#define P2_NO_CHAINCODE 0x00
|
#define P2_NO_CHAINCODE 0x00
|
||||||
|
|||||||
37
src/main.c
@@ -29,6 +29,8 @@
|
|||||||
#include "handle_get_printable_amount.h"
|
#include "handle_get_printable_amount.h"
|
||||||
#include "handle_check_address.h"
|
#include "handle_check_address.h"
|
||||||
#include "commands_712.h"
|
#include "commands_712.h"
|
||||||
|
#include "challenge.h"
|
||||||
|
#include "domain_name.h"
|
||||||
|
|
||||||
#ifdef HAVE_STARKWARE
|
#ifdef HAVE_STARKWARE
|
||||||
#include "stark_crypto.h"
|
#include "stark_crypto.h"
|
||||||
@@ -749,6 +751,19 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
|||||||
break;
|
break;
|
||||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
#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
|
#if 0
|
||||||
case 0xFF: // return to dashboard
|
case 0xFF: // return to dashboard
|
||||||
goto return_to_dashboard;
|
goto return_to_dashboard;
|
||||||
@@ -954,19 +969,22 @@ void coin_main(chain_config_t *coin_config) {
|
|||||||
G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0);
|
G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0);
|
||||||
#endif // TARGET_NANOX
|
#endif // TARGET_NANOX
|
||||||
|
|
||||||
if (N_storage.initialized != 0x01) {
|
if (!N_storage.initialized) {
|
||||||
internalStorage_t storage;
|
internalStorage_t storage;
|
||||||
#ifdef HAVE_ALLOW_DATA
|
#ifdef HAVE_ALLOW_DATA
|
||||||
storage.dataAllowed = 0x01;
|
storage.dataAllowed = true;
|
||||||
#else
|
#else
|
||||||
storage.dataAllowed = 0x00;
|
storage.dataAllowed = false;
|
||||||
#endif
|
#endif
|
||||||
storage.contractDetails = 0x00;
|
storage.contractDetails = false;
|
||||||
storage.displayNonce = 0x00;
|
storage.displayNonce = false;
|
||||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||||
storage.verbose_eip712 = 0x00;
|
storage.verbose_eip712 = false;
|
||||||
#endif
|
#endif
|
||||||
storage.initialized = 0x01;
|
#ifdef HAVE_DOMAIN_NAME
|
||||||
|
storage.verbose_domain_name = false;
|
||||||
|
#endif
|
||||||
|
storage.initialized = true;
|
||||||
nvm_write((void *) &N_storage, (void *) &storage, sizeof(internalStorage_t));
|
nvm_write((void *) &N_storage, (void *) &storage, sizeof(internalStorage_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,6 +998,11 @@ void coin_main(chain_config_t *coin_config) {
|
|||||||
BLE_power(1, "Nano X");
|
BLE_power(1, "Nano X");
|
||||||
#endif // HAVE_BLE
|
#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();
|
app_main();
|
||||||
}
|
}
|
||||||
CATCH(EXCEPTION_IO_RESET) {
|
CATCH(EXCEPTION_IO_RESET) {
|
||||||
|
|||||||
@@ -24,13 +24,16 @@ typedef struct bip32_path_t {
|
|||||||
} bip32_path_t;
|
} bip32_path_t;
|
||||||
|
|
||||||
typedef struct internalStorage_t {
|
typedef struct internalStorage_t {
|
||||||
unsigned char dataAllowed;
|
bool dataAllowed;
|
||||||
unsigned char contractDetails;
|
bool contractDetails;
|
||||||
unsigned char displayNonce;
|
bool displayNonce;
|
||||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||||
bool verbose_eip712;
|
bool verbose_eip712;
|
||||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||||
uint8_t initialized;
|
#ifdef HAVE_DOMAIN_NAME
|
||||||
|
bool verbose_domain_name;
|
||||||
|
#endif // HAVE_DOMAIN_NAME
|
||||||
|
bool initialized;
|
||||||
} internalStorage_t;
|
} internalStorage_t;
|
||||||
|
|
||||||
#ifdef HAVE_STARKWARE
|
#ifdef HAVE_STARKWARE
|
||||||
|
|||||||
17
src_bagl/ui_domain_name.c
Normal 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
@@ -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
|
||||||
@@ -7,13 +7,26 @@
|
|||||||
#define DISABLED_STR "Disabled"
|
#define DISABLED_STR "Disabled"
|
||||||
#define BUF_INCREMENT (MAX(strlen(ENABLED_STR), strlen(DISABLED_STR)) + 1)
|
#define BUF_INCREMENT (MAX(strlen(ENABLED_STR), strlen(DISABLED_STR)) + 1)
|
||||||
|
|
||||||
void display_settings(const ux_flow_step_t* const start_step);
|
// Reuse the strings.common.fullAmount buffer for settings displaying.
|
||||||
void switch_settings_blind_signing(void);
|
// No risk of collision as this buffer is unused in the settings menu
|
||||||
void switch_settings_display_data(void);
|
#define SETTING_BLIND_SIGNING_STATE (strings.common.fullAmount)
|
||||||
void switch_settings_display_nonce(void);
|
#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
|
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||||
void switch_settings_verbose_eip712(void);
|
static void switch_settings_verbose_eip712(void);
|
||||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||||
|
#ifdef HAVE_DOMAIN_NAME
|
||||||
|
static void switch_settings_verbose_domain_name(void);
|
||||||
|
#endif // HAVE_DOMAIN_NAME
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -75,7 +88,7 @@ UX_STEP_CB(
|
|||||||
"Transaction",
|
"Transaction",
|
||||||
"blind signing",
|
"blind signing",
|
||||||
#endif
|
#endif
|
||||||
strings.common.fullAddress
|
SETTING_BLIND_SIGNING_STATE
|
||||||
});
|
});
|
||||||
|
|
||||||
UX_STEP_CB(
|
UX_STEP_CB(
|
||||||
@@ -95,7 +108,7 @@ UX_STEP_CB(
|
|||||||
"Show contract data",
|
"Show contract data",
|
||||||
"details",
|
"details",
|
||||||
#endif
|
#endif
|
||||||
strings.common.fullAddress + BUF_INCREMENT
|
SETTING_DISPLAY_DATA_STATE
|
||||||
});
|
});
|
||||||
|
|
||||||
UX_STEP_CB(
|
UX_STEP_CB(
|
||||||
@@ -115,7 +128,7 @@ UX_STEP_CB(
|
|||||||
"Show account nonce",
|
"Show account nonce",
|
||||||
"in transactions",
|
"in transactions",
|
||||||
#endif
|
#endif
|
||||||
strings.common.fullAddress + (BUF_INCREMENT * 2)
|
SETTING_DISPLAY_NONCE_STATE
|
||||||
});
|
});
|
||||||
|
|
||||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||||
@@ -127,10 +140,23 @@ UX_STEP_CB(
|
|||||||
"Verbose EIP-712",
|
"Verbose EIP-712",
|
||||||
"Ignore filtering &",
|
"Ignore filtering &",
|
||||||
"display raw content",
|
"display raw content",
|
||||||
strings.common.fullAddress + (BUF_INCREMENT * 3)
|
SETTING_VERBOSE_EIP712_STATE
|
||||||
});
|
});
|
||||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
#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_STEP_CB(
|
||||||
ux_settings_flow_back_step,
|
ux_settings_flow_back_step,
|
||||||
@@ -149,54 +175,61 @@ UX_FLOW(ux_settings_flow,
|
|||||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||||
&ux_settings_flow_verbose_eip712_step,
|
&ux_settings_flow_verbose_eip712_step,
|
||||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
#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);
|
&ux_settings_flow_back_step);
|
||||||
|
|
||||||
void display_settings(const ux_flow_step_t* const start_step) {
|
static void display_settings(const ux_flow_step_t* const start_step) {
|
||||||
bool settings[] = {N_storage.dataAllowed,
|
strlcpy(SETTING_BLIND_SIGNING_STATE, BOOL_TO_STATE_STR(N_storage.dataAllowed), BUF_INCREMENT);
|
||||||
N_storage.contractDetails,
|
strlcpy(SETTING_DISPLAY_DATA_STATE,
|
||||||
N_storage.displayNonce,
|
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
|
#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
|
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||||
};
|
#ifdef HAVE_DOMAIN_NAME
|
||||||
uint8_t offset = 0;
|
strlcpy(SETTING_VERBOSE_DOMAIN_NAME_STATE,
|
||||||
|
BOOL_TO_STATE_STR(N_storage.verbose_domain_name),
|
||||||
for (unsigned int i = 0; i < ARRAY_SIZE(settings); ++i) {
|
BUF_INCREMENT);
|
||||||
strlcpy(strings.common.fullAddress + offset,
|
#endif // HAVE_DOMAIN_NAME
|
||||||
(settings[i] ? ENABLED_STR : DISABLED_STR),
|
|
||||||
sizeof(strings.common.fullAddress) - offset);
|
|
||||||
offset += BUF_INCREMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
ux_flow_init(0, ux_settings_flow, start_step);
|
ux_flow_init(0, ux_settings_flow, start_step);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switch_settings_blind_signing(void) {
|
static void toggle_setting(volatile bool* setting, const ux_flow_step_t* ui_step) {
|
||||||
uint8_t value = (N_storage.dataAllowed ? 0 : 1);
|
bool value = !*setting;
|
||||||
nvm_write((void*) &N_storage.dataAllowed, (void*) &value, sizeof(uint8_t));
|
nvm_write((void*) setting, (void*) &value, sizeof(value));
|
||||||
display_settings(&ux_settings_flow_blind_signing_step);
|
display_settings(ui_step);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switch_settings_display_data(void) {
|
static void switch_settings_blind_signing(void) {
|
||||||
uint8_t value = (N_storage.contractDetails ? 0 : 1);
|
toggle_setting(&N_storage.dataAllowed, &ux_settings_flow_blind_signing_step);
|
||||||
nvm_write((void*) &N_storage.contractDetails, (void*) &value, sizeof(uint8_t));
|
|
||||||
display_settings(&ux_settings_flow_display_data_step);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void switch_settings_display_nonce(void) {
|
static void switch_settings_display_data(void) {
|
||||||
uint8_t value = (N_storage.displayNonce ? 0 : 1);
|
toggle_setting(&N_storage.contractDetails, &ux_settings_flow_display_data_step);
|
||||||
nvm_write((void*) &N_storage.displayNonce, (void*) &value, sizeof(uint8_t));
|
}
|
||||||
display_settings(&ux_settings_flow_display_nonce_step);
|
|
||||||
|
static void switch_settings_display_nonce(void) {
|
||||||
|
toggle_setting(&N_storage.displayNonce, &ux_settings_flow_display_nonce_step);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||||
void switch_settings_verbose_eip712(void) {
|
static void switch_settings_verbose_eip712(void) {
|
||||||
bool value = !N_storage.verbose_eip712;
|
toggle_setting(&N_storage.verbose_eip712, &ux_settings_flow_verbose_eip712_step);
|
||||||
nvm_write((void*) &N_storage.verbose_eip712, (void*) &value, sizeof(value));
|
|
||||||
display_settings(&ux_settings_flow_verbose_eip712_step);
|
|
||||||
}
|
}
|
||||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
#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
|
// clang-format off
|
||||||
#ifdef TARGET_NANOS
|
#ifdef TARGET_NANOS
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "ui_plugin.h"
|
#include "ui_plugin.h"
|
||||||
#include "common_ui.h"
|
#include "common_ui.h"
|
||||||
#include "plugins.h"
|
#include "plugins.h"
|
||||||
|
#include "domain_name.h"
|
||||||
|
#include "ui_domain_name.h"
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
UX_STEP_NOCB(
|
UX_STEP_NOCB(
|
||||||
@@ -217,7 +219,19 @@ void ux_approve_tx(bool fromPlugin) {
|
|||||||
} else {
|
} else {
|
||||||
// We're in a regular transaction, just show the amount and the address
|
// 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_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) {
|
if (N_storage.displayNonce) {
|
||||||
@@ -235,4 +249,4 @@ void ux_approve_tx(bool fromPlugin) {
|
|||||||
ux_approval_tx_flow[step++] = FLOW_END_STEP;
|
ux_approval_tx_flow[step++] = FLOW_END_STEP;
|
||||||
|
|
||||||
ux_flow_init(0, ux_approval_tx_flow, NULL);
|
ux_flow_init(0, ux_approval_tx_flow, NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
|
||||||
|
|
||||||
#include "hash_bytes.h"
|
#include "hash_bytes.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +7,7 @@
|
|||||||
* @param[in] n number of bytes to hash
|
* @param[in] n number of bytes to hash
|
||||||
* @param[in] hash_ctx pointer to the hashing context
|
* @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);
|
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] byte byte to hash
|
||||||
* @param[in] hash_ctx pointer to the hashing context
|
* @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);
|
hash_nbytes(&byte, 1, hash_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
|
||||||
10
src_common/hash_bytes.h
Normal 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_
|
||||||
14
src_features/getChallenge/challenge.h
Normal 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
|
||||||
38
src_features/getChallenge/cmd_get_challenge.c
Normal 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
|
||||||
710
src_features/provideDomainName/cmd_provide_domain_name.c
Normal 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
|
||||||
18
src_features/provideDomainName/domain_name.h
Normal 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
|
||||||
@@ -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_
|
|
||||||
2
tests/ragger/.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
venv/
|
venv/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
snapshots-tmp/
|
||||||
|
elfs/
|
||||||
|
|||||||
@@ -1,17 +1,48 @@
|
|||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
from typing import Iterator, Dict, List
|
from typing import Optional
|
||||||
from ragger.backend import BackendInterface
|
from ragger.backend import BackendInterface
|
||||||
from ragger.utils import RAPDU
|
from ragger.utils import RAPDU
|
||||||
from ethereum_client.command_builder import EthereumCmdBuilder
|
from ragger.navigator import NavInsID, NavIns, NanoNavigator
|
||||||
from ethereum_client.setting import SettingType, SettingImpl
|
from .command_builder import EthereumCmdBuilder
|
||||||
from ethereum_client.eip712 import EIP712FieldType
|
from .setting import SettingType, SettingImpl
|
||||||
from ethereum_client.response_parser import EthereumRespParser
|
from .eip712 import EIP712FieldType
|
||||||
|
from .response_parser import EthereumRespParser
|
||||||
|
from .tlv import format_tlv
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
import keychain
|
||||||
|
import rlp
|
||||||
|
|
||||||
|
|
||||||
class EthereumClient:
|
ROOT_SCREENSHOT_PATH = Path(__file__).parent.parent
|
||||||
_settings: Dict[SettingType, SettingImpl] = {
|
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(
|
SettingType.BLIND_SIGNING: SettingImpl(
|
||||||
[ "nanos", "nanox", "nanosp" ]
|
[ "nanos", "nanox", "nanosp" ]
|
||||||
),
|
),
|
||||||
@@ -23,15 +54,20 @@ class EthereumClient:
|
|||||||
),
|
),
|
||||||
SettingType.VERBOSE_EIP712: SettingImpl(
|
SettingType.VERBOSE_EIP712: SettingImpl(
|
||||||
[ "nanox", "nanosp" ]
|
[ "nanox", "nanosp" ]
|
||||||
|
),
|
||||||
|
SettingType.VERBOSE_ENS: SettingImpl(
|
||||||
|
[ "nanox", "nanosp" ]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_click_delay = 1/4
|
_click_delay = 1/4
|
||||||
_eip712_filtering = False
|
_eip712_filtering = False
|
||||||
|
|
||||||
def __init__(self, client: BackendInterface):
|
def __init__(self, client: BackendInterface, golden_run: bool):
|
||||||
self._client = client
|
self._client = client
|
||||||
|
self._chain_id = 1
|
||||||
self._cmd_builder = EthereumCmdBuilder()
|
self._cmd_builder = EthereumCmdBuilder()
|
||||||
self._resp_parser = EthereumRespParser()
|
self._resp_parser = EthereumRespParser()
|
||||||
|
self._nav = NanoNavigator(client, client.firmware, golden_run)
|
||||||
signal.signal(signal.SIGALRM, self._click_signal_timeout)
|
signal.signal(signal.SIGALRM, self._click_signal_timeout)
|
||||||
for setting in self._settings.values():
|
for setting in self._settings.values():
|
||||||
setting.value = False
|
setting.value = False
|
||||||
@@ -65,11 +101,11 @@ class EthereumClient:
|
|||||||
array_levels: [],
|
array_levels: [],
|
||||||
key_name: str):
|
key_name: str):
|
||||||
with self._send(self._cmd_builder.eip712_send_struct_def_struct_field(
|
with self._send(self._cmd_builder.eip712_send_struct_def_struct_field(
|
||||||
field_type,
|
field_type,
|
||||||
type_name,
|
type_name,
|
||||||
type_size,
|
type_size,
|
||||||
array_levels,
|
array_levels,
|
||||||
key_name)):
|
key_name)):
|
||||||
pass
|
pass
|
||||||
return self._recv()
|
return self._recv()
|
||||||
|
|
||||||
@@ -91,8 +127,8 @@ class EthereumClient:
|
|||||||
self._disable_click_until_response()
|
self._disable_click_until_response()
|
||||||
assert self._recv().status == 0x9000
|
assert self._recv().status == 0x9000
|
||||||
|
|
||||||
def eip712_sign_new(self, bip32):
|
def eip712_sign_new(self, bip32_path: str):
|
||||||
with self._send(self._cmd_builder.eip712_sign_new(bip32)):
|
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
|
time.sleep(0.5) # tight on timing, needed by the CI otherwise might fail sometimes
|
||||||
if not self._settings[SettingType.VERBOSE_EIP712].value and \
|
if not self._settings[SettingType.VERBOSE_EIP712].value and \
|
||||||
not self._eip712_filtering: # need to skip the message hash
|
not self._eip712_filtering: # need to skip the message hash
|
||||||
@@ -104,10 +140,10 @@ class EthereumClient:
|
|||||||
return self._resp_parser.sign(resp.data)
|
return self._resp_parser.sign(resp.data)
|
||||||
|
|
||||||
def eip712_sign_legacy(self,
|
def eip712_sign_legacy(self,
|
||||||
bip32,
|
bip32_path: str,
|
||||||
domain_hash: bytes,
|
domain_hash: bytes,
|
||||||
message_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,
|
domain_hash,
|
||||||
message_hash)):
|
message_hash)):
|
||||||
self._client.right_click() # sign typed message screen
|
self._client.right_click() # sign typed message screen
|
||||||
@@ -125,7 +161,7 @@ class EthereumClient:
|
|||||||
assert resp.status == 0x9000
|
assert resp.status == 0x9000
|
||||||
return self._resp_parser.sign(resp.data)
|
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
|
# Go to settings
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
self._client.right_click()
|
self._client.right_click()
|
||||||
@@ -156,3 +192,61 @@ class EthereumClient:
|
|||||||
with self._send(self._cmd_builder.eip712_filtering_show_field(name, sig)):
|
with self._send(self._cmd_builder.eip712_filtering_show_field(name, sig)):
|
||||||
pass
|
pass
|
||||||
assert self._recv().status == 0x9000
|
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
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
from typing import Iterator
|
from typing import Iterator, Optional
|
||||||
from ethereum_client.eip712 import EIP712FieldType
|
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_DEF = 0x1a
|
||||||
EIP712_SEND_STRUCT_IMPL = 0x1c
|
EIP712_SEND_STRUCT_IMPL = 0x1c
|
||||||
EIP712_SEND_FILTERING = 0x1e
|
EIP712_SEND_FILTERING = 0x1e
|
||||||
EIP712_SIGN = 0x0c
|
EIP712_SIGN = 0x0c
|
||||||
|
GET_CHALLENGE = 0x20
|
||||||
|
PROVIDE_DOMAIN_NAME = 0x22
|
||||||
|
|
||||||
class P1Type(IntEnum):
|
class P1Type(IntEnum):
|
||||||
COMPLETE_SEND = 0x00
|
COMPLETE_SEND = 0x00
|
||||||
PARTIAL_SEND = 0x01
|
PARTIAL_SEND = 0x01
|
||||||
|
SIGN_FIRST_CHUNK = 0x00
|
||||||
|
SIGN_SUBSQT_CHUNK = 0x80
|
||||||
|
|
||||||
class P2Type(IntEnum):
|
class P2Type(IntEnum):
|
||||||
STRUCT_NAME = 0x00
|
STRUCT_NAME = 0x00
|
||||||
STRUCT_FIELD = 0xff
|
STRUCT_FIELD = 0xff
|
||||||
ARRAY = 0x0f
|
ARRAY = 0x0f
|
||||||
@@ -22,14 +29,14 @@ class P2Type(IntEnum):
|
|||||||
FILTERING_CONTRACT_NAME = 0x0f
|
FILTERING_CONTRACT_NAME = 0x0f
|
||||||
FILTERING_FIELD_NAME = 0xff
|
FILTERING_FIELD_NAME = 0xff
|
||||||
|
|
||||||
class EthereumCmdBuilder:
|
class EthereumCmdBuilder:
|
||||||
_CLA: int = 0xE0
|
_CLA: int = 0xE0
|
||||||
|
|
||||||
def _serialize(self,
|
def _serialize(self,
|
||||||
ins: InsType,
|
ins: InsType,
|
||||||
p1: int,
|
p1: int,
|
||||||
p2: int,
|
p2: int,
|
||||||
cdata: bytearray = bytearray()) -> bytes:
|
cdata: bytearray = bytes()) -> bytes:
|
||||||
|
|
||||||
header = bytearray()
|
header = bytearray()
|
||||||
header.append(self._CLA)
|
header.append(self._CLA)
|
||||||
@@ -109,27 +116,18 @@ class EthereumCmdBuilder:
|
|||||||
data_w_length[:0xff])
|
data_w_length[:0xff])
|
||||||
data_w_length = data_w_length[0xff:]
|
data_w_length = data_w_length[0xff:]
|
||||||
|
|
||||||
def _format_bip32(self, bip32, data: bytearray) -> bytearray:
|
def eip712_sign_new(self, bip32_path: str) -> bytes:
|
||||||
data.append(len(bip32))
|
data = pack_derivation_path(bip32_path)
|
||||||
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())
|
|
||||||
return self._serialize(InsType.EIP712_SIGN,
|
return self._serialize(InsType.EIP712_SIGN,
|
||||||
P1Type.COMPLETE_SEND,
|
P1Type.COMPLETE_SEND,
|
||||||
P2Type.NEW_IMPLEM,
|
P2Type.NEW_IMPLEM,
|
||||||
data)
|
data)
|
||||||
|
|
||||||
def eip712_sign_legacy(self,
|
def eip712_sign_legacy(self,
|
||||||
bip32,
|
bip32_path: str,
|
||||||
domain_hash: bytes,
|
domain_hash: bytes,
|
||||||
message_hash: bytes) -> bytes:
|
message_hash: bytes) -> bytes:
|
||||||
data = self._format_bip32(bip32, bytearray())
|
data = pack_derivation_path(bip32_path)
|
||||||
data += domain_hash
|
data += domain_hash
|
||||||
data += message_hash
|
data += message_hash
|
||||||
return self._serialize(InsType.EIP712_SIGN,
|
return self._serialize(InsType.EIP712_SIGN,
|
||||||
@@ -168,3 +166,30 @@ class EthereumCmdBuilder:
|
|||||||
P1Type.COMPLETE_SEND,
|
P1Type.COMPLETE_SEND,
|
||||||
P2Type.FILTERING_FIELD_NAME,
|
P2Type.FILTERING_FIELD_NAME,
|
||||||
self._eip712_filtering_send_name(name, sig))
|
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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
|
|
||||||
class EIP712FieldType(IntEnum):
|
class EIP712FieldType(IntEnum):
|
||||||
CUSTOM = 0,
|
CUSTOM = 0,
|
||||||
INT = auto()
|
INT = auto()
|
||||||
UINT = auto()
|
UINT = auto()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class EthereumRespParser:
|
class EthereumRespParser:
|
||||||
def sign(self, data: bytes):
|
def sign(self, data: bytes):
|
||||||
assert len(data) == (1 + 32 + 32)
|
assert len(data) == (1 + 32 + 32)
|
||||||
|
|
||||||
@@ -12,3 +12,7 @@ class EthereumRespParser:
|
|||||||
data = data[32:]
|
data = data[32:]
|
||||||
|
|
||||||
return v, r, s
|
return v, r, s
|
||||||
|
|
||||||
|
def challenge(self, data: bytes) -> int:
|
||||||
|
assert len(data) == 4
|
||||||
|
return int.from_bytes(data, "big")
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
class SettingType(IntEnum):
|
class SettingType(IntEnum):
|
||||||
BLIND_SIGNING = 0,
|
BLIND_SIGNING = 0,
|
||||||
DEBUG_DATA = auto()
|
DEBUG_DATA = auto()
|
||||||
NONCE = auto()
|
NONCE = auto()
|
||||||
VERBOSE_EIP712 = auto()
|
VERBOSE_EIP712 = auto()
|
||||||
|
VERBOSE_ENS = auto()
|
||||||
|
|
||||||
class SettingImpl:
|
class SettingImpl:
|
||||||
devices: List[str]
|
devices: List[str]
|
||||||
value: bool
|
value: bool
|
||||||
|
|
||||||
25
tests/ragger/app/tlv.py
Normal 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
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import os
|
|
||||||
import hashlib
|
|
||||||
from ecdsa.util import sigencode_der
|
|
||||||
from ecdsa import SigningKey
|
|
||||||
|
|
||||||
_key: SigningKey = None
|
|
||||||
|
|
||||||
def _init_key():
|
|
||||||
global _key
|
|
||||||
with open(os.path.dirname(__file__) + "/key.pem") as pem_file:
|
|
||||||
_key = SigningKey.from_pem(pem_file.read(), hashlib.sha256)
|
|
||||||
assert _key != None
|
|
||||||
|
|
||||||
def sign(data: bytes) -> bytes:
|
|
||||||
global _key
|
|
||||||
if not _key:
|
|
||||||
_init_key()
|
|
||||||
return _key.sign_deterministic(data, sigencode=sigencode_der)
|
|
||||||
@@ -1,68 +1,12 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from pathlib import Path
|
from ragger.conftest import configuration
|
||||||
from ragger.firmware import Firmware
|
from ragger.backend import BackendInterface
|
||||||
from ragger.backend import SpeculosBackend, LedgerCommBackend, LedgerWalletBackend, BackendInterface
|
from app.client import EthereumClient
|
||||||
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
|
|
||||||
|
|
||||||
# This final fixture will return the properly configured app client, to be used in tests
|
# This final fixture will return the properly configured app client, to be used in tests
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app_client(backend_client: BackendInterface) -> EthereumClient:
|
def app_client(backend: BackendInterface, golden_run: bool) -> EthereumClient:
|
||||||
return EthereumClient(backend_client)
|
return EthereumClient(backend, golden_run)
|
||||||
|
|
||||||
|
# Pull all features from the base ragger conftest using the overridden configuration
|
||||||
|
pytest_plugins = ("ragger.conftest.base_conftest", )
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import json
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import hashlib
|
import hashlib
|
||||||
from ethereum_client.client import EthereumClient, EIP712FieldType
|
from app.client import EthereumClient, EIP712FieldType
|
||||||
from cal import cal
|
import keychain
|
||||||
|
|
||||||
# global variables
|
# global variables
|
||||||
app_client: EthereumClient = None
|
app_client: EthereumClient = None
|
||||||
@@ -251,7 +251,7 @@ def send_filtering_message_info(display_name: str, filters_count: int):
|
|||||||
for char in display_name:
|
for char in display_name:
|
||||||
to_sign.append(ord(char))
|
to_sign.append(ord(char))
|
||||||
|
|
||||||
sig = cal.sign(to_sign)
|
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
|
||||||
app_client.eip712_filtering_message_info(display_name, filters_count, sig)
|
app_client.eip712_filtering_message_info(display_name, filters_count, sig)
|
||||||
|
|
||||||
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
|
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
|
||||||
@@ -269,7 +269,7 @@ def send_filtering_show_field(display_name):
|
|||||||
to_sign.append(ord(char))
|
to_sign.append(ord(char))
|
||||||
for char in display_name:
|
for char in display_name:
|
||||||
to_sign.append(ord(char))
|
to_sign.append(ord(char))
|
||||||
sig = cal.sign(to_sign)
|
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
|
||||||
app_client.eip712_filtering_show_field(display_name, sig)
|
app_client.eip712_filtering_show_field(display_name, sig)
|
||||||
|
|
||||||
def read_filtering_file(domain, message, filtering_file_path):
|
def read_filtering_file(domain, message, filtering_file_path):
|
||||||
|
|||||||
27
tests/ragger/keychain.py
Normal 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)
|
||||||
8
tests/ragger/keychain/domain_name.pem
Normal 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-----
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
requests>=2.28,<3.0
|
ragger[speculos]>=1.6.0,<1.7.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]
|
|
||||||
pytest
|
pytest
|
||||||
ecdsa
|
ecdsa
|
||||||
|
simple-rlp
|
||||||
|
|||||||
1
tests/ragger/snapshots/nanosp
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
nanox/
|
||||||
BIN
tests/ragger/snapshots/nanox/domain_name_non_mainnet/00000.png
Normal file
|
After Width: | Height: | Size: 414 B |
BIN
tests/ragger/snapshots/nanox/domain_name_non_mainnet/00001.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
tests/ragger/snapshots/nanox/domain_name_non_mainnet/00002.png
Normal file
|
After Width: | Height: | Size: 585 B |
BIN
tests/ragger/snapshots/nanox/domain_name_non_mainnet/00003.png
Normal file
|
After Width: | Height: | Size: 383 B |
BIN
tests/ragger/snapshots/nanox/domain_name_non_mainnet/00004.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
tests/ragger/snapshots/nanox/domain_name_non_mainnet/00005.png
Normal file
|
After Width: | Height: | Size: 472 B |
BIN
tests/ragger/snapshots/nanox/domain_name_non_mainnet/00006.png
Normal file
|
After Width: | Height: | Size: 382 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_False/00000.png
Normal file
|
After Width: | Height: | Size: 414 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_False/00001.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_False/00002.png
Normal file
|
After Width: | Height: | Size: 394 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_False/00003.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_False/00004.png
Normal file
|
After Width: | Height: | Size: 472 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_False/00005.png
Normal file
|
After Width: | Height: | Size: 382 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_True/00000.png
Normal file
|
After Width: | Height: | Size: 414 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_True/00001.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_True/00002.png
Normal file
|
After Width: | Height: | Size: 394 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_True/00003.png
Normal file
|
After Width: | Height: | Size: 585 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_True/00004.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_True/00005.png
Normal file
|
After Width: | Height: | Size: 472 B |
BIN
tests/ragger/snapshots/nanox/domain_name_verbose_True/00006.png
Normal file
|
After Width: | Height: | Size: 382 B |
BIN
tests/ragger/snapshots/nanox/domain_name_wrong_addr/00000.png
Normal file
|
After Width: | Height: | Size: 414 B |
BIN
tests/ragger/snapshots/nanox/domain_name_wrong_addr/00001.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
tests/ragger/snapshots/nanox/domain_name_wrong_addr/00002.png
Normal file
|
After Width: | Height: | Size: 588 B |
BIN
tests/ragger/snapshots/nanox/domain_name_wrong_addr/00003.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
tests/ragger/snapshots/nanox/domain_name_wrong_addr/00004.png
Normal file
|
After Width: | Height: | Size: 472 B |
BIN
tests/ragger/snapshots/nanox/domain_name_wrong_addr/00005.png
Normal file
|
After Width: | Height: | Size: 382 B |
128
tests/ragger/test_domain_name.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import pytest
|
||||||
|
from ragger.error import ExceptionRAPDU
|
||||||
|
from app.client import EthereumClient, StatusWord
|
||||||
|
from app.setting import SettingType
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# Values used across all tests
|
||||||
|
CHAIN_ID = 1
|
||||||
|
NAME = "ledger.eth"
|
||||||
|
ADDR = bytes.fromhex("0011223344556677889900112233445566778899")
|
||||||
|
KEY_ID = 1
|
||||||
|
ALGO_ID = 1
|
||||||
|
BIP32_PATH = "m/44'/60'/0'/0/0"
|
||||||
|
NONCE = 21
|
||||||
|
GAS_PRICE = 13000000000
|
||||||
|
GAS_LIMIT = 21000
|
||||||
|
AMOUNT = 1.22
|
||||||
|
|
||||||
|
@pytest.fixture(params=[False, True])
|
||||||
|
def verbose(request) -> bool:
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
def common(app_client: EthereumClient) -> int:
|
||||||
|
if app_client._client.firmware.device == "nanos":
|
||||||
|
pytest.skip("Not supported on LNS")
|
||||||
|
return app_client.get_challenge()
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_fund(app_client: EthereumClient, verbose: bool):
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
app_client.settings_set({
|
||||||
|
SettingType.VERBOSE_ENS: True
|
||||||
|
})
|
||||||
|
|
||||||
|
app_client.provide_domain_name(challenge, NAME, ADDR)
|
||||||
|
|
||||||
|
app_client.send_fund(BIP32_PATH,
|
||||||
|
NONCE,
|
||||||
|
GAS_PRICE,
|
||||||
|
GAS_LIMIT,
|
||||||
|
ADDR,
|
||||||
|
AMOUNT,
|
||||||
|
CHAIN_ID,
|
||||||
|
"domain_name_verbose_" + str(verbose))
|
||||||
|
|
||||||
|
def test_send_fund_wrong_challenge(app_client: EthereumClient):
|
||||||
|
caught = False
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_client.provide_domain_name(~challenge & 0xffffffff, NAME, ADDR)
|
||||||
|
except ExceptionRAPDU as e:
|
||||||
|
assert e.status == StatusWord.INVALID_DATA
|
||||||
|
else:
|
||||||
|
assert False # An exception should have been raised
|
||||||
|
|
||||||
|
def test_send_fund_wrong_addr(app_client: EthereumClient):
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
app_client.provide_domain_name(challenge, NAME, ADDR)
|
||||||
|
|
||||||
|
addr = bytearray(ADDR)
|
||||||
|
addr.reverse()
|
||||||
|
|
||||||
|
app_client.send_fund(BIP32_PATH,
|
||||||
|
NONCE,
|
||||||
|
GAS_PRICE,
|
||||||
|
GAS_LIMIT,
|
||||||
|
addr,
|
||||||
|
AMOUNT,
|
||||||
|
CHAIN_ID,
|
||||||
|
"domain_name_wrong_addr")
|
||||||
|
|
||||||
|
def test_send_fund_non_mainnet(app_client: EthereumClient):
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
app_client.provide_domain_name(challenge, NAME, ADDR)
|
||||||
|
|
||||||
|
app_client.send_fund(BIP32_PATH,
|
||||||
|
NONCE,
|
||||||
|
GAS_PRICE,
|
||||||
|
GAS_LIMIT,
|
||||||
|
ADDR,
|
||||||
|
AMOUNT,
|
||||||
|
5,
|
||||||
|
"domain_name_non_mainnet")
|
||||||
|
|
||||||
|
def test_send_fund_domain_too_long(app_client: EthereumClient):
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_client.provide_domain_name(challenge, "ledger" + "0"*25 + ".eth", ADDR)
|
||||||
|
except ExceptionRAPDU as e:
|
||||||
|
assert e.status == StatusWord.INVALID_DATA
|
||||||
|
else:
|
||||||
|
assert False # An exception should have been raised
|
||||||
|
|
||||||
|
def test_send_fund_domain_invalid_character(app_client: EthereumClient):
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_client.provide_domain_name(challenge, "l\xe8dger.eth", ADDR)
|
||||||
|
except ExceptionRAPDU as e:
|
||||||
|
assert e.status == StatusWord.INVALID_DATA
|
||||||
|
else:
|
||||||
|
assert False # An exception should have been raised
|
||||||
|
|
||||||
|
def test_send_fund_uppercase(app_client: EthereumClient):
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_client.provide_domain_name(challenge, NAME.upper(), ADDR)
|
||||||
|
except ExceptionRAPDU as e:
|
||||||
|
assert e.status == StatusWord.INVALID_DATA
|
||||||
|
else:
|
||||||
|
assert False # An exception should have been raised
|
||||||
|
|
||||||
|
def test_send_fund_domain_non_ens(app_client: EthereumClient):
|
||||||
|
challenge = common(app_client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_client.provide_domain_name(challenge, "ledger.hte", ADDR)
|
||||||
|
except ExceptionRAPDU as e:
|
||||||
|
assert e.status == StatusWord.INVALID_DATA
|
||||||
|
else:
|
||||||
|
assert False # An exception should have been raised
|
||||||
@@ -2,23 +2,17 @@ import pytest
|
|||||||
import os
|
import os
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from typing import List
|
from typing import List
|
||||||
from ethereum_client.client import EthereumClient, SettingType
|
from app.client import EthereumClient, SettingType
|
||||||
from eip712 import InputData
|
from eip712 import InputData
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
|
||||||
bip32 = [
|
BIP32_PATH = "m/44'/60'/0'/0/0"
|
||||||
0x8000002c,
|
|
||||||
0x8000003c,
|
|
||||||
0x80000000,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def input_files() -> List[str]:
|
def input_files() -> List[str]:
|
||||||
files = []
|
files = []
|
||||||
for file in os.scandir("./eip712/input_files"):
|
for file in os.scandir("%s/eip712/input_files" % (os.path.dirname(__file__))):
|
||||||
if fnmatch.fnmatch(file, "*-data.json"):
|
if fnmatch.fnmatch(file, "*-data.json"):
|
||||||
files.append(file.path)
|
files.append(file.path)
|
||||||
return sorted(files)
|
return sorted(files)
|
||||||
@@ -38,7 +32,7 @@ def filtering(request) -> bool:
|
|||||||
|
|
||||||
def test_eip712_legacy(app_client: EthereumClient):
|
def test_eip712_legacy(app_client: EthereumClient):
|
||||||
v, r, s = app_client.eip712_sign_legacy(
|
v, r, s = app_client.eip712_sign_legacy(
|
||||||
bip32,
|
BIP32_PATH,
|
||||||
bytes.fromhex('6137beb405d9ff777172aa879e33edb34a1460e701802746c5ef96e741710e59'),
|
bytes.fromhex('6137beb405d9ff777172aa879e33edb34a1460e701802746c5ef96e741710e59'),
|
||||||
bytes.fromhex('eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8')
|
bytes.fromhex('eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8')
|
||||||
)
|
)
|
||||||
@@ -47,10 +41,10 @@ def test_eip712_legacy(app_client: EthereumClient):
|
|||||||
assert r == bytes.fromhex("ea66f747173762715751c889fea8722acac3fc35db2c226d37a2e58815398f64")
|
assert r == bytes.fromhex("ea66f747173762715751c889fea8722acac3fc35db2c226d37a2e58815398f64")
|
||||||
assert s == bytes.fromhex("52d8ba9153de9255da220ffd36762c0b027701a3b5110f0a765f94b16a9dfb55")
|
assert s == bytes.fromhex("52d8ba9153de9255da220ffd36762c0b027701a3b5110f0a765f94b16a9dfb55")
|
||||||
|
|
||||||
|
|
||||||
def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool, filtering: bool):
|
def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool, filtering: bool):
|
||||||
print("=====> %s" % (input_file))
|
if app_client._client.firmware.device == "nanos":
|
||||||
if app_client._client.firmware.device != "nanos":
|
pytest.skip("Not supported on LNS")
|
||||||
|
else:
|
||||||
test_path = "%s/%s" % (input_file.parent, "-".join(input_file.stem.split("-")[:-1]))
|
test_path = "%s/%s" % (input_file.parent, "-".join(input_file.stem.split("-")[:-1]))
|
||||||
conf_file = "%s.ini" % (test_path)
|
conf_file = "%s.ini" % (test_path)
|
||||||
filter_file = None
|
filter_file = None
|
||||||
@@ -74,7 +68,7 @@ def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool,
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert InputData.process_file(app_client, input_file, filter_file) == True
|
assert InputData.process_file(app_client, input_file, filter_file) == True
|
||||||
v, r, s = app_client.eip712_sign_new(bip32)
|
v, r, s = app_client.eip712_sign_new(BIP32_PATH)
|
||||||
#print("[signature]")
|
#print("[signature]")
|
||||||
#print("v = %s" % (v.hex()))
|
#print("v = %s" % (v.hex()))
|
||||||
#print("r = %s" % (r.hex()))
|
#print("r = %s" % (r.hex()))
|
||||||
@@ -84,6 +78,4 @@ def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool,
|
|||||||
assert r == bytes.fromhex(config["signature"]["r"])
|
assert r == bytes.fromhex(config["signature"]["r"])
|
||||||
assert s == bytes.fromhex(config["signature"]["s"])
|
assert s == bytes.fromhex(config["signature"]["s"])
|
||||||
else:
|
else:
|
||||||
print("No filter file found, skipping...")
|
pytest.skip("No filter file found")
|
||||||
else:
|
|
||||||
print("Not supported by LNS, skipping...")
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 758 B After Width: | Height: | Size: 705 B |
|
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 758 B |
|
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 305 B |
@@ -5,9 +5,9 @@ import { waitForAppScreen, zemu, nano_models } from './test.fixture';
|
|||||||
nano_models.forEach(function(model) {
|
nano_models.forEach(function(model) {
|
||||||
test('[Nano ' + model.letter + '] Deposit ETH on compound, blind sign', zemu(model, async (sim, eth) => {
|
test('[Nano ' + model.letter + '] Deposit ETH on compound, blind sign', zemu(model, async (sim, eth) => {
|
||||||
let clicks;
|
let clicks;
|
||||||
// LNS does not have an EIP712 setting
|
// LNS does not have EIP712 & ENS settings
|
||||||
if (model.letter === 'S') clicks = 3;
|
if (model.letter === 'S') clicks = 3;
|
||||||
else clicks = 4;
|
else clicks = 5;
|
||||||
// Enable blind-signing
|
// Enable blind-signing
|
||||||
await sim.navigateAndCompareSnapshots('.', model.name + '_enable_blind_signing', [-2, 0, 0, clicks, 0]);
|
await sim.navigateAndCompareSnapshots('.', model.name + '_enable_blind_signing', [-2, 0, 0, clicks, 0]);
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ const NANOS_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanos.elf");
|
|||||||
const NANOX_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanox.elf");
|
const NANOX_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanox.elf");
|
||||||
|
|
||||||
const nano_models: DeviceModel[] = [
|
const nano_models: DeviceModel[] = [
|
||||||
{ name: 'nanos', letter: 'S', path: NANOS_ELF_PATH, clone_path: NANOS_CLONE_ELF_PATH },
|
{ name: 'nanos', letter: 'S', path: NANOS_ELF_PATH, clone_path: NANOS_CLONE_ELF_PATH }/*,
|
||||||
{ name: 'nanox', letter: 'X', path: NANOX_ELF_PATH, clone_path: NANOX_CLONE_ELF_PATH }
|
{ name: 'nanox', letter: 'X', path: NANOX_ELF_PATH, clone_path: NANOX_CLONE_ELF_PATH }*/
|
||||||
];
|
];
|
||||||
|
|
||||||
const TIMEOUT = 1000000;
|
const TIMEOUT = 1000000;
|
||||||
|
|||||||