Merge pull request #424 from LedgerHQ/release/1.10.2
App release 1.10.2
2
.github/actions/commit-changes/action.yml
vendored
@@ -114,6 +114,6 @@ runs:
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ inputs.secret }}
|
||||
branch: ${{ steps.commit.outputs.src_branch }}:${{ steps.commit.outputs.dst_branch }}
|
||||
branch: ${{ steps.commit.outputs.dst_branch }}
|
||||
directory: ${{ inputs.directory }}
|
||||
repository: ${{ inputs.repository }}
|
||||
|
||||
2
.github/workflows/auto-author-assign.yml
vendored
@@ -11,4 +11,4 @@ jobs:
|
||||
assign-author:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: toshimaru/auto-author-assign@v1.4.0
|
||||
- uses: toshimaru/auto-author-assign@v1.6.1
|
||||
|
||||
2
.github/workflows/build-workflow.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build Ethereum
|
||||
run: |
|
||||
|
||||
147
.github/workflows/ci-workflow.yml
vendored
@@ -18,14 +18,14 @@ jobs:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build with Clang Static Analyzer
|
||||
run: |
|
||||
make clean
|
||||
scan-build --use-cc=clang -analyze-headers -enable-checker security -enable-checker unix -enable-checker valist -o scan-build --status-bugs make default
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: scan-build
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build testing binaries
|
||||
run: |
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
cd tests/zemu/ && ./build_local_test_elfs.sh
|
||||
|
||||
- name: Upload app binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: e2e_zemu_elfs
|
||||
path: ./tests/zemu/elfs/
|
||||
@@ -68,14 +68,14 @@ jobs:
|
||||
echo $DISPLAY
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- run: sudo apt-get update -y && sudo apt-get install -y libusb-1.0.0 libudev-dev
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v2
|
||||
- name: Install NodeJS
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "14.4.0"
|
||||
node-version: "16"
|
||||
|
||||
- name: Install yarn
|
||||
run: npm install -g yarn
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
run: mkdir tests/zemu/elfs
|
||||
|
||||
- name: Download app binaries
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tmp/
|
||||
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build testing binaries
|
||||
run: |
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
make clean && make -j DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOSP_SDK && mv bin/app.elf tests/speculos/elfs/nanosp.elf
|
||||
|
||||
- name: Upload app binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: e2e_speculos_elfs
|
||||
path: ./tests/speculos/elfs
|
||||
@@ -136,13 +136,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Create tmp folder for artifacts
|
||||
run: mkdir tests/speculos/elfs
|
||||
|
||||
- name: Download app binaries
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: tmp/
|
||||
|
||||
@@ -166,113 +166,18 @@ jobs:
|
||||
# =====================================================
|
||||
|
||||
build_ragger_elfs:
|
||||
name: Building binaries for Ragger tests
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build test binaries
|
||||
run: |
|
||||
make -j BOLOS_SDK=$NANOS_SDK CAL_TESTING_KEY=1
|
||||
mv bin/app.elf app-nanos.elf
|
||||
make clean
|
||||
make -j BOLOS_SDK=$NANOX_SDK CAL_TESTING_KEY=1
|
||||
mv bin/app.elf app-nanox.elf
|
||||
make clean
|
||||
make -j BOLOS_SDK=$NANOSP_SDK CAL_TESTING_KEY=1
|
||||
mv bin/app.elf app-nanosp.elf
|
||||
|
||||
- name: Upload app binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ragger_elfs
|
||||
path: ./app-*.elf
|
||||
|
||||
create_ragger_env:
|
||||
name: Cache Ragger environment
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: APT update
|
||||
run: |
|
||||
sudo apt update
|
||||
|
||||
- name: Create virtual env with dependencies
|
||||
run: |
|
||||
cd tests/ragger
|
||||
python3 -m venv ./venv
|
||||
. ./venv/bin/activate
|
||||
pip3 install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt
|
||||
# Used for the cache key
|
||||
echo "py_deps=$(pip freeze | md5sum | cut -d' ' -f1)" >> $GITHUB_ENV
|
||||
|
||||
- name: Download QEMU
|
||||
run: |
|
||||
sudo apt install --download-only -y qemu-user-static
|
||||
mkdir -p tests/ragger/packages
|
||||
cp /var/cache/apt/archives/*.deb tests/ragger/packages/
|
||||
# Used for the cache key
|
||||
echo "deb_deps=$(find /var/cache/apt/archives/ -maxdepth 0 -type f -name '*.deb' | md5sum | cut -d' ' -f 1)" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-raggenv-${{ env.py_deps }}-${{ env.deb_deps }}
|
||||
path: |
|
||||
tests/ragger/venv/
|
||||
tests/ragger/packages/
|
||||
outputs:
|
||||
py_deps: ${{ env.py_deps }}
|
||||
deb_deps: ${{ env.deb_deps }}
|
||||
|
||||
name: Build app for Ragger tests
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
|
||||
with:
|
||||
upload_app_binaries_artifact: "ragger_elfs"
|
||||
flags: "DEBUG=1 CAL_CI_KEY=1 DOMAIN_NAME_TEST_KEY=1"
|
||||
run_for_devices: '["nanos", "nanox", "nanosp"]'
|
||||
|
||||
jobs-ragger-tests:
|
||||
name: Ragger tests
|
||||
strategy:
|
||||
matrix:
|
||||
model: ["nanos", "nanox", "nanosp"]
|
||||
needs: [build_ragger_elfs, create_ragger_env]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download previously built artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: ragger_elfs
|
||||
path: tmp/
|
||||
|
||||
- name: Put them where they belong
|
||||
run: |
|
||||
mkdir -p tests/ragger/elfs
|
||||
find tmp/ -type f -name '*.elf' -exec cp {} tests/ragger/elfs/ \;
|
||||
|
||||
- name: Get cached environment
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-raggenv-${{ needs.create_ragger_env.outputs.py_deps }}-${{ needs.create_ragger_env.outputs.deb_deps }}
|
||||
path: |
|
||||
tests/ragger/venv/
|
||||
tests/ragger/packages/
|
||||
|
||||
- name: Install QEMU
|
||||
run: |
|
||||
sudo mv tests/ragger/packages/*.deb /var/cache/apt/archives/
|
||||
sudo apt install -y qemu-user-static
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
CAL_SIGNATURE_TEST_KEY: ${{ secrets.CAL_SIGNATURE_TEST_KEY }}
|
||||
run: |
|
||||
cd tests/ragger
|
||||
. ./venv/bin/activate
|
||||
pytest --path ./elfs --model ${{ matrix.model }} -s -v
|
||||
name: Run Ragger tests
|
||||
needs: build_ragger_elfs
|
||||
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1
|
||||
with:
|
||||
download_app_binaries_artifact: "ragger_elfs"
|
||||
test_dir: tests/ragger
|
||||
run_for_devices: '["nanos", "nanox", "nanosp"]'
|
||||
|
||||
2
.github/workflows/lint-workflow.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Lint
|
||||
uses: DoozyX/clang-format-lint-action@v0.14
|
||||
|
||||
15
.github/workflows/sdk-generation.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# by default the action uses fetch-depth = 1, which creates
|
||||
# shallow repositories from which we can't push
|
||||
@@ -42,11 +42,10 @@ jobs:
|
||||
secret: ${{ secrets.CI_BOT_TOKEN }}
|
||||
repository: LedgerHQ/ethereum-plugin-sdk
|
||||
|
||||
- name: Update the SDK submodule in the Ethereum app
|
||||
uses: ./.github/actions/commit-changes
|
||||
- name: Create SDK update pull request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
name: 'ldg-github-ci'
|
||||
files: ethereum-plugin-sdk
|
||||
message: "[update][SDK] Branch ${{ steps.extract_branch.outputs.branch }} | Commit ${GITHUB_SHA}"
|
||||
secret: ${{ secrets.CI_BOT_TOKEN }}
|
||||
repository: LedgerHQ/app-ethereum
|
||||
branch: sdk/update-submodule
|
||||
delete-branch: true
|
||||
title: Update the SDK submodule
|
||||
reviewers: apailler-ledger
|
||||
|
||||
33
CHANGELOG.md
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [1.10.2](https://github.com/ledgerhq/app-ethereum/compare/1.10.1...1.10.2) - 2023-04-24
|
||||
|
||||
### Added
|
||||
|
||||
- (clone) ID4Good
|
||||
- (network) Cronos
|
||||
- (network) Scroll
|
||||
- (network) KCC
|
||||
- (network) Rootstock
|
||||
- (network) Evmos
|
||||
- (network) Metis Andromeda
|
||||
- (network) Kava EVM
|
||||
- (network) Klaytn Cypress
|
||||
- (network) Syscoin
|
||||
- (network) Velas EVM
|
||||
- (network) Boba Network
|
||||
- (network) Energi
|
||||
- Domain names support (LNX / LNS+)
|
||||
|
||||
### Changed
|
||||
|
||||
- Starknet blind signing wording
|
||||
|
||||
### Fixed
|
||||
|
||||
- Missing 44'/60' derivation path for XDC Network
|
||||
- Small visual glitch with EIP-712 verbose mode with the "Review struct" page
|
||||
- Possible overflow with very large transactions
|
||||
- EnergyWebChain ticker
|
||||
- Arbitrum ticker
|
||||
- Error handling on EIP-191 APDUs
|
||||
- Swap transactions handling
|
||||
|
||||
## [1.10.1](https://github.com/ledgerhq/app-ethereum/compare/1.10.0...1.10.1) - 2022-11-09
|
||||
|
||||
### Fixed
|
||||
|
||||
23
Makefile
@@ -34,7 +34,7 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'"
|
||||
|
||||
APPVERSION_M=1
|
||||
APPVERSION_N=10
|
||||
APPVERSION_P=1
|
||||
APPVERSION_P=2
|
||||
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
|
||||
APP_LOAD_FLAGS= --appFlags 0xa40 --dep Ethereum:$(APPVERSION)
|
||||
|
||||
@@ -147,10 +147,23 @@ ifneq ($(TARGET_NAME),TARGET_NANOS)
|
||||
DEFINES += HAVE_EIP712_FULL_SUPPORT
|
||||
endif
|
||||
|
||||
# CryptoAssetsList testing key
|
||||
CAL_TESTING_KEY:=0
|
||||
ifneq ($(CAL_TESTING_KEY),0)
|
||||
DEFINES += HAVE_CAL_TESTING_KEY
|
||||
# CryptoAssetsList key
|
||||
CAL_TEST_KEY:=0
|
||||
CAL_CI_KEY:=0
|
||||
ifneq ($(CAL_TEST_KEY),0)
|
||||
DEFINES += HAVE_CAL_TEST_KEY
|
||||
endif
|
||||
ifneq ($(CAL_CI_KEY),0)
|
||||
DEFINES += HAVE_CAL_CI_KEY
|
||||
endif
|
||||
|
||||
# ENS
|
||||
ifneq ($(TARGET_NAME),TARGET_NANOS)
|
||||
DEFINES += HAVE_DOMAIN_NAME
|
||||
DOMAIN_NAME_TEST_KEY:=0
|
||||
ifneq ($(DOMAIN_NAME_TEST_KEY),0)
|
||||
DEFINES += HAVE_DOMAIN_NAME_TEST_KEY
|
||||
endif
|
||||
endif
|
||||
|
||||
# Enabling debug PRINTF
|
||||
|
||||
@@ -38,6 +38,9 @@ Application version 1.9.19 - 2022-05-17
|
||||
- Add EIP712 STRUCT DEFINITION & EIP712 STRUCT IMPLEMENTATION
|
||||
- Update to SIGN ETH EIP712
|
||||
|
||||
### 1.10.2
|
||||
- Add domain names support
|
||||
|
||||
## About
|
||||
|
||||
This application describes the APDU messages interface to communicate with the Ethereum application.
|
||||
@@ -881,6 +884,82 @@ _Output data_
|
||||
None
|
||||
|
||||
|
||||
### GET CHALLENGE
|
||||
|
||||
#### Description
|
||||
|
||||
Sends a random 32-bit long value. Can prevent replay of signed payloads when the challenge
|
||||
is included in said payload.
|
||||
|
||||
#### Coding
|
||||
|
||||
_Command_
|
||||
|
||||
[width="80%"]
|
||||
|=============================================================
|
||||
| *CLA* | *INS* | *P1* | *P2* | *LC*
|
||||
| E0 | 20 | 00 | 00 | 00
|
||||
|=============================================================
|
||||
|
||||
_Input data_
|
||||
|
||||
None
|
||||
|
||||
_Output data_
|
||||
|
||||
[width="80%"]
|
||||
|===========================================
|
||||
| *Description* | *Length*
|
||||
| Challenge value (BE) | 4
|
||||
|===========================================
|
||||
|
||||
|
||||
### PROVIDE DOMAIN NAME
|
||||
|
||||
#### Description
|
||||
|
||||
This command provides a domain name (like ENS) to be displayed during transactions in place of the address it is associated to.
|
||||
It shall be run just before a transaction involving the associated address that would be displayed on the device.
|
||||
|
||||
The signature is computed on the TLV payload (minus the signature obviously).
|
||||
|
||||
#### Coding
|
||||
|
||||
_Command_
|
||||
|
||||
[width="80%"]
|
||||
|==============================================================
|
||||
| *CLA* | *INS* | *P1* | *P2* | *LC*
|
||||
| E0 | 22 | 01 : first chunk
|
||||
|
||||
00 : following chunk
|
||||
| 00 | 00
|
||||
|==============================================================
|
||||
|
||||
_Input data_
|
||||
|
||||
##### If P1 == first chunk
|
||||
|
||||
[width="80%"]
|
||||
|==========================================
|
||||
| *Description* | *Length (byte)*
|
||||
| Payload length | 2
|
||||
| TLV payload | variable
|
||||
|==========================================
|
||||
|
||||
##### If P1 == following chunk
|
||||
|
||||
[width="80%"]
|
||||
|==========================================
|
||||
| *Description* | *Length (byte)*
|
||||
| TLV payload | variable
|
||||
|==========================================
|
||||
|
||||
_Output data_
|
||||
|
||||
None
|
||||
|
||||
|
||||
## Transport protocol
|
||||
|
||||
### General transport description
|
||||
|
||||
@@ -134,6 +134,17 @@ The following return codes are expected, any other will abort the signing proces
|
||||
* ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized
|
||||
* ETH_PLUGIN_RESULT_FALLBACK : if the signing logic should fallback to the generic one
|
||||
|
||||
There are already defined functions to extract data from a parameter:
|
||||
[source,C]
|
||||
----
|
||||
void copy_address(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size);
|
||||
void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size);
|
||||
|
||||
// Get the value from the beginning of the parameter (right to left) and check if the rest of it is zero
|
||||
bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value);
|
||||
bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value);
|
||||
----
|
||||
|
||||
### ETH_PLUGIN_FINALIZE
|
||||
|
||||
[source,C]
|
||||
|
||||
@@ -96,6 +96,8 @@ tx = UnsignedTransaction(
|
||||
)
|
||||
|
||||
encodedTx = encode(tx, UnsignedTransaction)
|
||||
|
||||
# To test an EIP-1559 transaction, uncomment this line
|
||||
# encodedTx = bytearray.fromhex(
|
||||
# "02ef0306843b9aca008504a817c80082520894b2bb2b958afa2e96dab3f3ce7162b87daea39017872386f26fc1000080c0")
|
||||
|
||||
|
||||
BIN
icons/nanos_app_id4good.gif
Normal file
|
After Width: | Height: | Size: 81 B |
BIN
icons/nanos_app_oasys.gif
Normal file
|
After Width: | Height: | Size: 107 B |
BIN
icons/nanox_app_id4good.gif
Normal file
|
After Width: | Height: | Size: 78 B |
BIN
icons/nanox_app_oasys.gif
Normal file
|
After Width: | Height: | Size: 104 B |
3
makefile_conf/chain/energywebchain.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/246'" --path "44'/60'"
|
||||
DEFINES += CHAINID_UPCASE=\"ENERGYWEBCHAIN\" CHAINID_COINNAME=\"EWT\" CHAIN_KIND=CHAIN_KIND_ENERGYWEBCHAIN CHAIN_ID=246
|
||||
APPNAME = "EnergyWebChain"
|
||||
@@ -1,3 +0,0 @@
|
||||
APP_LOAD_PARAMS += --path "44'/246'" --path "44'/60'"
|
||||
DEFINES += CHAINID_UPCASE=\"EWC\" CHAINID_COINNAME=\"EWC\" CHAIN_KIND=CHAIN_KIND_EWC CHAIN_ID=246
|
||||
APPNAME = "EnergyWebChain"
|
||||
3
makefile_conf/chain/id4good.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/161803'"
|
||||
DEFINES += CHAINID_UPCASE=\"ID4GOOD\" CHAINID_COINNAME=\"A4G\" CHAIN_KIND=CHAIN_KIND_ID4GOOD CHAIN_ID=846000
|
||||
APPNAME = "ID4Good"
|
||||
3
makefile_conf/chain/oasys.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/685'" --path "44'/60'"
|
||||
DEFINES += CHAINID_UPCASE=\"OASYS\" CHAINID_COINNAME=\"OAS\" CHAIN_KIND=CHAIN_KIND_OASYS CHAIN_ID=248
|
||||
APPNAME = "Oasys"
|
||||
@@ -1,3 +1,3 @@
|
||||
APP_LOAD_PARAMS += --path "44'/550'"
|
||||
APP_LOAD_PARAMS += --path "44'/60'" --path "44'/550'"
|
||||
DEFINES += CHAINID_UPCASE=\"XDCNETWORK\" CHAINID_COINNAME=\"XDC\" CHAIN_KIND=CHAIN_KIND_XDCNETWORK CHAIN_ID=50
|
||||
APPNAME = "XDC Network"
|
||||
APPNAME = "XDC Network"
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#define INS_EIP712_STRUCT_DEF 0x1A
|
||||
#define INS_EIP712_STRUCT_IMPL 0x1C
|
||||
#define INS_EIP712_FILTERING 0x1E
|
||||
#define INS_ENS_GET_CHALLENGE 0x20
|
||||
#define INS_ENS_PROVIDE_INFO 0x22
|
||||
#define P1_CONFIRM 0x01
|
||||
#define P1_NON_CONFIRM 0x00
|
||||
#define P2_NO_CHAINCODE 0x00
|
||||
|
||||
@@ -49,7 +49,7 @@ typedef enum chain_kind_e {
|
||||
CHAIN_KIND_TOBALABA,
|
||||
CHAIN_KIND_DEXON,
|
||||
CHAIN_KIND_VOLTA,
|
||||
CHAIN_KIND_EWC,
|
||||
CHAIN_KIND_ENERGYWEBCHAIN,
|
||||
CHAIN_KIND_ARTIS_SIGMA1,
|
||||
CHAIN_KIND_ARTIS_TAU1,
|
||||
CHAIN_KIND_WEBCHAIN,
|
||||
@@ -72,7 +72,9 @@ typedef enum chain_kind_e {
|
||||
CHAIN_KIND_METER,
|
||||
CHAIN_KIND_MULTIVAC,
|
||||
CHAIN_KIND_TECRA,
|
||||
CHAIN_KIND_APOTHEMNETWORK
|
||||
CHAIN_KIND_APOTHEMNETWORK,
|
||||
CHAIN_KIND_ID4GOOD,
|
||||
CHAIN_KIND_OASYS
|
||||
} chain_kind_t;
|
||||
|
||||
typedef struct chain_config_s {
|
||||
|
||||
@@ -63,12 +63,8 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI,
|
||||
queryContractUI->item2 = &tmpCtx.transactionContext.extraInfo[0];
|
||||
}
|
||||
|
||||
strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN);
|
||||
|
||||
queryContractUI->screenIndex = screenIndex;
|
||||
strlcpy(queryContractUI->network_ticker,
|
||||
get_network_ticker(),
|
||||
sizeof(queryContractUI->network_ticker));
|
||||
strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN);
|
||||
queryContractUI->title = title;
|
||||
queryContractUI->titleLength = titleLength;
|
||||
queryContractUI->msg = msg;
|
||||
|
||||
@@ -31,6 +31,4 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
|
||||
// NULL for cached address, or base contract address
|
||||
eth_plugin_result_t eth_plugin_call(int method, void *parameter);
|
||||
|
||||
void plugin_ui_start(void);
|
||||
|
||||
#endif // _ETH_PLUGIN_HANDLER_H_
|
||||
|
||||
@@ -13,7 +13,8 @@ typedef enum {
|
||||
ETH_PLUGIN_INTERFACE_VERSION_2 = 2,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_3 = 3,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_4 = 4,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 5,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_5 = 5,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 6
|
||||
} eth_plugin_interface_version_t;
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <string.h>
|
||||
#include "eth_plugin_internal.h"
|
||||
#include "ethUtils.h" // allzeroes
|
||||
|
||||
bool erc20_plugin_available_check(void);
|
||||
|
||||
@@ -15,6 +16,24 @@ void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size) {
|
||||
memmove(dst, parameter, copy_size);
|
||||
}
|
||||
|
||||
bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value) {
|
||||
if (allzeroes(parameter, PARAMETER_LENGTH - sizeof(uint16_t))) {
|
||||
*value = U2BE(parameter, PARAMETER_LENGTH - sizeof(uint16_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value) {
|
||||
if (allzeroes(parameter, PARAMETER_LENGTH - sizeof(uint32_t))) {
|
||||
*value = U4BE(parameter, PARAMETER_LENGTH - sizeof(uint32_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
void starkware_plugin_call(int message, void* parameters);
|
||||
#endif
|
||||
|
||||
@@ -16,6 +16,11 @@ void copy_parameter(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size);
|
||||
void erc721_plugin_call(int message, void* parameters);
|
||||
void erc1155_plugin_call(int message, void* parameters);
|
||||
|
||||
// Get the value from the beginning of the parameter (right to left) and check if the rest of it is
|
||||
// zero
|
||||
bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value);
|
||||
bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value);
|
||||
|
||||
typedef bool (*PluginAvailableCheck)(void);
|
||||
|
||||
typedef struct internalEthPlugin_t {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "shared_context.h"
|
||||
#include "eth_plugin_handler.h"
|
||||
#include "ux.h"
|
||||
#include "feature_signTx.h"
|
||||
|
||||
void plugin_ui_start() {
|
||||
dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE;
|
||||
dataContext.tokenContext.pluginUiCurrentItem = 0;
|
||||
|
||||
ux_approve_tx(true);
|
||||
}
|
||||
@@ -20,8 +20,7 @@ int handle_get_printable_amount(get_printable_amount_parameters_t* params, chain
|
||||
if (params->is_fee) {
|
||||
uint8_t ticker_len = strnlen(config->coinName, sizeof(config->coinName));
|
||||
memcpy(ticker, config->coinName, ticker_len);
|
||||
ticker[ticker_len] = ' ';
|
||||
ticker[ticker_len + 1] = '\0';
|
||||
ticker[ticker_len] = '\0';
|
||||
decimals = WEI_TO_ETHER;
|
||||
} else {
|
||||
// If the amount is *not* a fee, decimals and ticker are built from the given config
|
||||
|
||||
70
src/main.c
@@ -29,6 +29,8 @@
|
||||
#include "handle_get_printable_amount.h"
|
||||
#include "handle_check_address.h"
|
||||
#include "commands_712.h"
|
||||
#include "challenge.h"
|
||||
#include "domain_name.h"
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
#include "stark_crypto.h"
|
||||
@@ -228,8 +230,8 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
|
||||
case CHAIN_KIND_VOLTA:
|
||||
numTokens = NUM_TOKENS_VOLTA;
|
||||
break;
|
||||
case CHAIN_KIND_EWC:
|
||||
numTokens = NUM_TOKENS_EWC;
|
||||
case CHAIN_KIND_ENERGYWEBCHAIN:
|
||||
numTokens = NUM_TOKENS_ENERGYWEBCHAIN;
|
||||
break;
|
||||
case CHAIN_KIND_WEBCHAIN:
|
||||
numTokens = NUM_TOKENS_WEBCHAIN;
|
||||
@@ -294,6 +296,12 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
|
||||
case CHAIN_KIND_APOTHEMNETWORK:
|
||||
numTokens = NUM_TOKENS_APOTHEMNETWORK;
|
||||
break;
|
||||
case CHAIN_KIND_ID4GOOD:
|
||||
numTokens = NUM_TOKENS_ID4GOOD;
|
||||
break;
|
||||
case CHAIN_KIND_OASYS:
|
||||
numTokens = NUM_TOKENS_OASYS;
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < numTokens; i++) {
|
||||
switch (chainConfig->kind) {
|
||||
@@ -381,8 +389,8 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
|
||||
case CHAIN_KIND_VOLTA:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_VOLTA[i]);
|
||||
break;
|
||||
case CHAIN_KIND_EWC:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_EWC[i]);
|
||||
case CHAIN_KIND_ENERGYWEBCHAIN:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_ENERGYWEBCHAIN[i]);
|
||||
break;
|
||||
case CHAIN_KIND_WEBCHAIN:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_WEBCHAIN[i]);
|
||||
@@ -447,6 +455,12 @@ extraInfo_t *getKnownToken(uint8_t *contractAddress) {
|
||||
case CHAIN_KIND_APOTHEMNETWORK:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_APOTHEMNETWORK[i]);
|
||||
break;
|
||||
case CHAIN_KIND_ID4GOOD:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_ID4GOOD[i]);
|
||||
break;
|
||||
case CHAIN_KIND_OASYS:
|
||||
currentToken = (tokenDefinition_t *) PIC(&TOKENS_OASYS[i]);
|
||||
break;
|
||||
}
|
||||
if (memcmp(currentToken->address, tmpContent.txContent.destination, ADDRESS_LENGTH) == 0) {
|
||||
return currentToken;
|
||||
@@ -667,10 +681,12 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
case INS_SIGN_PERSONAL_MESSAGE:
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
G_io_apdu_buffer[OFFSET_LC]);
|
||||
if (!handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
G_io_apdu_buffer[OFFSET_LC])) {
|
||||
reset_app_context();
|
||||
}
|
||||
break;
|
||||
|
||||
case INS_SIGN_EIP_712_MESSAGE:
|
||||
@@ -735,6 +751,19 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
break;
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
case INS_ENS_GET_CHALLENGE:
|
||||
handle_get_challenge();
|
||||
break;
|
||||
|
||||
case INS_ENS_PROVIDE_INFO:
|
||||
handle_provide_domain_name(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
G_io_apdu_buffer[OFFSET_LC]);
|
||||
break;
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
|
||||
#if 0
|
||||
case 0xFF: // return to dashboard
|
||||
goto return_to_dashboard;
|
||||
@@ -912,7 +941,7 @@ void app_exit() {
|
||||
|
||||
void init_coin_config(chain_config_t *coin_config) {
|
||||
memset(coin_config, 0, sizeof(chain_config_t));
|
||||
strcpy(coin_config->coinName, CHAINID_COINNAME " ");
|
||||
strcpy(coin_config->coinName, CHAINID_COINNAME);
|
||||
coin_config->chainId = CHAIN_ID;
|
||||
coin_config->kind = CHAIN_KIND;
|
||||
}
|
||||
@@ -940,16 +969,22 @@ void coin_main(chain_config_t *coin_config) {
|
||||
G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0);
|
||||
#endif // TARGET_NANOX
|
||||
|
||||
if (N_storage.initialized != 0x01) {
|
||||
if (!N_storage.initialized) {
|
||||
internalStorage_t storage;
|
||||
#ifdef HAVE_ALLOW_DATA
|
||||
storage.dataAllowed = 0x01;
|
||||
storage.dataAllowed = true;
|
||||
#else
|
||||
storage.dataAllowed = 0x00;
|
||||
storage.dataAllowed = false;
|
||||
#endif
|
||||
storage.contractDetails = 0x00;
|
||||
storage.displayNonce = 0x00;
|
||||
storage.initialized = 0x01;
|
||||
storage.contractDetails = false;
|
||||
storage.displayNonce = false;
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
storage.verbose_eip712 = false;
|
||||
#endif
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
storage.verbose_domain_name = false;
|
||||
#endif
|
||||
storage.initialized = true;
|
||||
nvm_write((void *) &N_storage, (void *) &storage, sizeof(internalStorage_t));
|
||||
}
|
||||
|
||||
@@ -963,6 +998,11 @@ void coin_main(chain_config_t *coin_config) {
|
||||
BLE_power(1, "Nano X");
|
||||
#endif // HAVE_BLE
|
||||
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
// to prevent it from having a fixed value at boot
|
||||
roll_challenge();
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
|
||||
app_main();
|
||||
}
|
||||
CATCH(EXCEPTION_IO_RESET) {
|
||||
|
||||
@@ -24,13 +24,16 @@ typedef struct bip32_path_t {
|
||||
} bip32_path_t;
|
||||
|
||||
typedef struct internalStorage_t {
|
||||
unsigned char dataAllowed;
|
||||
unsigned char contractDetails;
|
||||
unsigned char displayNonce;
|
||||
bool dataAllowed;
|
||||
bool contractDetails;
|
||||
bool displayNonce;
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
bool verbose_eip712;
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
uint8_t initialized;
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
bool verbose_domain_name;
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
bool initialized;
|
||||
} internalStorage_t;
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
@@ -50,10 +53,9 @@ typedef enum starkQuantumType_e {
|
||||
|
||||
typedef struct tokenContext_t {
|
||||
char pluginName[PLUGIN_ID_LENGTH];
|
||||
uint8_t pluginStatus;
|
||||
|
||||
uint8_t data[INT256_LENGTH];
|
||||
uint8_t fieldIndex;
|
||||
uint16_t fieldIndex;
|
||||
uint8_t fieldOffset;
|
||||
|
||||
uint8_t pluginUiMaxItems;
|
||||
@@ -65,9 +67,13 @@ typedef struct tokenContext_t {
|
||||
uint8_t contractAddress[ADDRESS_LENGTH];
|
||||
uint8_t methodSelector[SELECTOR_LENGTH];
|
||||
};
|
||||
uint8_t pluginContext[5 * INT256_LENGTH];
|
||||
// This needs to be strictly 4 bytes aligned since pointers to it will be casted as
|
||||
// plugin context struct pointers (structs that contain up to 4 bytes wide elements)
|
||||
uint8_t pluginContext[5 * INT256_LENGTH] __attribute__((aligned(4)));
|
||||
};
|
||||
|
||||
uint8_t pluginStatus;
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
uint8_t quantum[32];
|
||||
uint8_t mintingBlob[32];
|
||||
@@ -77,6 +83,8 @@ typedef struct tokenContext_t {
|
||||
|
||||
} tokenContext_t;
|
||||
|
||||
_Static_assert((offsetof(tokenContext_t, pluginContext) % 4) == 0, "Plugin context not aligned");
|
||||
|
||||
typedef struct publicKeyContext_t {
|
||||
cx_ecfp_public_key_t publicKey;
|
||||
char address[41];
|
||||
@@ -167,7 +175,7 @@ typedef enum {
|
||||
|
||||
#define NETWORK_STRING_MAX_SIZE 16
|
||||
|
||||
typedef struct txStringProperties_t {
|
||||
typedef struct txStringProperties_s {
|
||||
char fullAddress[43];
|
||||
char fullAmount[79]; // 2^256 is 78 digits long
|
||||
char maxFee[50];
|
||||
@@ -182,7 +190,7 @@ typedef struct txStringProperties_t {
|
||||
#endif
|
||||
#define SHARED_CTX_FIELD_2_SIZE 40
|
||||
|
||||
typedef struct strDataTmp_t {
|
||||
typedef struct strDataTmp_s {
|
||||
char tmp[SHARED_CTX_FIELD_1_SIZE];
|
||||
char tmp2[SHARED_CTX_FIELD_2_SIZE];
|
||||
} strDataTmp_t;
|
||||
|
||||
@@ -172,7 +172,7 @@ const tokenDefinition_t const TOKENS_DEXON[NUM_TOKENS_DEXON] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_VOLTA[NUM_TOKENS_VOLTA] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_EWC[NUM_TOKENS_EWC] = {};
|
||||
const tokenDefinition_t const TOKENS_ENERGYWEBCHAIN[NUM_TOKENS_ENERGYWEBCHAIN] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_ARTIS_SIGMA1[NUM_TOKENS_ARTIS_SIGMA1] = {};
|
||||
|
||||
@@ -222,4 +222,6 @@ const tokenDefinition_t const TOKENS_TECRA[NUM_TOKENS_TECRA] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_APOTHEMNETWORK[NUM_TOKENS_APOTHEMNETWORK] = {};
|
||||
|
||||
const tokenDefinition_t const TOKENS_OASYS[NUM_TOKENS_OASYS] = {};
|
||||
|
||||
#endif
|
||||
|
||||
34
src/tokens.h
@@ -21,7 +21,7 @@
|
||||
#include <stdint.h>
|
||||
#include "ethUstream.h"
|
||||
|
||||
#define MAX_TICKER_LEN 12 // 10 characters + ' ' + '\0'
|
||||
#define MAX_TICKER_LEN 11 // 10 characters + '\0'
|
||||
#define MAX_ITEMS 2
|
||||
|
||||
typedef struct tokenDefinition_t {
|
||||
@@ -43,21 +43,31 @@ extern tokenDefinition_t const TOKENS_EXTRA[NUM_TOKENS_EXTRA];
|
||||
|
||||
#ifndef HAVE_TOKENS_LIST
|
||||
|
||||
#if defined(HAVE_CAL_TEST_KEY) && defined(HAVE_CAL_CI_KEY)
|
||||
#error "CAL key contradiction, two alternative keys selected at once"
|
||||
#endif
|
||||
|
||||
static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
|
||||
#ifndef HAVE_CAL_TESTING_KEY
|
||||
// production key 2019-01-11 03:07PM (erc20signer)
|
||||
0x04, 0x5e, 0x6c, 0x10, 0x20, 0xc1, 0x4d, 0xc4, 0x64, 0x42, 0xfe, 0x89, 0xf9, 0x7c,
|
||||
0x0b, 0x68, 0xcd, 0xb1, 0x59, 0x76, 0xdc, 0x24, 0xf2, 0x4c, 0x31, 0x6e, 0x7b, 0x30,
|
||||
0xfe, 0x4e, 0x8c, 0xc7, 0x6b, 0x14, 0x89, 0x15, 0x0c, 0x21, 0x51, 0x4e, 0xbf, 0x44,
|
||||
0x0f, 0xf5, 0xde, 0xa5, 0x39, 0x3d, 0x83, 0xde, 0x53, 0x58, 0xcd, 0x09, 0x8f, 0xce,
|
||||
0x8f, 0xd0, 0xf8, 0x1d, 0xaa, 0x94, 0x97, 0x91, 0x83
|
||||
#else
|
||||
#if defined(HAVE_CAL_TEST_KEY)
|
||||
// test key 2019-01-11 03:07PM (erc20signer)
|
||||
0x04, 0x20, 0xda, 0x62, 0x00, 0x3c, 0x0c, 0xe0, 0x97, 0xe3, 0x36, 0x44, 0xa1, 0x0f,
|
||||
0xe4, 0xc3, 0x04, 0x54, 0x06, 0x9a, 0x44, 0x54, 0xf0, 0xfa, 0x9d, 0x4e, 0x84, 0xf4,
|
||||
0x50, 0x91, 0x42, 0x9b, 0x52, 0x20, 0xaf, 0x9e, 0x35, 0xc0, 0xb2, 0xd9, 0x28, 0x93,
|
||||
0x80, 0x13, 0x73, 0x07, 0xde, 0x4d, 0xd1, 0xd4, 0x18, 0x42, 0x8c, 0xf2, 0x1a, 0x93,
|
||||
0xb3, 0x35, 0x61, 0xbb, 0x09, 0xd8, 0x8f, 0xe5, 0x79
|
||||
#elif defined(HAVE_CAL_CI_KEY)
|
||||
0x04, 0x4c, 0xca, 0x8f, 0xad, 0x49, 0x6a, 0xa5, 0x04, 0x0a, 0x00, 0xa7, 0xeb, 0x2f,
|
||||
0x5c, 0xc3, 0xb8, 0x53, 0x76, 0xd8, 0x8b, 0xa1, 0x47, 0xa7, 0xd7, 0x05, 0x4a, 0x99,
|
||||
0xc6, 0x40, 0x56, 0x18, 0x87, 0xfe, 0x17, 0xa0, 0x96, 0xe3, 0x6c, 0x3b, 0x52, 0x3b,
|
||||
0x24, 0x4f, 0x3e, 0x2f, 0xf7, 0xf8, 0x40, 0xae, 0x26, 0xc4, 0xe7, 0x7a, 0xd3, 0xbc,
|
||||
0x73, 0x9a, 0xf5, 0xde, 0x6f, 0x2d, 0x77, 0xa7, 0xb6
|
||||
#else
|
||||
// production key 2019-01-11 03:07PM (erc20signer)
|
||||
0x04, 0x5e, 0x6c, 0x10, 0x20, 0xc1, 0x4d, 0xc4, 0x64, 0x42, 0xfe, 0x89, 0xf9, 0x7c,
|
||||
0x0b, 0x68, 0xcd, 0xb1, 0x59, 0x76, 0xdc, 0x24, 0xf2, 0x4c, 0x31, 0x6e, 0x7b, 0x30,
|
||||
0xfe, 0x4e, 0x8c, 0xc7, 0x6b, 0x14, 0x89, 0x15, 0x0c, 0x21, 0x51, 0x4e, 0xbf, 0x44,
|
||||
0x0f, 0xf5, 0xde, 0xa5, 0x39, 0x3d, 0x83, 0xde, 0x53, 0x58, 0xcd, 0x09, 0x8f, 0xce,
|
||||
0x8f, 0xd0, 0xf8, 0x1d, 0xaa, 0x94, 0x97, 0x91, 0x83
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -90,7 +100,7 @@ static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
|
||||
#define NUM_TOKENS_TOBALABA 0
|
||||
#define NUM_TOKENS_DEXON 0
|
||||
#define NUM_TOKENS_VOLTA 0
|
||||
#define NUM_TOKENS_EWC 0
|
||||
#define NUM_TOKENS_ENERGYWEBCHAIN 0
|
||||
#define NUM_TOKENS_ARTIS_SIGMA1 0
|
||||
#define NUM_TOKENS_ARTIS_TAU1 0
|
||||
#define NUM_TOKENS_WEBCHAIN 0
|
||||
@@ -115,6 +125,7 @@ static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
|
||||
#define NUM_TOKENS_MULTIVAC 0
|
||||
#define NUM_TOKENS_TECRA 0
|
||||
#define NUM_TOKENS_APOTHEMNETWORK 0
|
||||
#define NUM_TOKENS_OASYS 0
|
||||
|
||||
extern tokenDefinition_t const TOKENS_AKROMA[NUM_TOKENS_AKROMA];
|
||||
extern tokenDefinition_t const TOKENS_ELLAISM[NUM_TOKENS_ELLAISM];
|
||||
@@ -141,7 +152,7 @@ extern tokenDefinition_t const TOKENS_TOMOCHAIN[NUM_TOKENS_TOMOCHAIN];
|
||||
extern tokenDefinition_t const TOKENS_TOBALABA[NUM_TOKENS_TOBALABA];
|
||||
extern tokenDefinition_t const TOKENS_DEXON[NUM_TOKENS_DEXON];
|
||||
extern tokenDefinition_t const TOKENS_VOLTA[NUM_TOKENS_VOLTA];
|
||||
extern tokenDefinition_t const TOKENS_EWC[NUM_TOKENS_EWC];
|
||||
extern tokenDefinition_t const TOKENS_ENERGYWEBCHAIN[NUM_TOKENS_ENERGYWEBCHAIN];
|
||||
extern tokenDefinition_t const TOKENS_ARTIS_SIGMA1[NUM_TOKENS_ARTIS_SIGMA1];
|
||||
extern tokenDefinition_t const TOKENS_ARTIS_TAU1[NUM_TOKENS_ARTIS_TAU1];
|
||||
extern tokenDefinition_t const TOKENS_WEBCHAIN[NUM_TOKENS_WEBCHAIN];
|
||||
@@ -165,6 +176,7 @@ extern tokenDefinition_t const TOKENS_METER[NUM_TOKENS_METER];
|
||||
extern tokenDefinition_t const TOKENS_MULTIVAC[NUM_TOKENS_MULTIVAC];
|
||||
extern tokenDefinition_t const TOKENS_TECRA[NUM_TOKENS_TECRA];
|
||||
extern tokenDefinition_t const TOKENS_APOTHEMNETWORK[NUM_TOKENS_APOTHEMNETWORK];
|
||||
extern tokenDefinition_t const TOKENS_OASYS[NUM_TOKENS_OASYS];
|
||||
|
||||
#endif /* HAVE_TOKENS_LIST */
|
||||
|
||||
|
||||
@@ -132,6 +132,9 @@ void amountToString(const uint8_t *amount,
|
||||
uint8_t ticker_len = strnlen(ticker, MAX_TICKER_LEN);
|
||||
|
||||
memcpy(out_buffer, ticker, MIN(out_buffer_size, ticker_len));
|
||||
if (ticker_len > 0) {
|
||||
out_buffer[ticker_len++] = ' ';
|
||||
}
|
||||
|
||||
if (adjustDecimals(tmp_buffer,
|
||||
amount_len,
|
||||
@@ -155,8 +158,7 @@ bool parse_swap_config(const uint8_t *config, uint8_t config_len, char *ticker,
|
||||
}
|
||||
memcpy(ticker, config + offset, ticker_len);
|
||||
offset += ticker_len;
|
||||
ticker[ticker_len] = ' ';
|
||||
ticker[ticker_len + 1] = '\0';
|
||||
ticker[ticker_len] = '\0';
|
||||
|
||||
if (config_len - offset < 1) {
|
||||
return false;
|
||||
|
||||
17
src_bagl/ui_domain_name.c
Normal file
@@ -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 BUF_INCREMENT (MAX(strlen(ENABLED_STR), strlen(DISABLED_STR)) + 1)
|
||||
|
||||
void display_settings(const ux_flow_step_t* const start_step);
|
||||
void switch_settings_blind_signing(void);
|
||||
void switch_settings_display_data(void);
|
||||
void switch_settings_display_nonce(void);
|
||||
// Reuse the strings.common.fullAmount buffer for settings displaying.
|
||||
// No risk of collision as this buffer is unused in the settings menu
|
||||
#define SETTING_BLIND_SIGNING_STATE (strings.common.fullAmount)
|
||||
#define SETTING_DISPLAY_DATA_STATE (strings.common.fullAmount + (BUF_INCREMENT * 1))
|
||||
#define SETTING_DISPLAY_NONCE_STATE (strings.common.fullAmount + (BUF_INCREMENT * 2))
|
||||
#define SETTING_VERBOSE_EIP712_STATE (strings.common.fullAmount + (BUF_INCREMENT * 3))
|
||||
#define SETTING_VERBOSE_DOMAIN_NAME_STATE (strings.common.fullAmount + (BUF_INCREMENT * 4))
|
||||
|
||||
#define BOOL_TO_STATE_STR(b) (b ? ENABLED_STR : DISABLED_STR)
|
||||
|
||||
static void display_settings(const ux_flow_step_t* const start_step);
|
||||
static void switch_settings_blind_signing(void);
|
||||
static void switch_settings_display_data(void);
|
||||
static void switch_settings_display_nonce(void);
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
void switch_settings_verbose_eip712(void);
|
||||
static void switch_settings_verbose_eip712(void);
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
static void switch_settings_verbose_domain_name(void);
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// clang-format off
|
||||
@@ -75,7 +88,7 @@ UX_STEP_CB(
|
||||
"Transaction",
|
||||
"blind signing",
|
||||
#endif
|
||||
strings.common.fullAddress
|
||||
SETTING_BLIND_SIGNING_STATE
|
||||
});
|
||||
|
||||
UX_STEP_CB(
|
||||
@@ -95,7 +108,7 @@ UX_STEP_CB(
|
||||
"Show contract data",
|
||||
"details",
|
||||
#endif
|
||||
strings.common.fullAddress + BUF_INCREMENT
|
||||
SETTING_DISPLAY_DATA_STATE
|
||||
});
|
||||
|
||||
UX_STEP_CB(
|
||||
@@ -115,7 +128,7 @@ UX_STEP_CB(
|
||||
"Show account nonce",
|
||||
"in transactions",
|
||||
#endif
|
||||
strings.common.fullAddress + (BUF_INCREMENT * 2)
|
||||
SETTING_DISPLAY_NONCE_STATE
|
||||
});
|
||||
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
@@ -127,10 +140,23 @@ UX_STEP_CB(
|
||||
"Verbose EIP-712",
|
||||
"Ignore filtering &",
|
||||
"display raw content",
|
||||
strings.common.fullAddress + (BUF_INCREMENT * 3)
|
||||
SETTING_VERBOSE_EIP712_STATE
|
||||
});
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
UX_STEP_CB(
|
||||
ux_settings_flow_verbose_domain_name_step,
|
||||
bnnn,
|
||||
switch_settings_verbose_domain_name(),
|
||||
{
|
||||
"Verbose domains",
|
||||
"Show",
|
||||
"resolved address",
|
||||
SETTING_VERBOSE_DOMAIN_NAME_STATE
|
||||
});
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
|
||||
|
||||
UX_STEP_CB(
|
||||
ux_settings_flow_back_step,
|
||||
@@ -149,54 +175,61 @@ UX_FLOW(ux_settings_flow,
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
&ux_settings_flow_verbose_eip712_step,
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
&ux_settings_flow_verbose_domain_name_step,
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
&ux_settings_flow_back_step);
|
||||
|
||||
void display_settings(const ux_flow_step_t* const start_step) {
|
||||
bool settings[] = {N_storage.dataAllowed,
|
||||
N_storage.contractDetails,
|
||||
N_storage.displayNonce,
|
||||
static void display_settings(const ux_flow_step_t* const start_step) {
|
||||
strlcpy(SETTING_BLIND_SIGNING_STATE, BOOL_TO_STATE_STR(N_storage.dataAllowed), BUF_INCREMENT);
|
||||
strlcpy(SETTING_DISPLAY_DATA_STATE,
|
||||
BOOL_TO_STATE_STR(N_storage.contractDetails),
|
||||
BUF_INCREMENT);
|
||||
strlcpy(SETTING_DISPLAY_NONCE_STATE, BOOL_TO_STATE_STR(N_storage.displayNonce), BUF_INCREMENT);
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
N_storage.verbose_eip712
|
||||
strlcpy(SETTING_VERBOSE_EIP712_STATE,
|
||||
BOOL_TO_STATE_STR(N_storage.verbose_eip712),
|
||||
BUF_INCREMENT);
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
};
|
||||
uint8_t offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(settings); ++i) {
|
||||
strlcpy(strings.common.fullAddress + offset,
|
||||
(settings[i] ? ENABLED_STR : DISABLED_STR),
|
||||
sizeof(strings.common.fullAddress) - offset);
|
||||
offset += BUF_INCREMENT;
|
||||
}
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
strlcpy(SETTING_VERBOSE_DOMAIN_NAME_STATE,
|
||||
BOOL_TO_STATE_STR(N_storage.verbose_domain_name),
|
||||
BUF_INCREMENT);
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
|
||||
ux_flow_init(0, ux_settings_flow, start_step);
|
||||
}
|
||||
|
||||
void switch_settings_blind_signing(void) {
|
||||
uint8_t value = (N_storage.dataAllowed ? 0 : 1);
|
||||
nvm_write((void*) &N_storage.dataAllowed, (void*) &value, sizeof(uint8_t));
|
||||
display_settings(&ux_settings_flow_blind_signing_step);
|
||||
static void toggle_setting(volatile bool* setting, const ux_flow_step_t* ui_step) {
|
||||
bool value = !*setting;
|
||||
nvm_write((void*) setting, (void*) &value, sizeof(value));
|
||||
display_settings(ui_step);
|
||||
}
|
||||
|
||||
void switch_settings_display_data(void) {
|
||||
uint8_t value = (N_storage.contractDetails ? 0 : 1);
|
||||
nvm_write((void*) &N_storage.contractDetails, (void*) &value, sizeof(uint8_t));
|
||||
display_settings(&ux_settings_flow_display_data_step);
|
||||
static void switch_settings_blind_signing(void) {
|
||||
toggle_setting(&N_storage.dataAllowed, &ux_settings_flow_blind_signing_step);
|
||||
}
|
||||
|
||||
void switch_settings_display_nonce(void) {
|
||||
uint8_t value = (N_storage.displayNonce ? 0 : 1);
|
||||
nvm_write((void*) &N_storage.displayNonce, (void*) &value, sizeof(uint8_t));
|
||||
display_settings(&ux_settings_flow_display_nonce_step);
|
||||
static void switch_settings_display_data(void) {
|
||||
toggle_setting(&N_storage.contractDetails, &ux_settings_flow_display_data_step);
|
||||
}
|
||||
|
||||
static void switch_settings_display_nonce(void) {
|
||||
toggle_setting(&N_storage.displayNonce, &ux_settings_flow_display_nonce_step);
|
||||
}
|
||||
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
void switch_settings_verbose_eip712(void) {
|
||||
bool value = !N_storage.verbose_eip712;
|
||||
nvm_write((void*) &N_storage.verbose_eip712, (void*) &value, sizeof(value));
|
||||
display_settings(&ux_settings_flow_verbose_eip712_step);
|
||||
static void switch_settings_verbose_eip712(void) {
|
||||
toggle_setting(&N_storage.verbose_eip712, &ux_settings_flow_verbose_eip712_step);
|
||||
}
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
static void switch_settings_verbose_domain_name(void) {
|
||||
toggle_setting(&N_storage.verbose_domain_name, &ux_settings_flow_verbose_domain_name_step);
|
||||
}
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// clang-format off
|
||||
#ifdef TARGET_NANOS
|
||||
|
||||
@@ -18,6 +18,9 @@ static void dummy_pre_cb(void) {
|
||||
|
||||
static void dummy_post_cb(void) {
|
||||
if (ui_pos == UI_191_POS_QUESTION) {
|
||||
// temporarily disable button clicks, they will be re-enabled as soon as new data
|
||||
// is received and the page is redrawn with ux_flow_init()
|
||||
G_ux.stack[0].button_push_callback = NULL;
|
||||
continue_displaying_message();
|
||||
} else // UI_191_END
|
||||
{
|
||||
@@ -55,6 +58,7 @@ UX_STEP_CB(
|
||||
#else
|
||||
nnn,
|
||||
#endif
|
||||
G_ux.stack[0].button_push_callback = NULL; // disable button clicks
|
||||
skip_rest_of_message(),
|
||||
{
|
||||
#ifndef TARGET_NANOS
|
||||
|
||||
@@ -7,19 +7,25 @@ enum { UI_712_POS_REVIEW, UI_712_POS_END };
|
||||
static uint8_t ui_pos;
|
||||
|
||||
static void dummy_cb(void) {
|
||||
if (!ui_712_next_field()) {
|
||||
if (ui_pos == UI_712_POS_REVIEW) {
|
||||
ux_flow_next();
|
||||
ui_pos = UI_712_POS_END;
|
||||
} else // UI_712_POS_END
|
||||
{
|
||||
ux_flow_prev();
|
||||
ui_pos = UI_712_POS_REVIEW;
|
||||
}
|
||||
} else {
|
||||
// temporarily disable button clicks, they will be re-enabled as soon as new data
|
||||
// is received and the page is redrawn with ux_flow_init()
|
||||
G_ux.stack[0].button_push_callback = NULL;
|
||||
switch (ui_712_next_field()) {
|
||||
case EIP712_NO_MORE_FIELD:
|
||||
if (ui_pos == UI_712_POS_REVIEW) {
|
||||
ux_flow_next();
|
||||
ui_pos = UI_712_POS_END;
|
||||
} else // UI_712_POS_END
|
||||
{
|
||||
ux_flow_prev();
|
||||
ui_pos = UI_712_POS_REVIEW;
|
||||
}
|
||||
break;
|
||||
case EIP712_FIELD_INCOMING:
|
||||
// temporarily disable button clicks, they will be re-enabled as soon as new data
|
||||
// is received and the page is redrawn with ux_flow_init()
|
||||
G_ux.stack[0].button_push_callback = NULL;
|
||||
break;
|
||||
case EIP712_FIELD_LATER:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "ui_plugin.h"
|
||||
#include "common_ui.h"
|
||||
#include "plugins.h"
|
||||
#include "domain_name.h"
|
||||
#include "ui_domain_name.h"
|
||||
|
||||
// clang-format off
|
||||
UX_STEP_NOCB(
|
||||
@@ -217,7 +219,19 @@ void ux_approve_tx(bool fromPlugin) {
|
||||
} else {
|
||||
// We're in a regular transaction, just show the amount and the address
|
||||
ux_approval_tx_flow[step++] = &ux_approval_amount_step;
|
||||
ux_approval_tx_flow[step++] = &ux_approval_address_step;
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
uint64_t chain_id = get_chain_id();
|
||||
if (has_domain_name(&chain_id, tmpContent.txContent.destination)) {
|
||||
ux_approval_tx_flow[step++] = &ux_domain_name_step;
|
||||
if (N_storage.verbose_domain_name) {
|
||||
ux_approval_tx_flow[step++] = &ux_approval_address_step;
|
||||
}
|
||||
} else {
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
ux_approval_tx_flow[step++] = &ux_approval_address_step;
|
||||
#ifdef HAVE_DOMAIN_NAME
|
||||
}
|
||||
#endif // HAVE_DOMAIN_NAME
|
||||
}
|
||||
|
||||
if (N_storage.displayNonce) {
|
||||
@@ -235,4 +249,4 @@ void ux_approve_tx(bool fromPlugin) {
|
||||
ux_approval_tx_flow[step++] = FLOW_END_STEP;
|
||||
|
||||
ux_flow_init(0, ux_approval_tx_flow, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ UX_STEP_NOCB(ux_stark_unsafe_sign_1_step,
|
||||
pnn,
|
||||
{
|
||||
&C_icon_warning,
|
||||
"Unsafe",
|
||||
"Stark Sign",
|
||||
"StarkNet",
|
||||
"Blind Sign",
|
||||
});
|
||||
|
||||
UX_STEP_NOCB_INIT(
|
||||
@@ -36,7 +36,7 @@ UX_STEP_NOCB_INIT(
|
||||
bnnn_paging,
|
||||
stark_unsafe_sign_display_hash(),
|
||||
{
|
||||
.title = "Hash",
|
||||
.title = "Tx Hash",
|
||||
.text = strings.tmp.tmp
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ bool adjustDecimals(const char *src,
|
||||
size_t targetLength,
|
||||
uint8_t decimals);
|
||||
|
||||
static __attribute__((no_instrument_function)) inline int allzeroes(void *buf, size_t n) {
|
||||
static __attribute__((no_instrument_function)) inline int allzeroes(const void *buf, size_t n) {
|
||||
uint8_t *p = (uint8_t *) buf;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (p[i]) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#ifdef HAVE_EIP712_FULL_SUPPORT
|
||||
|
||||
#include "hash_bytes.h"
|
||||
|
||||
/**
|
||||
@@ -9,7 +7,7 @@
|
||||
* @param[in] n number of bytes to hash
|
||||
* @param[in] hash_ctx pointer to the hashing context
|
||||
*/
|
||||
void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *const hash_ctx) {
|
||||
void hash_nbytes(const uint8_t *bytes_ptr, size_t n, cx_hash_t *hash_ctx) {
|
||||
cx_hash(hash_ctx, 0, bytes_ptr, n, NULL, 0);
|
||||
}
|
||||
|
||||
@@ -19,8 +17,6 @@ void hash_nbytes(const uint8_t *const bytes_ptr, uint8_t n, cx_hash_t *const has
|
||||
* @param[in] byte byte to hash
|
||||
* @param[in] hash_ctx pointer to the hashing context
|
||||
*/
|
||||
void hash_byte(uint8_t byte, cx_hash_t *const hash_ctx) {
|
||||
void hash_byte(uint8_t byte, cx_hash_t *hash_ctx) {
|
||||
hash_nbytes(&byte, 1, hash_ctx);
|
||||
}
|
||||
|
||||
#endif // HAVE_EIP712_FULL_SUPPORT
|
||||
10
src_common/hash_bytes.h
Normal file
@@ -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_
|
||||
@@ -8,47 +8,61 @@
|
||||
#include "utils.h"
|
||||
|
||||
// Mappping of chain ids to networks.
|
||||
const network_info_t NETWORK_MAPPING[] = {
|
||||
{.chain_id = 1, .name = "Ethereum", .ticker = "ETH "},
|
||||
{.chain_id = 3, .name = "Ropsten", .ticker = "ETH "},
|
||||
{.chain_id = 4, .name = "Rinkeby", .ticker = "ETH "},
|
||||
{.chain_id = 5, .name = "Goerli", .ticker = "ETH "},
|
||||
{.chain_id = 10, .name = "Optimism", .ticker = "ETH "},
|
||||
{.chain_id = 42, .name = "Kovan", .ticker = "ETH "},
|
||||
{.chain_id = 56, .name = "BSC", .ticker = "BNB "},
|
||||
{.chain_id = 100, .name = "xDai", .ticker = "xDAI "},
|
||||
{.chain_id = 137, .name = "Polygon", .ticker = "MATIC "},
|
||||
{.chain_id = 250, .name = "Fantom", .ticker = "FTM "},
|
||||
{.chain_id = 42161, .name = "Arbitrum", .ticker = "AETH "},
|
||||
{.chain_id = 42220, .name = "Celo", .ticker = "CELO "},
|
||||
{.chain_id = 43114, .name = "Avalanche", .ticker = "AVAX "},
|
||||
{.chain_id = 44787, .name = "Celo Alfajores", .ticker = "aCELO "},
|
||||
{.chain_id = 62320, .name = "Celo Baklava", .ticker = "bCELO "},
|
||||
{.chain_id = 11297108109, .name = "Palm Network", .ticker = "PALM "},
|
||||
{.chain_id = 1818, .name = "Cube", .ticker = "CUBE "},
|
||||
{.chain_id = 336, .name = "Shiden", .ticker = "SDN "},
|
||||
{.chain_id = 592, .name = "Astar", .ticker = "ASTR "},
|
||||
{.chain_id = 50, .name = "XDC", .ticker = "XDC "},
|
||||
{.chain_id = 82, .name = "Meter", .ticker = "MTR "},
|
||||
{.chain_id = 62621, .name = "Multivac", .ticker = "MTV "},
|
||||
{.chain_id = 20531812, .name = "Tecra", .ticker = "TCR "},
|
||||
{.chain_id = 20531811, .name = "TecraTestnet", .ticker = "TCR "},
|
||||
{.chain_id = 51, .name = "Apothemnetwork", .ticker = "XDC "},
|
||||
{.chain_id = 199, .name = "BTTC", .ticker = "BTT "},
|
||||
{.chain_id = 1030, .name = "Conflux", .ticker = "CFX "},
|
||||
{.chain_id = 61, .name = "Ethereum Classic", .ticker = "ETC "},
|
||||
{.chain_id = 246, .name = "EnergyWebChain", .ticker = "EWC "},
|
||||
{.chain_id = 14, .name = "Flare", .ticker = "FLR "},
|
||||
{.chain_id = 16, .name = "Flare Coston", .ticker = "FLR "},
|
||||
{.chain_id = 24, .name = "KardiaChain", .ticker = "KAI "},
|
||||
{.chain_id = 1284, .name = "Moonbeam", .ticker = "GLMR "},
|
||||
{.chain_id = 1285, .name = "Moonriver", .ticker = "MOVR "},
|
||||
{.chain_id = 66, .name = "OKXChain", .ticker = "OKT "},
|
||||
{.chain_id = 99, .name = "POA", .ticker = "POA "},
|
||||
{.chain_id = 7341, .name = "Shyft", .ticker = "SHFT "},
|
||||
{.chain_id = 19, .name = "Songbird", .ticker = "SGB "},
|
||||
{.chain_id = 73799, .name = "Volta", .ticker = "VOLTA "},
|
||||
{.chain_id = 25, .name = "Cronos", .ticker = "CRO "}};
|
||||
static const network_info_t NETWORK_MAPPING[] = {
|
||||
{.chain_id = 1, .name = "Ethereum", .ticker = "ETH"},
|
||||
{.chain_id = 3, .name = "Ropsten", .ticker = "ETH"},
|
||||
{.chain_id = 4, .name = "Rinkeby", .ticker = "ETH"},
|
||||
{.chain_id = 5, .name = "Goerli", .ticker = "ETH"},
|
||||
{.chain_id = 10, .name = "Optimism", .ticker = "ETH"},
|
||||
{.chain_id = 42, .name = "Kovan", .ticker = "ETH"},
|
||||
{.chain_id = 56, .name = "BSC", .ticker = "BNB"},
|
||||
{.chain_id = 100, .name = "xDai", .ticker = "xDAI"},
|
||||
{.chain_id = 137, .name = "Polygon", .ticker = "MATIC"},
|
||||
{.chain_id = 250, .name = "Fantom", .ticker = "FTM"},
|
||||
{.chain_id = 42161, .name = "Arbitrum", .ticker = "ETH"},
|
||||
{.chain_id = 42220, .name = "Celo", .ticker = "CELO"},
|
||||
{.chain_id = 43114, .name = "Avalanche", .ticker = "AVAX"},
|
||||
{.chain_id = 44787, .name = "Celo Alfajores", .ticker = "aCELO"},
|
||||
{.chain_id = 62320, .name = "Celo Baklava", .ticker = "bCELO"},
|
||||
{.chain_id = 11297108109, .name = "Palm Network", .ticker = "PALM"},
|
||||
{.chain_id = 1818, .name = "Cube", .ticker = "CUBE"},
|
||||
{.chain_id = 336, .name = "Shiden", .ticker = "SDN"},
|
||||
{.chain_id = 592, .name = "Astar", .ticker = "ASTR"},
|
||||
{.chain_id = 50, .name = "XDC", .ticker = "XDC"},
|
||||
{.chain_id = 82, .name = "Meter", .ticker = "MTR"},
|
||||
{.chain_id = 62621, .name = "Multivac", .ticker = "MTV"},
|
||||
{.chain_id = 20531812, .name = "Tecra", .ticker = "TCR"},
|
||||
{.chain_id = 20531811, .name = "TecraTestnet", .ticker = "TCR"},
|
||||
{.chain_id = 51, .name = "Apothemnetwork", .ticker = "XDC"},
|
||||
{.chain_id = 199, .name = "BTTC", .ticker = "BTT"},
|
||||
{.chain_id = 1030, .name = "Conflux", .ticker = "CFX"},
|
||||
{.chain_id = 61, .name = "Ethereum Classic", .ticker = "ETC"},
|
||||
{.chain_id = 246, .name = "EnergyWebChain", .ticker = "EWT"},
|
||||
{.chain_id = 14, .name = "Flare", .ticker = "FLR"},
|
||||
{.chain_id = 16, .name = "Flare Coston", .ticker = "FLR"},
|
||||
{.chain_id = 24, .name = "KardiaChain", .ticker = "KAI"},
|
||||
{.chain_id = 1284, .name = "Moonbeam", .ticker = "GLMR"},
|
||||
{.chain_id = 1285, .name = "Moonriver", .ticker = "MOVR"},
|
||||
{.chain_id = 66, .name = "OKXChain", .ticker = "OKT"},
|
||||
{.chain_id = 99, .name = "POA", .ticker = "POA"},
|
||||
{.chain_id = 7341, .name = "Shyft", .ticker = "SHFT"},
|
||||
{.chain_id = 19, .name = "Songbird", .ticker = "SGB"},
|
||||
{.chain_id = 73799, .name = "Volta", .ticker = "VOLTA"},
|
||||
{.chain_id = 25, .name = "Cronos", .ticker = "CRO"},
|
||||
{.chain_id = 534354, .name = "Scroll (Pre-Alpha)", .ticker = "SCR"},
|
||||
{.chain_id = 534353, .name = "Scroll (Goerli)", .ticker = "SCR"},
|
||||
{.chain_id = 534352, .name = "Scroll", .ticker = "SCR"},
|
||||
{.chain_id = 321, .name = "KCC", .ticker = "KCS"},
|
||||
{.chain_id = 30, .name = "Rootstock", .ticker = "RBTC"},
|
||||
{.chain_id = 9001, .name = "Evmos", .ticker = "EVMOS"},
|
||||
{.chain_id = 1088, .name = "Metis Andromeda", .ticker = "METIS"},
|
||||
{.chain_id = 2222, .name = "Kava EVM", .ticker = "KAVA"},
|
||||
{.chain_id = 8217, .name = "Klaytn Cypress", .ticker = "KLAY"},
|
||||
{.chain_id = 57, .name = "Syscoin", .ticker = "SYS"},
|
||||
{.chain_id = 106, .name = "Velas EVM", .ticker = "VLX"},
|
||||
{.chain_id = 288, .name = "Boba Network", .ticker = "ETH"},
|
||||
{.chain_id = 39797, .name = "Energi", .ticker = "NRG"},
|
||||
{.chain_id = 248, .name = "Oasys", .ticker = "OAS"}};
|
||||
|
||||
uint64_t get_chain_id(void) {
|
||||
uint64_t chain_id = 0;
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "tokens.h"
|
||||
#include "shared_context.h"
|
||||
|
||||
#define MAX_NETWORK_TICKER_LEN 8
|
||||
|
||||
typedef struct network_info_s {
|
||||
const char *name;
|
||||
const char *ticker;
|
||||
|
||||
@@ -230,18 +230,20 @@ bool tostring256(const uint256_t *const number,
|
||||
UPPER(LOWER(base)) = 0;
|
||||
LOWER(LOWER(base)) = baseParam;
|
||||
uint32_t offset = 0;
|
||||
if ((baseParam < 2) || (baseParam > 16)) {
|
||||
if ((outLength == 0) || (baseParam < 2) || (baseParam > 16)) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
if (offset > (outLength - 1)) {
|
||||
return false;
|
||||
}
|
||||
divmod256(&rDiv, &base, &rDiv, &rMod);
|
||||
out[offset++] = HEXDIGITS[(uint8_t) LOWER(LOWER(rMod))];
|
||||
} while (!zero256(&rDiv));
|
||||
} while (!zero256(&rDiv) && (offset < outLength));
|
||||
|
||||
if (offset > (outLength - 1)) {
|
||||
if (offset == outLength) { // destination buffer too small
|
||||
if (outLength > 3) {
|
||||
strlcpy(out, "...", outLength);
|
||||
} else {
|
||||
out[0] = '\0';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -42,8 +42,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
}
|
||||
cx_hash((cx_hash_t *) &sha256, 0, workBuffer + offset, tickerLength, NULL, 0);
|
||||
memmove(token->ticker, workBuffer + offset, tickerLength);
|
||||
token->ticker[tickerLength] = ' ';
|
||||
token->ticker[tickerLength + 1] = '\0';
|
||||
token->ticker[tickerLength] = '\0';
|
||||
offset += tickerLength;
|
||||
dataLength -= tickerLength;
|
||||
|
||||
@@ -136,8 +135,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
}
|
||||
cx_hash_sha256(workBuffer + offset, tickerLength + 20 + 4 + 4, hash, 32);
|
||||
memmove(token->ticker, workBuffer + offset, tickerLength);
|
||||
token->ticker[tickerLength] = ' ';
|
||||
token->ticker[tickerLength + 1] = '\0';
|
||||
token->ticker[tickerLength] = '\0';
|
||||
offset += tickerLength;
|
||||
dataLength -= tickerLength;
|
||||
memmove(token->address, workBuffer + offset, 20);
|
||||
|
||||
@@ -21,6 +21,9 @@ static const char SIGN_MAGIC[] =
|
||||
* @param[in] sw status word
|
||||
*/
|
||||
static void apdu_reply(uint16_t sw) {
|
||||
if ((sw != APDU_RESPONSE_OK) && states.ui_started) {
|
||||
ui_idle();
|
||||
}
|
||||
G_io_apdu_buffer[0] = (sw >> 8) & 0xff;
|
||||
G_io_apdu_buffer[1] = sw & 0xff;
|
||||
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
|
||||
@@ -90,18 +93,18 @@ static void reset_ui_buffer(void) {
|
||||
*/
|
||||
static const uint8_t *first_apdu_data(const uint8_t *data, uint8_t *length) {
|
||||
if (appState != APP_STATE_IDLE) {
|
||||
reset_app_context();
|
||||
apdu_reply(APDU_RESPONSE_CONDITION_NOT_SATISFIED);
|
||||
}
|
||||
appState = APP_STATE_SIGNING_MESSAGE;
|
||||
data = parseBip32(data, length, &tmpCtx.messageSigningContext.bip32);
|
||||
if (data == NULL) {
|
||||
apdu_reply(0x6a80);
|
||||
apdu_reply(APDU_RESPONSE_INVALID_DATA);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*length < sizeof(uint32_t)) {
|
||||
PRINTF("Invalid data\n");
|
||||
apdu_reply(0x6a80);
|
||||
apdu_reply(APDU_RESPONSE_INVALID_DATA);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -140,7 +143,7 @@ static bool feed_hash(const uint8_t *const data, const uint8_t length) {
|
||||
PRINTF("Error: Length mismatch ! (%u > %u)!\n",
|
||||
length,
|
||||
tmpCtx.messageSigningContext.remainingLength);
|
||||
apdu_reply(0x6a80);
|
||||
apdu_reply(APDU_RESPONSE_INVALID_DATA);
|
||||
return false;
|
||||
}
|
||||
cx_hash((cx_hash_t *) &global_sha3, 0, data, length, NULL, 0);
|
||||
@@ -194,7 +197,7 @@ static void feed_display(void) {
|
||||
}
|
||||
|
||||
if ((unprocessed_length() == 0) && (tmpCtx.messageSigningContext.remainingLength > 0)) {
|
||||
apdu_reply(0x9000);
|
||||
apdu_reply(APDU_RESPONSE_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +225,11 @@ bool handleSignPersonalMessage(uint8_t p1,
|
||||
processed_size = data - payload;
|
||||
} else if (p1 != P1_MORE) {
|
||||
PRINTF("Error: Unexpected P1 (%u)!\n", p1);
|
||||
apdu_reply(0x6B00);
|
||||
apdu_reply(APDU_RESPONSE_INVALID_P1_P2);
|
||||
return false;
|
||||
} else if (appState != APP_STATE_SIGNING_MESSAGE) {
|
||||
PRINTF("Error: App not already in signing state!\n");
|
||||
apdu_reply(APDU_RESPONSE_INVALID_DATA);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -241,7 +248,7 @@ bool handleSignPersonalMessage(uint8_t p1,
|
||||
ui_191_switch_to_sign();
|
||||
#endif
|
||||
} else {
|
||||
apdu_reply(0x9000);
|
||||
apdu_reply(APDU_RESPONSE_OK);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -266,7 +273,7 @@ void question_switcher(void) {
|
||||
void skip_rest_of_message(void) {
|
||||
states.sign_state = STATE_191_HASH_ONLY;
|
||||
if (tmpCtx.messageSigningContext.remainingLength > 0) {
|
||||
apdu_reply(0x9000);
|
||||
apdu_reply(APDU_RESPONSE_OK);
|
||||
} else {
|
||||
ui_191_switch_to_sign();
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -115,10 +115,12 @@ void ui_712_redraw_generic_step(void) {
|
||||
/**
|
||||
* Called to fetch the next field if they have not all been processed yet
|
||||
*
|
||||
* @return whether there will be a next field
|
||||
* Also handles the special "Review struct" screen of the verbose mode
|
||||
*
|
||||
* @return the next field state
|
||||
*/
|
||||
bool ui_712_next_field(void) {
|
||||
bool next = false;
|
||||
e_eip712_nfs ui_712_next_field(void) {
|
||||
e_eip712_nfs state = EIP712_NO_MORE_FIELD;
|
||||
|
||||
if (ui_ctx == NULL) {
|
||||
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
|
||||
@@ -126,13 +128,13 @@ bool ui_712_next_field(void) {
|
||||
if (ui_ctx->structs_to_review > 0) {
|
||||
ui_712_review_struct(path_get_nth_field_to_last(ui_ctx->structs_to_review));
|
||||
ui_ctx->structs_to_review -= 1;
|
||||
}
|
||||
if (!ui_ctx->end_reached) {
|
||||
state = EIP712_FIELD_LATER;
|
||||
} else if (!ui_ctx->end_reached) {
|
||||
handle_eip712_return_code(true);
|
||||
next = true;
|
||||
state = EIP712_FIELD_INCOMING;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
#define UI_712_FIELD_NAME_PROVIDED (1 << 1)
|
||||
|
||||
typedef enum { EIP712_FILTERING_BASIC, EIP712_FILTERING_FULL } e_eip712_filtering_mode;
|
||||
typedef enum {
|
||||
EIP712_FIELD_LATER,
|
||||
EIP712_FIELD_INCOMING,
|
||||
EIP712_NO_MORE_FIELD
|
||||
} e_eip712_nfs; // next field state
|
||||
|
||||
typedef struct {
|
||||
bool shown;
|
||||
@@ -22,7 +27,7 @@ typedef struct {
|
||||
|
||||
bool ui_712_init(void);
|
||||
void ui_712_deinit(void);
|
||||
bool ui_712_next_field(void);
|
||||
e_eip712_nfs ui_712_next_field(void);
|
||||
void ui_712_review_struct(const void *const struct_ptr);
|
||||
bool ui_712_new_field(const void *const field_ptr, const uint8_t *const data, uint8_t length);
|
||||
void ui_712_end_sign(void);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "utils.h"
|
||||
#include "ui_flow.h"
|
||||
#include "common_ui.h"
|
||||
#include "common_712.h"
|
||||
#include "ethUtils.h"
|
||||
|
||||
@@ -33,7 +33,7 @@ void handleSignEIP712Message_v0(uint8_t p1,
|
||||
#ifdef NO_CONSENT
|
||||
io_seproxyhal_touch_signMessage_ok(NULL);
|
||||
#else // NO_CONSENT
|
||||
ux_flow_init(0, ux_sign_712_v0_flow, NULL);
|
||||
ui_sign_712_v0();
|
||||
#endif // NO_CONSENT
|
||||
|
||||
*flags |= IO_ASYNCH_REPLY;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "ethUtils.h"
|
||||
#include "common_ui.h"
|
||||
#include "ui_callbacks.h"
|
||||
#include <ctype.h>
|
||||
|
||||
#define ERR_SILENT_MODE_CHECK_FAILED 0x6001
|
||||
|
||||
@@ -170,26 +171,6 @@ customStatus_e customProcessor(txContext_t *context) {
|
||||
return CUSTOM_NOT_HANDLED;
|
||||
}
|
||||
|
||||
void to_uppercase(char *str, unsigned char size) {
|
||||
for (unsigned char i = 0; i < size && str[i] != 0; i++) {
|
||||
str[i] = str[i] >= 'a' ? str[i] - ('a' - 'A') : str[i];
|
||||
}
|
||||
}
|
||||
|
||||
void compareOrCopy(char *preapproved_string, size_t size, char *parsed_string, bool silent_mode) {
|
||||
if (silent_mode) {
|
||||
/* ETH address are not fundamentally case sensitive but might
|
||||
have some for checksum purpose, so let's get rid of these diffs */
|
||||
to_uppercase(preapproved_string, strlen(preapproved_string));
|
||||
to_uppercase(parsed_string, strlen(parsed_string));
|
||||
if (memcmp(preapproved_string, parsed_string, strlen(preapproved_string))) {
|
||||
THROW(ERR_SILENT_MODE_CHECK_FAILED);
|
||||
}
|
||||
} else {
|
||||
strlcpy(preapproved_string, parsed_string, size);
|
||||
}
|
||||
}
|
||||
|
||||
void reportFinalizeError(bool direct) {
|
||||
reset_app_context();
|
||||
if (direct) {
|
||||
@@ -200,20 +181,20 @@ void reportFinalizeError(bool direct) {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert `BEgasPrice` and `BEgasLimit` to Uint256 and then stores the multiplication of both in
|
||||
// `output`.
|
||||
static void computeFees(txInt256_t *BEgasPrice, txInt256_t *BEgasLimit, uint256_t *output) {
|
||||
uint256_t gasPrice = {0};
|
||||
uint256_t gasLimit = {0};
|
||||
|
||||
PRINTF("Gas price %.*H\n", BEgasPrice->length, BEgasPrice->value);
|
||||
PRINTF("Gas limit %.*H\n", BEgasLimit->length, BEgasLimit->value);
|
||||
convertUint256BE(BEgasPrice->value, BEgasPrice->length, &gasPrice);
|
||||
convertUint256BE(BEgasLimit->value, BEgasLimit->length, &gasLimit);
|
||||
mul256(&gasPrice, &gasLimit, output);
|
||||
static void address_to_string(uint8_t *in,
|
||||
size_t in_len,
|
||||
char *out,
|
||||
size_t out_len,
|
||||
cx_sha3_t *sha3,
|
||||
uint64_t chainId) {
|
||||
if (in_len != 0) {
|
||||
getEthDisplayableAddress(in, out, out_len, sha3, chainId);
|
||||
} else {
|
||||
strlcpy(out, "Contract", out_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) {
|
||||
static void raw_fee_to_string(uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) {
|
||||
const char *feeTicker = get_network_ticker();
|
||||
uint8_t tickerOffset = 0;
|
||||
uint32_t i;
|
||||
@@ -240,6 +221,7 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
|
||||
displayBuffer[tickerOffset] = feeTicker[tickerOffset];
|
||||
tickerOffset++;
|
||||
}
|
||||
if ((uint32_t) tickerOffset < displayBufferSize) displayBuffer[tickerOffset++] = ' ';
|
||||
while (G_io_apdu_buffer[i]) {
|
||||
if ((uint32_t) (tickerOffset) + i >= displayBufferSize) {
|
||||
break;
|
||||
@@ -254,32 +236,40 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
|
||||
}
|
||||
|
||||
// Compute the fees, transform it to a string, prepend a ticker to it and copy everything to
|
||||
// `displayBuffer`.
|
||||
void prepareAndCopyFees(txInt256_t *BEGasPrice,
|
||||
txInt256_t *BEGasLimit,
|
||||
char *displayBuffer,
|
||||
uint32_t displayBufferSize) {
|
||||
// `displayBuffer` output
|
||||
static void max_transaction_fee_to_string(const txInt256_t *BEGasPrice,
|
||||
const txInt256_t *BEGasLimit,
|
||||
char *displayBuffer,
|
||||
uint32_t displayBufferSize) {
|
||||
// Use temporary variables to convert values to uint256_t
|
||||
uint256_t gasPrice = {0};
|
||||
uint256_t gasLimit = {0};
|
||||
// Use temporary variable to store the result of the operation in uint256_t
|
||||
uint256_t rawFee = {0};
|
||||
computeFees(BEGasPrice, BEGasLimit, &rawFee);
|
||||
feesToString(&rawFee, displayBuffer, displayBufferSize);
|
||||
|
||||
PRINTF("Gas price %.*H\n", BEGasPrice->length, BEGasPrice->value);
|
||||
PRINTF("Gas limit %.*H\n", BEGasLimit->length, BEGasLimit->value);
|
||||
convertUint256BE(BEGasPrice->value, BEGasPrice->length, &gasPrice);
|
||||
convertUint256BE(BEGasLimit->value, BEGasLimit->length, &gasLimit);
|
||||
mul256(&gasPrice, &gasLimit, &rawFee);
|
||||
raw_fee_to_string(&rawFee, displayBuffer, displayBufferSize);
|
||||
}
|
||||
|
||||
void prepareFeeDisplay() {
|
||||
prepareAndCopyFees(&tmpContent.txContent.gasprice,
|
||||
&tmpContent.txContent.startgas,
|
||||
strings.common.maxFee,
|
||||
sizeof(strings.common.maxFee));
|
||||
static void nonce_to_string(const txInt256_t *nonce, char *out, size_t out_size) {
|
||||
uint256_t nonce_uint256;
|
||||
convertUint256BE(nonce->value, nonce->length, &nonce_uint256);
|
||||
tostring256(&nonce_uint256, 10, out, out_size);
|
||||
}
|
||||
|
||||
void prepareNetworkDisplay() {
|
||||
static void get_network_as_string(char *out, size_t out_size) {
|
||||
const char *name = get_network_name();
|
||||
if (name == NULL) {
|
||||
// No network name found so simply copy the chain ID as the network name.
|
||||
uint64_t chain_id = get_chain_id();
|
||||
u64_to_string(chain_id, strings.common.network_name, sizeof(strings.common.network_name));
|
||||
u64_to_string(chain_id, out, out_size);
|
||||
} else {
|
||||
// Network name found, simply copy it.
|
||||
strlcpy(strings.common.network_name, name, sizeof(strings.common.network_name));
|
||||
strlcpy(out, name, out_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,12 +294,27 @@ static void get_public_key(uint8_t *out, uint8_t outLength) {
|
||||
getEthAddressFromKey(&publicKey, out, &global_sha3);
|
||||
}
|
||||
|
||||
/* Local implmentation of strncasecmp, workaround of the segfaulting base implem
|
||||
* Remove once strncasecmp is fixed
|
||||
*/
|
||||
static int strcasecmp_workaround(const char *str1, const char *str2) {
|
||||
unsigned char c1, c2;
|
||||
do {
|
||||
c1 = *str1++;
|
||||
c2 = *str2++;
|
||||
if (toupper(c1) != toupper(c2)) {
|
||||
return toupper(c1) - toupper(c2);
|
||||
}
|
||||
} while (c1 != '\0');
|
||||
return 0;
|
||||
}
|
||||
|
||||
void finalizeParsing(bool direct) {
|
||||
char displayBuffer[50];
|
||||
uint8_t decimals = WEI_TO_ETHER;
|
||||
const char *ticker = get_network_ticker();
|
||||
ethPluginFinalize_t pluginFinalize;
|
||||
bool genericUI = true;
|
||||
bool use_standard_UI = true;
|
||||
|
||||
// Verify the chain
|
||||
if (chainConfig->chainId != ETHEREUM_MAINNET_CHAINID) {
|
||||
@@ -334,7 +339,6 @@ void finalizeParsing(bool direct) {
|
||||
|
||||
// Finalize the plugin handling
|
||||
if (dataContext.tokenContext.pluginStatus >= ETH_PLUGIN_RESULT_SUCCESSFUL) {
|
||||
genericUI = false;
|
||||
eth_plugin_prepare_finalize(&pluginFinalize);
|
||||
|
||||
uint8_t msg_sender[ADDRESS_LENGTH] = {0};
|
||||
@@ -380,6 +384,8 @@ void finalizeParsing(bool direct) {
|
||||
// Handle the right interface
|
||||
switch (pluginFinalize.uiType) {
|
||||
case ETH_UI_TYPE_GENERIC:
|
||||
// Use the dedicated ETH plugin UI
|
||||
use_standard_UI = false;
|
||||
tmpContent.txContent.dataPresent = false;
|
||||
// Add the number of screens + the number of additional screens to get the total
|
||||
// number of screens needed.
|
||||
@@ -387,7 +393,8 @@ void finalizeParsing(bool direct) {
|
||||
pluginFinalize.numScreens + pluginProvideInfo.additionalScreens;
|
||||
break;
|
||||
case ETH_UI_TYPE_AMOUNT_ADDRESS:
|
||||
genericUI = true;
|
||||
// Use the standard ETH UI as this plugin uses the amount/address UI
|
||||
use_standard_UI = true;
|
||||
tmpContent.txContent.dataPresent = false;
|
||||
if ((pluginFinalize.amount == NULL) || (pluginFinalize.address == NULL)) {
|
||||
PRINTF("Incorrect amount/address set by plugin\n");
|
||||
@@ -412,11 +419,15 @@ void finalizeParsing(bool direct) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
genericUI = true;
|
||||
}
|
||||
}
|
||||
|
||||
// User has just validated a swap but ETH received apdus about a non standard plugin / contract
|
||||
if (called_from_swap && !use_standard_UI) {
|
||||
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, called_from_swap\n");
|
||||
THROW(ERR_SILENT_MODE_CHECK_FAILED);
|
||||
}
|
||||
|
||||
if (tmpContent.txContent.dataPresent && !N_storage.dataAllowed) {
|
||||
reportFinalizeError(direct);
|
||||
ui_warning_contract_data();
|
||||
@@ -425,66 +436,94 @@ void finalizeParsing(bool direct) {
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare destination address to display
|
||||
if (genericUI) {
|
||||
if (tmpContent.txContent.destinationLength != 0) {
|
||||
getEthDisplayableAddress(tmpContent.txContent.destination,
|
||||
displayBuffer,
|
||||
sizeof(displayBuffer),
|
||||
&global_sha3,
|
||||
chainConfig->chainId);
|
||||
compareOrCopy(strings.common.fullAddress,
|
||||
sizeof(strings.common.fullAddress),
|
||||
// Prepare destination address and amount to display
|
||||
if (use_standard_UI) {
|
||||
// Format the address in a temporary buffer, if in swap case compare it with validated
|
||||
// address, else commit it
|
||||
address_to_string(tmpContent.txContent.destination,
|
||||
tmpContent.txContent.destinationLength,
|
||||
displayBuffer,
|
||||
called_from_swap);
|
||||
sizeof(displayBuffer),
|
||||
&global_sha3,
|
||||
chainConfig->chainId);
|
||||
if (called_from_swap) {
|
||||
// Ensure the values are the same that the ones that have been previously validated
|
||||
if (strcasecmp_workaround(strings.common.fullAddress, displayBuffer) != 0) {
|
||||
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, address check failed\n");
|
||||
THROW(ERR_SILENT_MODE_CHECK_FAILED);
|
||||
}
|
||||
} else {
|
||||
strcpy(strings.common.fullAddress, "Contract");
|
||||
strlcpy(strings.common.fullAddress, displayBuffer, sizeof(strings.common.fullAddress));
|
||||
}
|
||||
}
|
||||
PRINTF("Address displayed: %s\n", strings.common.fullAddress);
|
||||
|
||||
// Prepare amount to display
|
||||
if (genericUI) {
|
||||
// Format the amount in a temporary buffer, if in swap case compare it with validated
|
||||
// amount, else commit it
|
||||
amountToString(tmpContent.txContent.value.value,
|
||||
tmpContent.txContent.value.length,
|
||||
decimals,
|
||||
ticker,
|
||||
displayBuffer,
|
||||
sizeof(displayBuffer));
|
||||
compareOrCopy(strings.common.fullAmount,
|
||||
sizeof(strings.common.fullAmount),
|
||||
displayBuffer,
|
||||
called_from_swap);
|
||||
if (called_from_swap) {
|
||||
// Ensure the values are the same that the ones that have been previously validated
|
||||
if (strcmp(strings.common.fullAmount, displayBuffer) != 0) {
|
||||
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, amount check failed\n");
|
||||
THROW(ERR_SILENT_MODE_CHECK_FAILED);
|
||||
}
|
||||
} else {
|
||||
strlcpy(strings.common.fullAmount, displayBuffer, sizeof(strings.common.fullAmount));
|
||||
}
|
||||
PRINTF("Amount displayed: %s\n", strings.common.fullAmount);
|
||||
}
|
||||
|
||||
// Prepare nonce to display
|
||||
uint256_t nonce;
|
||||
convertUint256BE(tmpContent.txContent.nonce.value, tmpContent.txContent.nonce.length, &nonce);
|
||||
tostring256(&nonce, 10, displayBuffer, sizeof(displayBuffer));
|
||||
strlcpy(strings.common.nonce, displayBuffer, sizeof(strings.common.nonce));
|
||||
// Compute the max fee in a temporary buffer, if in swap case compare it with validated max fee,
|
||||
// else commit it
|
||||
max_transaction_fee_to_string(&tmpContent.txContent.gasprice,
|
||||
&tmpContent.txContent.startgas,
|
||||
displayBuffer,
|
||||
sizeof(displayBuffer));
|
||||
if (called_from_swap) {
|
||||
// Ensure the values are the same that the ones that have been previously validated
|
||||
if (strcmp(strings.common.maxFee, displayBuffer) != 0) {
|
||||
PRINTF("ERR_SILENT_MODE_CHECK_FAILED, fees check failed\n");
|
||||
THROW(ERR_SILENT_MODE_CHECK_FAILED);
|
||||
}
|
||||
} else {
|
||||
strlcpy(strings.common.maxFee, displayBuffer, sizeof(strings.common.maxFee));
|
||||
}
|
||||
|
||||
// Compute maximum fee
|
||||
prepareFeeDisplay();
|
||||
PRINTF("Fees displayed: %s\n", strings.common.maxFee);
|
||||
|
||||
// Prepare nonce to display
|
||||
nonce_to_string(&tmpContent.txContent.nonce,
|
||||
strings.common.nonce,
|
||||
sizeof(strings.common.nonce));
|
||||
PRINTF("Nonce: %s\n", strings.common.nonce);
|
||||
|
||||
// Prepare chainID field
|
||||
prepareNetworkDisplay();
|
||||
get_network_as_string(strings.common.network_name, sizeof(strings.common.network_name));
|
||||
PRINTF("Network: %s\n", strings.common.network_name);
|
||||
|
||||
bool no_consent;
|
||||
bool no_consent_check;
|
||||
|
||||
no_consent = called_from_swap;
|
||||
// If called from swap, the user as already validated a standard transaction
|
||||
// We have already checked the fields of this transaction above
|
||||
no_consent_check = called_from_swap && use_standard_UI;
|
||||
|
||||
#ifdef NO_CONSENT
|
||||
no_consent = true;
|
||||
no_consent_check = true;
|
||||
#endif // NO_CONSENT
|
||||
|
||||
if (no_consent) {
|
||||
if (no_consent_check) {
|
||||
io_seproxyhal_touch_tx_ok(NULL);
|
||||
} else {
|
||||
if (genericUI) {
|
||||
if (use_standard_UI) {
|
||||
ux_approve_tx(false);
|
||||
} else {
|
||||
plugin_ui_start();
|
||||
dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE;
|
||||
dataContext.tokenContext.pluginUiCurrentItem = 0;
|
||||
ux_approve_tx(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,10 +326,11 @@ void starkware_print_amount(uint8_t *amountData,
|
||||
}
|
||||
tostring256(&amount, 10, (char *) (G_io_apdu_buffer + 100), 100);
|
||||
strlcpy(destination, ticker, destinationLength);
|
||||
strlcat(destination, " ", destinationLength);
|
||||
adjustDecimals((char *) (G_io_apdu_buffer + 100),
|
||||
strlen((char *) (G_io_apdu_buffer + 100)),
|
||||
destination + strlen(ticker),
|
||||
destinationLength - strlen(ticker),
|
||||
destination + strlen(ticker) + 1,
|
||||
destinationLength - strlen(ticker) - 1,
|
||||
decimals);
|
||||
}
|
||||
|
||||
|
||||
2
tests/ragger/.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
snapshots-tmp/
|
||||
elfs/
|
||||
|
||||
@@ -1,17 +1,48 @@
|
||||
from enum import IntEnum, auto
|
||||
from typing import Iterator, Dict, List
|
||||
from typing import Optional
|
||||
from ragger.backend import BackendInterface
|
||||
from ragger.utils import RAPDU
|
||||
from ethereum_client.command_builder import EthereumCmdBuilder
|
||||
from ethereum_client.setting import SettingType, SettingImpl
|
||||
from ethereum_client.eip712 import EIP712FieldType
|
||||
from ethereum_client.response_parser import EthereumRespParser
|
||||
from ragger.navigator import NavInsID, NavIns, NanoNavigator
|
||||
from .command_builder import EthereumCmdBuilder
|
||||
from .setting import SettingType, SettingImpl
|
||||
from .eip712 import EIP712FieldType
|
||||
from .response_parser import EthereumRespParser
|
||||
from .tlv import format_tlv
|
||||
import signal
|
||||
import time
|
||||
from pathlib import Path
|
||||
import keychain
|
||||
import rlp
|
||||
|
||||
|
||||
class EthereumClient:
|
||||
_settings: Dict[SettingType, SettingImpl] = {
|
||||
ROOT_SCREENSHOT_PATH = Path(__file__).parent.parent
|
||||
WEI_IN_ETH = 1e+18
|
||||
|
||||
|
||||
class StatusWord(IntEnum):
|
||||
OK = 0x9000
|
||||
ERROR_NO_INFO = 0x6a00
|
||||
INVALID_DATA = 0x6a80
|
||||
INSUFFICIENT_MEMORY = 0x6a84
|
||||
INVALID_INS = 0x6d00
|
||||
INVALID_P1_P2 = 0x6b00
|
||||
CONDITION_NOT_SATISFIED = 0x6985
|
||||
REF_DATA_NOT_FOUND = 0x6a88
|
||||
|
||||
class DOMAIN_NAME_TAG(IntEnum):
|
||||
STRUCTURE_TYPE = 0x01
|
||||
STRUCTURE_VERSION = 0x02
|
||||
CHALLENGE = 0x12
|
||||
SIGNER_KEY_ID = 0x13
|
||||
SIGNER_ALGO = 0x14
|
||||
SIGNATURE = 0x15
|
||||
DOMAIN_NAME = 0x20
|
||||
COIN_TYPE = 0x21
|
||||
ADDRESS = 0x22
|
||||
|
||||
|
||||
class EthereumClient:
|
||||
_settings: dict[SettingType, SettingImpl] = {
|
||||
SettingType.BLIND_SIGNING: SettingImpl(
|
||||
[ "nanos", "nanox", "nanosp" ]
|
||||
),
|
||||
@@ -23,15 +54,20 @@ class EthereumClient:
|
||||
),
|
||||
SettingType.VERBOSE_EIP712: SettingImpl(
|
||||
[ "nanox", "nanosp" ]
|
||||
),
|
||||
SettingType.VERBOSE_ENS: SettingImpl(
|
||||
[ "nanox", "nanosp" ]
|
||||
)
|
||||
}
|
||||
_click_delay = 1/4
|
||||
_eip712_filtering = False
|
||||
|
||||
def __init__(self, client: BackendInterface):
|
||||
def __init__(self, client: BackendInterface, golden_run: bool):
|
||||
self._client = client
|
||||
self._chain_id = 1
|
||||
self._cmd_builder = EthereumCmdBuilder()
|
||||
self._resp_parser = EthereumRespParser()
|
||||
self._nav = NanoNavigator(client, client.firmware, golden_run)
|
||||
signal.signal(signal.SIGALRM, self._click_signal_timeout)
|
||||
for setting in self._settings.values():
|
||||
setting.value = False
|
||||
@@ -65,11 +101,11 @@ class EthereumClient:
|
||||
array_levels: [],
|
||||
key_name: str):
|
||||
with self._send(self._cmd_builder.eip712_send_struct_def_struct_field(
|
||||
field_type,
|
||||
type_name,
|
||||
type_size,
|
||||
array_levels,
|
||||
key_name)):
|
||||
field_type,
|
||||
type_name,
|
||||
type_size,
|
||||
array_levels,
|
||||
key_name)):
|
||||
pass
|
||||
return self._recv()
|
||||
|
||||
@@ -91,8 +127,8 @@ class EthereumClient:
|
||||
self._disable_click_until_response()
|
||||
assert self._recv().status == 0x9000
|
||||
|
||||
def eip712_sign_new(self, bip32):
|
||||
with self._send(self._cmd_builder.eip712_sign_new(bip32)):
|
||||
def eip712_sign_new(self, bip32_path: str):
|
||||
with self._send(self._cmd_builder.eip712_sign_new(bip32_path)):
|
||||
time.sleep(0.5) # tight on timing, needed by the CI otherwise might fail sometimes
|
||||
if not self._settings[SettingType.VERBOSE_EIP712].value and \
|
||||
not self._eip712_filtering: # need to skip the message hash
|
||||
@@ -104,10 +140,10 @@ class EthereumClient:
|
||||
return self._resp_parser.sign(resp.data)
|
||||
|
||||
def eip712_sign_legacy(self,
|
||||
bip32,
|
||||
bip32_path: str,
|
||||
domain_hash: bytes,
|
||||
message_hash: bytes):
|
||||
with self._send(self._cmd_builder.eip712_sign_legacy(bip32,
|
||||
with self._send(self._cmd_builder.eip712_sign_legacy(bip32_path,
|
||||
domain_hash,
|
||||
message_hash)):
|
||||
self._client.right_click() # sign typed message screen
|
||||
@@ -125,7 +161,7 @@ class EthereumClient:
|
||||
assert resp.status == 0x9000
|
||||
return self._resp_parser.sign(resp.data)
|
||||
|
||||
def settings_set(self, new_values: Dict[SettingType, bool]):
|
||||
def settings_set(self, new_values: dict[SettingType, bool]):
|
||||
# Go to settings
|
||||
for _ in range(2):
|
||||
self._client.right_click()
|
||||
@@ -156,3 +192,61 @@ class EthereumClient:
|
||||
with self._send(self._cmd_builder.eip712_filtering_show_field(name, sig)):
|
||||
pass
|
||||
assert self._recv().status == 0x9000
|
||||
|
||||
def send_fund(self,
|
||||
bip32_path: str,
|
||||
nonce: int,
|
||||
gas_price: int,
|
||||
gas_limit: int,
|
||||
to: bytes,
|
||||
amount: float,
|
||||
chain_id: int,
|
||||
screenshot_collection: str = None):
|
||||
data = list()
|
||||
data.append(nonce)
|
||||
data.append(gas_price)
|
||||
data.append(gas_limit)
|
||||
data.append(to)
|
||||
data.append(int(amount * WEI_IN_ETH))
|
||||
data.append(bytes())
|
||||
data.append(chain_id)
|
||||
data.append(bytes())
|
||||
data.append(bytes())
|
||||
|
||||
for chunk in self._cmd_builder.sign(bip32_path, rlp.encode(data)):
|
||||
with self._send(chunk):
|
||||
nav_ins = NavIns(NavInsID.RIGHT_CLICK)
|
||||
final_ins = [ NavIns(NavInsID.BOTH_CLICK) ]
|
||||
target_text = "and send"
|
||||
if screenshot_collection:
|
||||
self._nav.navigate_until_text_and_compare(nav_ins,
|
||||
final_ins,
|
||||
target_text,
|
||||
ROOT_SCREENSHOT_PATH,
|
||||
screenshot_collection)
|
||||
else:
|
||||
self._nav.navigate_until_text(nav_ins,
|
||||
final_ins,
|
||||
target_text)
|
||||
|
||||
def get_challenge(self) -> int:
|
||||
with self._send(self._cmd_builder.get_challenge()):
|
||||
pass
|
||||
resp = self._recv()
|
||||
return self._resp_parser.challenge(resp.data)
|
||||
|
||||
def provide_domain_name(self, challenge: int, name: str, addr: bytes):
|
||||
payload = format_tlv(DOMAIN_NAME_TAG.STRUCTURE_TYPE, 3) # TrustedDomainName
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.STRUCTURE_VERSION, 1)
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.SIGNER_KEY_ID, 0) # test key
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.SIGNER_ALGO, 1) # secp256k1
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.CHALLENGE, challenge)
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.COIN_TYPE, 0x3c) # ETH in slip-44
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.DOMAIN_NAME, name)
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.ADDRESS, addr)
|
||||
payload += format_tlv(DOMAIN_NAME_TAG.SIGNATURE,
|
||||
keychain.sign_data(keychain.Key.DOMAIN_NAME, payload))
|
||||
|
||||
for chunk in self._cmd_builder.provide_domain_name(payload):
|
||||
with self._send(chunk):
|
||||
pass
|
||||
@@ -1,18 +1,25 @@
|
||||
from enum import IntEnum, auto
|
||||
from typing import Iterator
|
||||
from ethereum_client.eip712 import EIP712FieldType
|
||||
from typing import Iterator, Optional
|
||||
from .eip712 import EIP712FieldType
|
||||
from ragger.bip import pack_derivation_path
|
||||
import struct
|
||||
|
||||
class InsType(IntEnum):
|
||||
class InsType(IntEnum):
|
||||
SIGN = 0x04
|
||||
EIP712_SEND_STRUCT_DEF = 0x1a
|
||||
EIP712_SEND_STRUCT_IMPL = 0x1c
|
||||
EIP712_SEND_FILTERING = 0x1e
|
||||
EIP712_SIGN = 0x0c
|
||||
GET_CHALLENGE = 0x20
|
||||
PROVIDE_DOMAIN_NAME = 0x22
|
||||
|
||||
class P1Type(IntEnum):
|
||||
class P1Type(IntEnum):
|
||||
COMPLETE_SEND = 0x00
|
||||
PARTIAL_SEND = 0x01
|
||||
SIGN_FIRST_CHUNK = 0x00
|
||||
SIGN_SUBSQT_CHUNK = 0x80
|
||||
|
||||
class P2Type(IntEnum):
|
||||
class P2Type(IntEnum):
|
||||
STRUCT_NAME = 0x00
|
||||
STRUCT_FIELD = 0xff
|
||||
ARRAY = 0x0f
|
||||
@@ -22,14 +29,14 @@ class P2Type(IntEnum):
|
||||
FILTERING_CONTRACT_NAME = 0x0f
|
||||
FILTERING_FIELD_NAME = 0xff
|
||||
|
||||
class EthereumCmdBuilder:
|
||||
class EthereumCmdBuilder:
|
||||
_CLA: int = 0xE0
|
||||
|
||||
def _serialize(self,
|
||||
ins: InsType,
|
||||
p1: int,
|
||||
p2: int,
|
||||
cdata: bytearray = bytearray()) -> bytes:
|
||||
cdata: bytearray = bytes()) -> bytes:
|
||||
|
||||
header = bytearray()
|
||||
header.append(self._CLA)
|
||||
@@ -109,27 +116,18 @@ class EthereumCmdBuilder:
|
||||
data_w_length[:0xff])
|
||||
data_w_length = data_w_length[0xff:]
|
||||
|
||||
def _format_bip32(self, bip32, data: bytearray) -> bytearray:
|
||||
data.append(len(bip32))
|
||||
for idx in bip32:
|
||||
data.append((idx & 0xff000000) >> 24)
|
||||
data.append((idx & 0x00ff0000) >> 16)
|
||||
data.append((idx & 0x0000ff00) >> 8)
|
||||
data.append((idx & 0x000000ff))
|
||||
return data
|
||||
|
||||
def eip712_sign_new(self, bip32) -> bytes:
|
||||
data = self._format_bip32(bip32, bytearray())
|
||||
def eip712_sign_new(self, bip32_path: str) -> bytes:
|
||||
data = pack_derivation_path(bip32_path)
|
||||
return self._serialize(InsType.EIP712_SIGN,
|
||||
P1Type.COMPLETE_SEND,
|
||||
P2Type.NEW_IMPLEM,
|
||||
data)
|
||||
|
||||
def eip712_sign_legacy(self,
|
||||
bip32,
|
||||
bip32_path: str,
|
||||
domain_hash: bytes,
|
||||
message_hash: bytes) -> bytes:
|
||||
data = self._format_bip32(bip32, bytearray())
|
||||
data = pack_derivation_path(bip32_path)
|
||||
data += domain_hash
|
||||
data += message_hash
|
||||
return self._serialize(InsType.EIP712_SIGN,
|
||||
@@ -168,3 +166,30 @@ class EthereumCmdBuilder:
|
||||
P1Type.COMPLETE_SEND,
|
||||
P2Type.FILTERING_FIELD_NAME,
|
||||
self._eip712_filtering_send_name(name, sig))
|
||||
|
||||
def sign(self, bip32_path: str, rlp_data: bytes) -> Iterator[bytes]:
|
||||
payload = pack_derivation_path(bip32_path)
|
||||
payload += rlp_data
|
||||
p1 = P1Type.SIGN_FIRST_CHUNK
|
||||
while len(payload) > 0:
|
||||
yield self._serialize(InsType.SIGN,
|
||||
p1,
|
||||
0x00,
|
||||
payload[:0xff])
|
||||
payload = payload[0xff:]
|
||||
p1 = P1Type.SIGN_SUBSQT_CHUNK
|
||||
|
||||
def get_challenge(self) -> bytes:
|
||||
return self._serialize(InsType.GET_CHALLENGE, 0x00, 0x00)
|
||||
|
||||
def provide_domain_name(self, tlv_payload: bytes) -> bytes:
|
||||
payload = struct.pack(">H", len(tlv_payload))
|
||||
payload += tlv_payload
|
||||
p1 = 1
|
||||
while len(payload) > 0:
|
||||
yield self._serialize(InsType.PROVIDE_DOMAIN_NAME,
|
||||
p1,
|
||||
0x00,
|
||||
payload[:0xff])
|
||||
payload = payload[0xff:]
|
||||
p1 = 0
|
||||
@@ -1,6 +1,6 @@
|
||||
from enum import IntEnum, auto
|
||||
|
||||
class EIP712FieldType(IntEnum):
|
||||
class EIP712FieldType(IntEnum):
|
||||
CUSTOM = 0,
|
||||
INT = auto()
|
||||
UINT = auto()
|
||||
@@ -1,4 +1,4 @@
|
||||
class EthereumRespParser:
|
||||
class EthereumRespParser:
|
||||
def sign(self, data: bytes):
|
||||
assert len(data) == (1 + 32 + 32)
|
||||
|
||||
@@ -12,3 +12,7 @@ class EthereumRespParser:
|
||||
data = data[32:]
|
||||
|
||||
return v, r, s
|
||||
|
||||
def challenge(self, data: bytes) -> int:
|
||||
assert len(data) == 4
|
||||
return int.from_bytes(data, "big")
|
||||
@@ -1,13 +1,14 @@
|
||||
from enum import IntEnum, auto
|
||||
from typing import List
|
||||
|
||||
class SettingType(IntEnum):
|
||||
class SettingType(IntEnum):
|
||||
BLIND_SIGNING = 0,
|
||||
DEBUG_DATA = auto()
|
||||
NONCE = auto()
|
||||
VERBOSE_EIP712 = auto()
|
||||
VERBOSE_ENS = auto()
|
||||
|
||||
class SettingImpl:
|
||||
class SettingImpl:
|
||||
devices: List[str]
|
||||
value: bool
|
||||
|
||||
25
tests/ragger/app/tlv.py
Normal file
@@ -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,68 +1,12 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from ragger import Firmware
|
||||
from ragger.backend import SpeculosBackend, LedgerCommBackend, LedgerWalletBackend, BackendInterface
|
||||
from ethereum_client.client import EthereumClient
|
||||
|
||||
FWS = [
|
||||
Firmware("nanos", "2.1"),
|
||||
Firmware("nanox", "2.0.2"),
|
||||
Firmware("nanosp", "1.0.3")
|
||||
]
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--backend", action="store", default="speculos")
|
||||
parser.addoption("--path", action="store", default="./elfs")
|
||||
parser.addoption("--model", action="store", required=True)
|
||||
|
||||
# accessing the value of the "--backend" option as a fixture
|
||||
@pytest.fixture
|
||||
def arg_backend(pytestconfig) -> str:
|
||||
return pytestconfig.getoption("backend")
|
||||
|
||||
@pytest.fixture
|
||||
def arg_path(pytestconfig) -> str:
|
||||
return pytestconfig.getoption("path")
|
||||
|
||||
@pytest.fixture
|
||||
def arg_model(pytestconfig) -> str:
|
||||
return pytestconfig.getoption("model")
|
||||
|
||||
# Providing the firmware as a fixture
|
||||
@pytest.fixture
|
||||
def firmware(arg_model: str) -> Firmware:
|
||||
for fw in FWS:
|
||||
if fw.device == arg_model:
|
||||
return fw
|
||||
raise ValueError("Unknown device model \"%s\"" % (arg_model))
|
||||
|
||||
def get_elf_path(arg_path: str, firmware: Firmware) -> Path:
|
||||
elf_dir = Path(arg_path).resolve()
|
||||
assert elf_dir.is_dir(), ("%s is not a directory" % (arg_path))
|
||||
app = elf_dir / ("app-%s.elf" % firmware.device)
|
||||
assert app.is_file(), ("Firmware %s does not exist !" % (app))
|
||||
return app
|
||||
|
||||
# Depending on the "--backend" option value, a different backend is
|
||||
# instantiated, and the tests will either run on Speculos or on a physical
|
||||
# device depending on the backend
|
||||
def create_backend(backend: str, arg_path: str, firmware: Firmware) -> BackendInterface:
|
||||
if backend.lower() == "ledgercomm":
|
||||
return LedgerCommBackend(firmware, interface="hid")
|
||||
elif backend.lower() == "ledgerwallet":
|
||||
return LedgerWalletBackend(firmware)
|
||||
elif backend.lower() == "speculos":
|
||||
return SpeculosBackend(get_elf_path(arg_path, firmware), firmware)
|
||||
else:
|
||||
raise ValueError(f"Backend '{backend}' is unknown. Valid backends are: {BACKENDS}")
|
||||
|
||||
# This fixture will create and return the backend client
|
||||
@pytest.fixture
|
||||
def backend_client(arg_backend: str, arg_path: str, firmware: Firmware) -> BackendInterface:
|
||||
with create_backend(arg_backend, arg_path, firmware) as b:
|
||||
yield b
|
||||
from ragger.conftest import configuration
|
||||
from ragger.backend import BackendInterface
|
||||
from app.client import EthereumClient
|
||||
|
||||
# This final fixture will return the properly configured app client, to be used in tests
|
||||
@pytest.fixture
|
||||
def app_client(backend_client: BackendInterface) -> EthereumClient:
|
||||
return EthereumClient(backend_client)
|
||||
def app_client(backend: BackendInterface, golden_run: bool) -> EthereumClient:
|
||||
return EthereumClient(backend, golden_run)
|
||||
|
||||
# Pull all features from the base ragger conftest using the overridden configuration
|
||||
pytest_plugins = ("ragger.conftest.base_conftest", )
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
import hashlib
|
||||
from ecdsa import SigningKey
|
||||
from ecdsa.util import sigencode_der
|
||||
from ethereum_client.client import EthereumClient, EIP712FieldType
|
||||
import base64
|
||||
from app.client import EthereumClient, EIP712FieldType
|
||||
import keychain
|
||||
|
||||
# global variables
|
||||
app_client: EthereumClient = None
|
||||
@@ -24,7 +21,7 @@ sig_ctx = {}
|
||||
# Output = ('uint8', [2, None, 4]) | ('bool', [])
|
||||
def get_array_levels(typename):
|
||||
array_lvls = list()
|
||||
regex = re.compile("(.*)\[([0-9]*)\]$")
|
||||
regex = re.compile(r"(.*)\[([0-9]*)\]$")
|
||||
|
||||
while True:
|
||||
result = regex.search(typename)
|
||||
@@ -45,7 +42,7 @@ def get_array_levels(typename):
|
||||
# Input = "uint64" | "string"
|
||||
# Output = ('uint', 64) | ('string', None)
|
||||
def get_typesize(typename):
|
||||
regex = re.compile("^(\w+?)(\d*)$")
|
||||
regex = re.compile(r"^(\w+?)(\d*)$")
|
||||
result = regex.search(typename)
|
||||
typename = result.group(1)
|
||||
typesize = result.group(2)
|
||||
@@ -254,7 +251,7 @@ def send_filtering_message_info(display_name: str, filters_count: int):
|
||||
for char in display_name:
|
||||
to_sign.append(ord(char))
|
||||
|
||||
sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der)
|
||||
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
|
||||
app_client.eip712_filtering_message_info(display_name, filters_count, sig)
|
||||
|
||||
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
|
||||
@@ -272,7 +269,7 @@ def send_filtering_show_field(display_name):
|
||||
to_sign.append(ord(char))
|
||||
for char in display_name:
|
||||
to_sign.append(ord(char))
|
||||
sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der)
|
||||
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
|
||||
app_client.eip712_filtering_show_field(display_name, sig)
|
||||
|
||||
def read_filtering_file(domain, message, filtering_file_path):
|
||||
@@ -299,9 +296,6 @@ def init_signature_context(types, domain):
|
||||
global sig_ctx
|
||||
|
||||
handle_optional_domain_values(domain)
|
||||
env_key = os.environ["CAL_SIGNATURE_TEST_KEY"]
|
||||
key = base64.b64decode(env_key).decode() # base 64 string -> decode bytes -> string
|
||||
sig_ctx["key"] = SigningKey.from_pem(key, hashlib.sha256)
|
||||
caddr = domain["verifyingContract"]
|
||||
if caddr.startswith("0x"):
|
||||
caddr = caddr[2:]
|
||||
|
||||
27
tests/ragger/keychain.py
Normal file
@@ -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/cal.pem
Normal file
@@ -0,0 +1,8 @@
|
||||
-----BEGIN EC PARAMETERS-----
|
||||
BgUrgQQACg==
|
||||
-----END EC PARAMETERS-----
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHQCAQEEIHoMkoRaNq0neb1TxRBor4WouV8PQqJf02sg4eh768LpoAcGBSuBBAAK
|
||||
oUQDQgAETMqPrUlqpQQKAKfrL1zDuFN22IuhR6fXBUqZxkBWGIf+F6CW42w7Ujsk
|
||||
Tz4v9/hAribE53rTvHOa9d5vLXentg==
|
||||
-----END EC PRIVATE KEY-----
|
||||
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
|
||||
click>=8.0,<9.0 # needed by the CI as it installs an older version and breaks dependencies
|
||||
protobuf==3.20.1 # To fix the protobuf dependency bug
|
||||
ragger[speculos]
|
||||
ragger[speculos]>=1.6.0,<1.7.0
|
||||
pytest
|
||||
ecdsa
|
||||
simple-rlp
|
||||
|
||||
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 |