Merge pull request #359 from LedgerHQ/develop

App release 1.10.0
This commit is contained in:
apaillier-ledger
2022-10-27 17:56:10 +02:00
committed by GitHub
142 changed files with 7799 additions and 1337 deletions

View File

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

View File

@@ -1,7 +1,6 @@
name: Compilation
on:
workflow_dispatch:
push:
branches:
- master
@@ -9,48 +8,27 @@ on:
branches:
- master
- develop
workflow_dispatch:
jobs:
nano_debug_build:
name: Build debug application for NanoS, X and S+
nano_release_build:
name: Build release application for NanoS, X and S+
strategy:
matrix:
include:
- SDK: "$NANOS_SDK"
artifact: nanos
- SDK: "$NANOX_SDK"
artifact: nanox
- SDK: "$NANOSP_SDK"
artifact: nanosp
sdk: ["$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK"]
runs-on: ubuntu-latest
container:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- name: Clone
uses: actions/checkout@v2
with:
submodules: recursive
- name: Build an altcoin
run: |
make BOLOS_SDK=${{ matrix.SDK }} DEBUG=1 ALLOW_DATA=1 CHAIN=ethereum_classic
mv bin/app.elf ethereum_classic_${{ matrix.artifact }}.elf
- name: Upload altcoin binary
uses: actions/upload-artifact@v2
with:
name: ethereum_classic_${{ matrix.artifact }}
path: ./ethereum_classic_${{ matrix.artifact }}.elf
- name: Build Ethereum
run: |
make clean
make BOLOS_SDK=${{ matrix.SDK }} DEBUG=1 ALLOW_DATA=1
mv bin/app.elf ethereum_${{ matrix.artifact }}.elf
make -j BOLOS_SDK=${{ matrix.sdk }}
- name: Upload app binary
uses: actions/upload-artifact@v2
with:
name: ethereum_${{ matrix.artifact }}
path: ./ethereum_${{ matrix.artifact }}.elf
- name: Build an altcoin
run: |
make clean
make -j BOLOS_SDK=${{ matrix.sdk }} CHAIN=polygon

View File

@@ -1,7 +1,6 @@
name: Tests
on:
workflow_dispatch:
push:
branches:
- master
@@ -9,14 +8,14 @@ on:
branches:
- master
- develop
workflow_dispatch:
jobs:
scan-build:
name: Clang Static Analyzer
runs-on: ubuntu-latest
container:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- uses: actions/checkout@v2
@@ -25,6 +24,7 @@ jobs:
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
if: failure()
with:
@@ -39,7 +39,7 @@ jobs:
name: Building binaries for E2E Zemu tests
runs-on: ubuntu-latest
container:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- uses: actions/checkout@v2
@@ -52,19 +52,21 @@ jobs:
- name: Upload app binaries
uses: actions/upload-artifact@v2
with:
name: e2e_elfs
name: e2e_zemu_elfs
path: ./tests/zemu/elfs/
jobs-e2e-zemu-tests:
name: E2E Zemu tests
needs: [building_for_e2e_zemu_tests]
runs-on: ubuntu-latest
steps:
- name: Test
run: |
id
echo $HOME
echo $DISPLAY
- name: Checkout
uses: actions/checkout@v2
@@ -90,7 +92,7 @@ jobs:
path: tmp/
- name: Gather elfs
run: cp `find tmp/e2e_elfs/ -name "*.elf"` tests/zemu/elfs/
run: cp `find tmp/e2e_zemu_elfs/ -name "*.elf"` tests/zemu/elfs/
- name: Run zemu tests
run: cd tests/zemu/ && yarn test
@@ -104,7 +106,7 @@ jobs:
name: Building binaries for E2E Speculos tests
runs-on: ubuntu-latest
container:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest
steps:
- uses: actions/checkout@v2
@@ -112,14 +114,14 @@ jobs:
- name: Build testing binaries
run: |
mkdir tests/speculos/elfs
make clean && make DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOS_SDK && mv bin/app.elf tests/speculos/elfs/nanos.elf
make clean && make DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOX_SDK && mv bin/app.elf tests/speculos/elfs/nanox.elf
make clean && make DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOSP_SDK && mv bin/app.elf tests/speculos/elfs/nanosp.elf
make clean && make -j DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOS_SDK && mv bin/app.elf tests/speculos/elfs/nanos.elf
make clean && make -j DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK=$NANOX_SDK && mv bin/app.elf tests/speculos/elfs/nanox.elf
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
with:
name: e2e_elfs
name: e2e_speculos_elfs
path: ./tests/speculos/elfs
@@ -127,8 +129,8 @@ jobs:
name: Speculos tests
strategy:
matrix:
model: ["nanosp", "nanos", "nanox"]
model: ["nanosp", "nanos", "nanox"]
needs: [building_for_e2e_speculos_tests]
runs-on: ubuntu-latest
@@ -145,7 +147,7 @@ jobs:
path: tmp/
- name: Gather elfs
run: cp `find tmp/e2e_elfs/ -name "*.elf"` tests/speculos/elfs/
run: cp `find tmp/e2e_speculos_elfs/ -name "*.elf"` tests/speculos/elfs/
- name: Install dependencies
run: |
@@ -154,6 +156,123 @@ jobs:
pip install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt
- name: Run speculos tests
run: |
run: |
cd tests/speculos
pytest --model ${{ matrix.model }} --path ./elfs/${{ matrix.model }}.elf --display headless
pytest --model ${{ matrix.model }} --path ./elfs/${{ matrix.model }}.elf --display headless
# =====================================================
# RAGGER TESTS
# =====================================================
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 speculos
# Used for the cache key
echo "py_deps=$(pip freeze | md5sum | cut -d' ' -f1)" >> $GITHUB_ENV
- name: Download QEMU
run: |
sudo apt install --download-only -y qemu-user-static
mkdir -p tests/ragger/packages
cp /var/cache/apt/archives/*.deb tests/ragger/packages/
# Used for the cache key
echo "deb_deps=$(find /var/cache/apt/archives/ -maxdepth 0 -type f -name '*.deb' | md5sum | cut -d' ' -f 1)" >> $GITHUB_ENV
- name: Set up cache
uses: actions/cache@v3
with:
key: ${{ runner.os }}-raggenv-${{ env.py_deps }}-${{ env.deb_deps }}
path: |
tests/ragger/venv/
tests/ragger/packages/
outputs:
py_deps: ${{ env.py_deps }}
deb_deps: ${{ env.deb_deps }}
jobs-ragger-tests:
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 }}

View File

@@ -5,6 +5,12 @@ 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.0](https://github.com/ledgerhq/app-ethereum/compare/1.9.20...1.10.0) - 2022-10-26
### Changed
- EIP-712 signatures are now computed on-device and display their content (clear-signing) (LNX & LNS+)
## [1.9.20](https://github.com/ledgerhq/app-ethereum/compare/1.9.19...1.9.20) - 2022-10-10
### Added

View File

@@ -33,8 +33,8 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'"
##################
APPVERSION_M=1
APPVERSION_N=9
APPVERSION_P=20
APPVERSION_N=10
APPVERSION_P=0
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
APP_LOAD_FLAGS= --appFlags 0xa40 --dep Ethereum:$(APPVERSION)
@@ -137,6 +137,22 @@ DEFINES += HAVE_NFT_TESTING_KEY
endif
endif
# Dynamic memory allocator
ifneq ($(TARGET_NAME),TARGET_NANOS)
DEFINES += HAVE_DYN_MEM_ALLOC
endif
# EIP-712
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
endif
# Enabling debug PRINTF
DEBUG:=0
ifneq ($(DEBUG),0)

File diff suppressed because it is too large Load Diff

View File

@@ -21,16 +21,30 @@
#define INS_PROVIDE_NFT_INFORMATION 0x14
#define INS_SET_PLUGIN 0x16
#define INS_PERFORM_PRIVACY_OPERATION 0x18
#define INS_EIP712_STRUCT_DEF 0x1A
#define INS_EIP712_STRUCT_IMPL 0x1C
#define INS_EIP712_FILTERING 0x1E
#define P1_CONFIRM 0x01
#define P1_NON_CONFIRM 0x00
#define P2_NO_CHAINCODE 0x00
#define P2_CHAINCODE 0x01
#define P1_FIRST 0x00
#define P1_MORE 0x80
#define P2_EIP712_LEGACY_IMPLEM 0x00
#define P2_EIP712_FULL_IMPLEM 0x01
#define COMMON_CLA 0xB0
#define COMMON_INS_GET_WALLET_ID 0x04
#define APDU_RESPONSE_OK 0x9000
#define APDU_RESPONSE_ERROR_NO_INFO 0x6a00
#define APDU_RESPONSE_INVALID_DATA 0x6a80
#define APDU_RESPONSE_INSUFFICIENT_MEMORY 0x6a84
#define APDU_RESPONSE_INVALID_INS 0x6d00
#define APDU_RESPONSE_INVALID_P1_P2 0x6b00
#define APDU_RESPONSE_CONDITION_NOT_SATISFIED 0x6985
#define APDU_RESPONSE_REF_DATA_NOT_FOUND 0x6a88
#ifdef HAVE_STARKWARE
#define STARKWARE_CLA 0xF0
@@ -51,12 +65,7 @@
#endif
#define OFFSET_CLA 0
#define OFFSET_INS 1
#define OFFSET_P1 2
#define OFFSET_P2 3
#define OFFSET_LC 4
#define OFFSET_CDATA 5
enum { OFFSET_CLA = 0, OFFSET_INS, OFFSET_P1, OFFSET_P2, OFFSET_LC, OFFSET_CDATA };
#define ERR_APDU_EMPTY 0x6982
#define ERR_APDU_SIZE_MISMATCH 0x6983
@@ -64,62 +73,62 @@
void handleGetPublicKey(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleProvideErc20TokenInformation(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
const uint8_t *workBuffer,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleProvideNFTInformation(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleSign(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleGetAppConfiguration(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
bool handleSignPersonalMessage(uint8_t p1,
uint8_t p2,
const uint8_t *const payload,
uint8_t length);
void handleSignEIP712Message(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleSignEIP712Message_v0(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleSetExternalPlugin(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleSetPlugin(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handlePerformPrivacyOperation(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
@@ -128,13 +137,13 @@ void handlePerformPrivacyOperation(uint8_t p1,
void handleGetEth2PublicKey(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleSetEth2WinthdrawalIndex(uint8_t p1,
uint8_t p2,
uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
@@ -145,28 +154,30 @@ void handleSetEth2WinthdrawalIndex(uint8_t p1,
void handleStarkwareGetPublicKey(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleStarkwareSignMessage(uint8_t p1,
uint8_t p2,
uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleStarkwareProvideQuantum(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleStarkwareUnsafeSign(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx);
#endif
extern uint16_t apdu_response_code;
#endif // _APDU_CONSTANTS_H_

View File

@@ -25,6 +25,11 @@ void ui_191_switch_to_message_end(void);
void ui_191_switch_to_sign(void);
void ui_191_switch_to_question(void);
// EIP-712
void ui_712_start(void);
void ui_712_switch_to_message(void);
void ui_712_switch_to_sign(void);
#include "ui_callbacks.h"
#include <string.h>

View File

@@ -28,6 +28,7 @@
#include "handle_swap_sign_transaction.h"
#include "handle_get_printable_amount.h"
#include "handle_check_address.h"
#include "commands_712.h"
#ifdef HAVE_STARKWARE
#include "stark_crypto.h"
@@ -48,6 +49,7 @@ strings_t strings;
cx_sha3_t global_sha3;
uint8_t appState;
uint16_t apdu_response_code;
bool called_from_swap;
pluginType_t pluginType;
#ifdef HAVE_STARKWARE
@@ -491,7 +493,7 @@ void handleGetWalletId(volatile unsigned int *tx) {
#endif // HAVE_WALLET_ID_SDK
const uint8_t *parseBip32(const uint8_t *dataBuffer, uint16_t *dataLength, bip32_path_t *bip32) {
const uint8_t *parseBip32(const uint8_t *dataBuffer, uint8_t *dataLength, bip32_path_t *bip32) {
if (*dataLength < 1) {
PRINTF("Invalid data\n");
return NULL;
@@ -672,13 +674,25 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
break;
case INS_SIGN_EIP_712_MESSAGE:
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
handleSignEIP712Message(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
switch (G_io_apdu_buffer[OFFSET_P2]) {
case P2_EIP712_LEGACY_IMPLEM:
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
handleSignEIP712Message_v0(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
G_io_apdu_buffer[OFFSET_LC],
flags,
tx);
break;
#ifdef HAVE_EIP712_FULL_SUPPORT
case P2_EIP712_FULL_IMPLEM:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_sign(G_io_apdu_buffer);
break;
#endif // HAVE_EIP712_FULL_SUPPORT
default:
THROW(APDU_RESPONSE_INVALID_P1_P2);
}
break;
#ifdef HAVE_ETH2
@@ -704,6 +718,23 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
#endif
#ifdef HAVE_EIP712_FULL_SUPPORT
case INS_EIP712_STRUCT_DEF:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_struct_def(G_io_apdu_buffer);
break;
case INS_EIP712_STRUCT_IMPL:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_struct_impl(G_io_apdu_buffer);
break;
case INS_EIP712_FILTERING:
*flags |= IO_ASYNCH_REPLY;
handle_eip712_filtering(G_io_apdu_buffer);
break;
#endif // HAVE_EIP712_FULL_SUPPORT
#if 0
case 0xFF: // return to dashboard
goto return_to_dashboard;

View File

@@ -27,6 +27,9 @@ typedef struct internalStorage_t {
unsigned char dataAllowed;
unsigned char contractDetails;
unsigned char displayNonce;
#ifdef HAVE_EIP712_FULL_SUPPORT
bool verbose_eip712;
#endif // HAVE_EIP712_FULL_SUPPORT
uint8_t initialized;
} internalStorage_t;
@@ -219,6 +222,6 @@ extern uint32_t eth2WithdrawalIndex;
#endif
void reset_app_context(void);
const uint8_t *parseBip32(const uint8_t *, uint16_t *, bip32_path_t *);
const uint8_t *parseBip32(const uint8_t *dataBuffer, uint8_t *dataLength, bip32_path_t *bip32);
#endif // _SHARED_CONTEXT_H_

View File

@@ -4,6 +4,7 @@
#include "shared_context.h"
#include "ethUtils.h"
#include "uint256.h"
#include "uint_common.h"
#include "os_io_seproxyhal.h"

View File

@@ -19,7 +19,7 @@
#include "tokens.h"
const tokenDefinition_t const TOKENS_EXTRA[NUM_TOKENS_EXTRA] = {
const tokenDefinition_t TOKENS_EXTRA[NUM_TOKENS_EXTRA] = {
// Ropsten DeversiFi tokens
{{0x4c, 0x5f, 0x66, 0x59, 0x61, 0x97, 0xa8, 0x6f, 0xb3, 0x0a,

View File

@@ -44,7 +44,7 @@ extern tokenDefinition_t const TOKENS_EXTRA[NUM_TOKENS_EXTRA];
#ifndef HAVE_TOKENS_LIST
static const uint8_t LEDGER_SIGNATURE_PUBLIC_KEY[] = {
#ifndef LEDGER_TEST_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,

View File

@@ -14,8 +14,6 @@ unsigned int io_seproxyhal_touch_signMessage_ok(void);
unsigned int io_seproxyhal_touch_signMessage_cancel(void);
unsigned int io_seproxyhal_touch_data_ok(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_data_cancel(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_signMessage712_v0_ok(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_signMessage712_v0_cancel(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_eth2_address_ok(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_privacy_ok(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_privacy_cancel(const bagl_element_t *e);

View File

@@ -20,38 +20,44 @@
#include "ethUstream.h"
#include "ethUtils.h"
#include "uint128.h"
#include "uint256.h"
#include "tokens.h"
#include "utils.h"
static const unsigned char hex_digits[] =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
void array_hexstr(char *strbuf, const void *bin, unsigned int len) {
while (len--) {
*strbuf++ = hex_digits[((*((char *) bin)) >> 4) & 0xF];
*strbuf++ = hex_digits[(*((char *) bin)) & 0xF];
*strbuf++ = HEXDIGITS[((*((char *) bin)) >> 4) & 0xF];
*strbuf++ = HEXDIGITS[(*((char *) bin)) & 0xF];
bin = (const void *) ((unsigned int) bin + 1);
}
*strbuf = 0; // EOS
}
void convertUint256BE(uint8_t *data, uint32_t length, uint256_t *target) {
uint8_t tmp[INT256_LENGTH];
memset(tmp, 0, 32);
memmove(tmp + 32 - length, data, length);
readu256BE(tmp, target);
void convertUint64BEto128(const uint8_t *const data, uint32_t length, uint128_t *const target) {
uint8_t tmp[INT128_LENGTH];
int64_t value;
value = u64_from_BE(data, length);
memset(tmp, ((value < 0) ? 0xff : 0), sizeof(tmp) - length);
memmove(tmp + sizeof(tmp) - length, data, length);
readu128BE(tmp, target);
}
int local_strchr(char *string, char ch) {
unsigned int length = strlen(string);
unsigned int i;
for (i = 0; i < length; i++) {
if (string[i] == ch) {
return i;
}
}
return -1;
void convertUint128BE(const uint8_t *const data, uint32_t length, uint128_t *const target) {
uint8_t tmp[INT128_LENGTH];
memset(tmp, 0, sizeof(tmp) - length);
memmove(tmp + sizeof(tmp) - length, data, length);
readu128BE(tmp, target);
}
void convertUint256BE(const uint8_t *const data, uint32_t length, uint256_t *const target) {
uint8_t tmp[INT256_LENGTH];
memset(tmp, 0, sizeof(tmp) - length);
memmove(tmp + sizeof(tmp) - length, data, length);
readu256BE(tmp, target);
}
uint64_t u64_from_BE(const uint8_t *in, uint8_t size) {

View File

@@ -22,11 +22,13 @@
#include "uint256.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
void array_hexstr(char* strbuf, const void* bin, unsigned int len);
void convertUint256BE(uint8_t* data, uint32_t length, uint256_t* target);
int local_strchr(char* string, char ch);
void convertUint128BE(const uint8_t* const data, uint32_t length, uint128_t* const target);
void convertUint256BE(const uint8_t* const data, uint32_t length, uint256_t* const target);
void convertUint64BEto128(const uint8_t* const data, uint32_t length, uint128_t* const target);
uint64_t u64_from_BE(const uint8_t* in, uint8_t size);

View File

@@ -1,10 +1,19 @@
#include "shared_context.h"
#include "ui_callbacks.h"
#include "common_ui.h"
#include "utils.h"
#define ENABLED_STR "Enabled"
#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);
#ifdef HAVE_EIP712_FULL_SUPPORT
void switch_settings_verbose_eip712(void);
#endif // HAVE_EIP712_FULL_SUPPORT
//////////////////////////////////////////////////////////////////////
// clang-format off
@@ -48,75 +57,83 @@ UX_FLOW(ux_idle_flow,
&ux_idle_flow_4_step,
FLOW_LOOP);
#if defined(TARGET_NANOS)
// clang-format off
UX_STEP_CB(
ux_settings_flow_1_step,
ux_settings_flow_blind_signing_step,
#ifdef TARGET_NANOS
bnnn_paging,
switch_settings_blind_signing(),
{
.title = "Blind signing",
.text = strings.common.fullAddress,
});
UX_STEP_CB(
ux_settings_flow_2_step,
bnnn_paging,
switch_settings_display_data(),
{
.title = "Debug data",
.text = strings.common.fullAddress + 12
});
UX_STEP_CB(
ux_settings_flow_3_step,
bnnn_paging,
switch_settings_display_nonce(),
{
.title = "Account nonce",
.text = strings.common.fullAddress + 26
});
#else
UX_STEP_CB(
ux_settings_flow_1_step,
bnnn,
#endif
switch_settings_blind_signing(),
{
#ifdef TARGET_NANOS
.title = "Blind signing",
.text =
#else
"Blind signing",
"Enable transaction",
"Transaction",
"blind signing",
strings.common.fullAddress,
#endif
strings.common.fullAddress
});
UX_STEP_CB(
ux_settings_flow_2_step,
ux_settings_flow_display_data_step,
#ifdef TARGET_NANOS
bnnn_paging,
#else
bnnn,
#endif
switch_settings_display_data(),
{
#ifdef TARGET_NANOS
.title = "Debug data",
.text =
#else
"Debug data",
"Display contract data",
"Show contract data",
"details",
strings.common.fullAddress + 12
});
UX_STEP_CB(
ux_settings_flow_3_step,
bnnn,
switch_settings_display_nonce(),
{
"Nonce",
"Display account nonce",
"in transactions",
strings.common.fullAddress + 26
});
#endif
strings.common.fullAddress + BUF_INCREMENT
});
UX_STEP_CB(
ux_settings_flow_4_step,
ux_settings_flow_display_nonce_step,
#ifdef TARGET_NANOS
bnnn_paging,
#else
bnnn,
#endif
switch_settings_display_nonce(),
{
#ifdef TARGET_NANOS
.title = "Account nonce",
.text =
#else
"Nonce",
"Show account nonce",
"in transactions",
#endif
strings.common.fullAddress + (BUF_INCREMENT * 2)
});
#ifdef HAVE_EIP712_FULL_SUPPORT
UX_STEP_CB(
ux_settings_flow_verbose_eip712_step,
bnnn,
switch_settings_verbose_eip712(),
{
"Verbose EIP-712",
"Ignore filtering &",
"display raw content",
strings.common.fullAddress + (BUF_INCREMENT * 3)
});
#endif // HAVE_EIP712_FULL_SUPPORT
UX_STEP_CB(
ux_settings_flow_back_step,
pb,
ui_idle(),
{
@@ -126,43 +143,63 @@ UX_STEP_CB(
// clang-format on
UX_FLOW(ux_settings_flow,
&ux_settings_flow_1_step,
&ux_settings_flow_2_step,
&ux_settings_flow_3_step,
&ux_settings_flow_4_step);
&ux_settings_flow_blind_signing_step,
&ux_settings_flow_display_data_step,
&ux_settings_flow_display_nonce_step,
#ifdef HAVE_EIP712_FULL_SUPPORT
&ux_settings_flow_verbose_eip712_step,
#endif // HAVE_EIP712_FULL_SUPPORT
&ux_settings_flow_back_step);
void display_settings(const ux_flow_step_t* const start_step) {
strlcpy(strings.common.fullAddress, (N_storage.dataAllowed ? "Enabled" : "NOT Enabled"), 12);
strlcpy(strings.common.fullAddress + 12,
(N_storage.contractDetails ? "Displayed" : "NOT Displayed"),
26 - 12);
strlcpy(strings.common.fullAddress + 26,
(N_storage.displayNonce ? "Displayed" : "NOT Displayed"),
sizeof(strings.common.fullAddress) - 26);
bool settings[] = {N_storage.dataAllowed,
N_storage.contractDetails,
N_storage.displayNonce,
#ifdef HAVE_EIP712_FULL_SUPPORT
N_storage.verbose_eip712
#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;
}
ux_flow_init(0, ux_settings_flow, start_step);
}
void switch_settings_blind_signing() {
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_1_step);
display_settings(&ux_settings_flow_blind_signing_step);
}
void switch_settings_display_data() {
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_2_step);
display_settings(&ux_settings_flow_display_data_step);
}
void switch_settings_display_nonce() {
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_3_step);
display_settings(&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);
}
#endif // HAVE_EIP712_FULL_SUPPORT
//////////////////////////////////////////////////////////////////////
// clang-format off
#if defined(TARGET_NANOS)
#ifdef TARGET_NANOS
UX_STEP_CB(
ux_warning_contract_data_step,
bnnn_paging,
@@ -171,7 +208,7 @@ UX_STEP_CB(
"Error",
"Blind signing must be enabled in Settings",
});
#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
#else
UX_STEP_CB(
ux_warning_contract_data_step,
pnn,

View File

@@ -1,62 +1,87 @@
#include "shared_context.h"
#include "ui_callbacks.h"
#ifdef HAVE_EIP712_FULL_SUPPORT
void prepare_domain_hash_v0() {
snprintf(strings.tmp.tmp, 70, "0x%.*H", 32, tmpCtx.messageSigningContext712.domainHash);
}
#include "ui_logic.h"
#include "shared_context.h" // strings
void prepare_message_hash_v0() {
snprintf(strings.tmp.tmp, 70, "0x%.*H", 32, tmpCtx.messageSigningContext712.messageHash);
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;
}
}
}
// clang-format off
UX_STEP_NOCB(
ux_sign_712_v0_flow_1_step,
ux_712_step_review,
pnn,
{
&C_icon_certificate,
"Sign",
&C_icon_eye,
"Review",
"typed message",
});
UX_STEP_NOCB_INIT(
ux_sign_712_v0_flow_2_step,
bnnn_paging,
prepare_domain_hash_v0(),
{
.title = "Domain hash",
UX_STEP_NOCB(
ux_712_step_dynamic,
bnnn_paging,
{
.title = strings.tmp.tmp2,
.text = strings.tmp.tmp,
});
UX_STEP_NOCB_INIT(
ux_sign_712_v0_flow_3_step,
bnnn_paging,
prepare_message_hash_v0(),
{
.title = "Message hash",
.text = strings.tmp.tmp,
});
}
);
UX_STEP_INIT(
ux_712_step_dummy,
NULL,
NULL,
{
dummy_cb();
}
);
UX_STEP_CB(
ux_sign_712_v0_flow_4_step,
pbb,
io_seproxyhal_touch_signMessage712_v0_ok(NULL),
ux_712_step_approve,
pb,
ui_712_approve(NULL),
{
&C_icon_validate_14,
"Sign",
"message",
"Approve",
});
UX_STEP_CB(
ux_sign_712_v0_flow_5_step,
pbb,
io_seproxyhal_touch_signMessage712_v0_cancel(NULL),
ux_712_step_reject,
pb,
ui_712_reject(NULL),
{
&C_icon_crossmark,
"Cancel",
"signature",
"Reject",
});
// clang-format on
UX_FLOW(ux_sign_712_v0_flow,
&ux_sign_712_v0_flow_1_step,
&ux_sign_712_v0_flow_2_step,
&ux_sign_712_v0_flow_3_step,
&ux_sign_712_v0_flow_4_step,
&ux_sign_712_v0_flow_5_step);
UX_FLOW(ux_712_flow,
&ux_712_step_review,
&ux_712_step_dynamic,
&ux_712_step_dummy,
&ux_712_step_approve,
&ux_712_step_reject);
void ui_712_start(void) {
ux_flow_init(0, ux_712_flow, NULL);
ui_pos = UI_712_POS_REVIEW;
}
void ui_712_switch_to_message(void) {
ux_flow_init(0, ux_712_flow, &ux_712_step_dynamic);
ui_pos = UI_712_POS_REVIEW;
}
void ui_712_switch_to_sign(void) {
ux_flow_init(0, ux_712_flow, &ux_712_step_approve);
ui_pos = UI_712_POS_END;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,72 @@
#include "shared_context.h"
#include "ui_callbacks.h"
#include "common_712.h"
#include "ethUtils.h"
void prepare_domain_hash_v0() {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"0x%.*H",
KECCAK256_HASH_BYTESIZE,
tmpCtx.messageSigningContext712.domainHash);
}
void prepare_message_hash_v0() {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"0x%.*H",
KECCAK256_HASH_BYTESIZE,
tmpCtx.messageSigningContext712.messageHash);
}
// clang-format off
UX_STEP_NOCB(
ux_sign_712_v0_flow_1_step,
pnn,
{
&C_icon_certificate,
"Sign",
"typed message",
});
UX_STEP_NOCB_INIT(
ux_sign_712_v0_flow_2_step,
bnnn_paging,
prepare_domain_hash_v0(),
{
.title = "Domain hash",
.text = strings.tmp.tmp,
});
UX_STEP_NOCB_INIT(
ux_sign_712_v0_flow_3_step,
bnnn_paging,
prepare_message_hash_v0(),
{
.title = "Message hash",
.text = strings.tmp.tmp,
});
UX_STEP_CB(
ux_sign_712_v0_flow_4_step,
pbb,
ui_712_approve_cb(NULL),
{
&C_icon_validate_14,
"Sign",
"message",
});
UX_STEP_CB(
ux_sign_712_v0_flow_5_step,
pbb,
ui_712_reject_cb(NULL),
{
&C_icon_crossmark,
"Cancel",
"signature",
});
// clang-format on
UX_FLOW(ux_sign_712_v0_flow,
&ux_sign_712_v0_flow_1_step,
&ux_sign_712_v0_flow_2_step,
&ux_sign_712_v0_flow_3_step,
&ux_sign_712_v0_flow_4_step,
&ux_sign_712_v0_flow_5_step);

View File

@@ -7,6 +7,7 @@
#include "eth_plugin_handler.h"
#include "ui_plugin.h"
#include "common_ui.h"
#include "plugins.h"
// clang-format off
UX_STEP_NOCB(

View File

@@ -3,18 +3,7 @@
#include "shared_context.h"
#include "ui_callbacks.h"
#include "ethUtils.h"
void stark_sign_display_master_account() {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"0x%.*H",
32,
dataContext.starkContext.transferDestination);
}
void stark_sign_display_condition_fact() {
snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "0x%.*H", 32, dataContext.starkContext.fact);
}
#include "starkDisplayUtils.h"
// clang-format off
UX_STEP_NOCB(ux_stark_limit_order_1_step,

View File

@@ -3,38 +3,11 @@
#include "eth_plugin_handler.h"
#include "ui_callbacks.h"
#include "ui_plugin.h"
#include "plugins.h"
// This function is not exported by the SDK
void ux_layout_paging_redisplay_by_addr(unsigned int stack_slot);
void plugin_ui_get_id() {
ethQueryContractID_t pluginQueryContractID;
eth_plugin_prepare_query_contract_ID(&pluginQueryContractID,
strings.common.fullAddress,
sizeof(strings.common.fullAddress),
strings.common.fullAmount,
sizeof(strings.common.fullAmount));
// Query the original contract for ID if it's not an internal alias
if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_ID, (void *) &pluginQueryContractID)) {
PRINTF("Plugin query contract ID call failed\n");
io_seproxyhal_touch_tx_cancel(NULL);
}
}
void plugin_ui_get_item() {
ethQueryContractUI_t pluginQueryContractUI;
eth_plugin_prepare_query_contract_UI(&pluginQueryContractUI,
dataContext.tokenContext.pluginUiCurrentItem,
strings.common.fullAddress,
sizeof(strings.common.fullAddress),
strings.common.fullAmount,
sizeof(strings.common.fullAmount));
if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_UI, (void *) &pluginQueryContractUI)) {
PRINTF("Plugin query contract UI call failed\n");
io_seproxyhal_touch_tx_cancel(NULL);
}
}
void display_next_plugin_item(bool entering) {
if (entering) {
if (dataContext.tokenContext.pluginUiState == PLUGIN_UI_OUTSIDE) {

View File

@@ -1,8 +1,6 @@
#ifndef _UI_PLUGIN_H_
#define _UI_PLUGIN_H_
void plugin_ui_get_id();
void plugin_ui_get_item();
void display_next_plugin_item(bool entering);
#endif // _UI_PLUGIN_H_

View File

@@ -37,6 +37,7 @@ typedef customStatus_e (*ustreamProcess_t)(struct txContext_t *context);
#define TX_FLAG_TYPE 0x01
#define ADDRESS_LENGTH 20
#define INT128_LENGTH 16
#define INT256_LENGTH 32
// First variant of every Tx enum.

View File

@@ -27,8 +27,6 @@
#include <stdint.h>
#include <string.h>
#include "os.h"
#include "cx.h"
#include "ethUtils.h"
#include "chainConfig.h"
#include "ethUstream.h"

View File

@@ -23,6 +23,8 @@
#include "cx.h"
#include "chainConfig.h"
#define KECCAK256_HASH_BYTESIZE 32
/**
* @brief Decode an RLP encoded field - see
* https://github.com/ethereum/wiki/wiki/RLP

65
src_common/mem.c Normal file
View File

@@ -0,0 +1,65 @@
/**
* Dynamic allocator that uses a fixed-length buffer that is hopefully big enough
*
* The two functions alloc & dealloc use the buffer as a simple stack.
* Especially useful when an unpredictable amount of data will be received and have to be stored
* during the transaction but discarded right after.
*/
#ifdef HAVE_DYN_MEM_ALLOC
#include <stdint.h>
#include "mem.h"
#define SIZE_MEM_BUFFER 8192
static uint8_t mem_buffer[SIZE_MEM_BUFFER];
static size_t mem_idx;
/**
* Initializes the memory buffer index
*/
void mem_init(void) {
mem_idx = 0;
}
/**
* Resets the memory buffer index
*/
void mem_reset(void) {
mem_init();
}
/**
* Allocates (push) a chunk of the memory buffer of a given size.
*
* Checks to see if there are enough space left in the memory buffer, returns
* the current location in the memory buffer and moves the index accordingly.
*
* @param[in] size Requested allocation size in bytes
* @return Allocated memory pointer; \ref NULL if not enough space left.
*/
void *mem_alloc(size_t size) {
if ((mem_idx + size) > SIZE_MEM_BUFFER) // Buffer exceeded
{
return NULL;
}
mem_idx += size;
return &mem_buffer[mem_idx - size];
}
/**
* De-allocates (pop) a chunk of memory buffer by a given size.
*
* @param[in] size Requested deallocation size in bytes
*/
void mem_dealloc(size_t size) {
if (size > mem_idx) // More than is already allocated
{
mem_idx = 0;
} else {
mem_idx -= size;
}
}
#endif // HAVE_DYN_MEM_ALLOC

15
src_common/mem.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef MEM_H_
#define MEM_H_
#ifdef HAVE_DYN_MEM_ALLOC
#include <stdlib.h>
void mem_init(void);
void mem_reset(void);
void *mem_alloc(size_t size);
void mem_dealloc(size_t size);
#endif // HAVE_DYN_MEM_ALLOC
#endif // MEM_H_

60
src_common/mem_utils.c Normal file
View File

@@ -0,0 +1,60 @@
#ifdef HAVE_DYN_MEM_ALLOC
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "mem.h"
#include "mem_utils.h"
/**
* Format an unsigned number up to 32-bit into memory into an ASCII string.
*
* @param[in] value Value to write in memory
* @param[out] length number of characters written to memory
*
* @return pointer to memory area or \ref NULL if the allocation failed
*/
char *mem_alloc_and_format_uint(uint32_t value, uint8_t *const length) {
char *mem_ptr;
uint32_t value_copy;
uint8_t size;
size = 1; // minimum size, even if 0
value_copy = value;
while (value_copy >= 10) {
value_copy /= 10;
size += 1;
}
// +1 for the null character
if ((mem_ptr = mem_alloc(sizeof(char) * (size + 1)))) {
snprintf(mem_ptr, (size + 1), "%u", value);
mem_dealloc(sizeof(char)); // to skip the null character
if (length != NULL) {
*length = size;
}
}
return mem_ptr;
}
/**
* Allocate and align, required when dealing with pointers of multi-bytes data
* like structures that will be dereferenced at runtime.
*
* @param[in] size the size of the data we want to allocate in memory
* @param[in] alignment the byte alignment needed
*
* @return pointer to the memory area, \ref NULL if the allocation failed
*/
void *mem_alloc_and_align(size_t size, size_t alignment) {
uint8_t align_diff = (uintptr_t) mem_alloc(0) % alignment;
if (align_diff > 0) // alignment needed
{
if (mem_alloc(alignment - align_diff) == NULL) {
return NULL;
}
}
return mem_alloc(size);
}
#endif // HAVE_DYN_MEM_ALLOC

16
src_common/mem_utils.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef MEM_UTILS_H_
#define MEM_UTILS_H_
#ifdef HAVE_DYN_MEM_ALLOC
#include <stdint.h>
#include <stdbool.h>
#define MEM_ALLOC_AND_ALIGN_TYPE(type) mem_alloc_and_align(sizeof(type), __alignof__(type))
char *mem_alloc_and_format_uint(uint32_t value, uint8_t *const written_chars);
void *mem_alloc_and_align(size_t size, size_t alignment);
#endif // HAVE_DYN_MEM_ALLOC
#endif // MEM_UTILS_H_

40
src_common/plugins.c Normal file
View File

@@ -0,0 +1,40 @@
#include "eth_plugin_handler.h"
#include "ui_callbacks.h"
void plugin_ui_get_id(void) {
ethQueryContractID_t pluginQueryContractID;
eth_plugin_prepare_query_contract_ID(&pluginQueryContractID,
strings.common.fullAddress,
sizeof(strings.common.fullAddress),
strings.common.fullAmount,
sizeof(strings.common.fullAmount));
// Query the original contract for ID if it's not an internal alias
if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_ID, (void *) &pluginQueryContractID)) {
PRINTF("Plugin query contract ID call failed\n");
io_seproxyhal_touch_tx_cancel(NULL);
}
}
void plugin_ui_get_item_internal(char *title_buffer,
size_t title_buffer_size,
char *msg_buffer,
size_t msg_buffer_size) {
ethQueryContractUI_t pluginQueryContractUI;
eth_plugin_prepare_query_contract_UI(&pluginQueryContractUI,
dataContext.tokenContext.pluginUiCurrentItem,
title_buffer,
title_buffer_size,
msg_buffer,
msg_buffer_size);
if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_UI, (void *) &pluginQueryContractUI)) {
PRINTF("Plugin query contract UI call failed\n");
io_seproxyhal_touch_tx_cancel(NULL);
}
}
void plugin_ui_get_item(void) {
plugin_ui_get_item_internal(strings.common.fullAddress,
sizeof(strings.common.fullAddress),
strings.common.fullAmount,
sizeof(strings.common.fullAmount));
}

11
src_common/plugins.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef _PLUGIN_H_
#define _PLUGIN_H_
void plugin_ui_get_id();
void plugin_ui_get_item();
void plugin_ui_get_item_internal(uint8_t *title_buffer,
size_t title_buffer_size,
uint8_t *msg_buffer,
size_t msg_buffer_size);
#endif // _PLUGIN_H_

View File

@@ -0,0 +1,17 @@
#ifdef HAVE_STARKWARE
#include "shared_context.h"
void stark_sign_display_master_account() {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"0x%.*H",
32,
dataContext.starkContext.transferDestination);
}
void stark_sign_display_condition_fact() {
snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "0x%.*H", 32, dataContext.starkContext.fact);
}
#endif

View File

@@ -0,0 +1,6 @@
#ifdef HAVE_STARKWARE
void stark_sign_display_master_account();
void stark_sign_display_condition_fact();
#endif

292
src_common/uint128.c Normal file
View File

@@ -0,0 +1,292 @@
/*******************************************************************************
* Ledger Ethereum App
* (c) 2016-2019 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
// Adapted from https://github.com/calccrypto/uint256_t
#include <stdio.h>
#include <string.h>
#include "uint128.h"
#include "uint_common.h"
#include "ethUtils.h" // HEXDIGITS
void readu128BE(const uint8_t *const buffer, uint128_t *const target) {
UPPER_P(target) = readUint64BE(buffer);
LOWER_P(target) = readUint64BE(buffer + 8);
}
bool zero128(const uint128_t *const number) {
return ((LOWER_P(number) == 0) && (UPPER_P(number) == 0));
}
void copy128(uint128_t *const target, const uint128_t *const number) {
UPPER_P(target) = UPPER_P(number);
LOWER_P(target) = LOWER_P(number);
}
void clear128(uint128_t *const target) {
UPPER_P(target) = 0;
LOWER_P(target) = 0;
}
void shiftl128(const uint128_t *const number, uint32_t value, uint128_t *const target) {
if (value >= 128) {
clear128(target);
} else if (value == 64) {
UPPER_P(target) = LOWER_P(number);
LOWER_P(target) = 0;
} else if (value == 0) {
copy128(target, number);
} else if (value < 64) {
UPPER_P(target) = (UPPER_P(number) << value) + (LOWER_P(number) >> (64 - value));
LOWER_P(target) = (LOWER_P(number) << value);
} else if ((128 > value) && (value > 64)) {
UPPER_P(target) = LOWER_P(number) << (value - 64);
LOWER_P(target) = 0;
} else {
clear128(target);
}
}
void shiftr128(const uint128_t *const number, uint32_t value, uint128_t *const target) {
if (value >= 128) {
clear128(target);
} else if (value == 64) {
UPPER_P(target) = 0;
LOWER_P(target) = UPPER_P(number);
} else if (value == 0) {
copy128(target, number);
} else if (value < 64) {
uint128_t result;
UPPER(result) = UPPER_P(number) >> value;
LOWER(result) = (UPPER_P(number) << (64 - value)) + (LOWER_P(number) >> value);
copy128(target, &result);
} else if ((128 > value) && (value > 64)) {
LOWER_P(target) = UPPER_P(number) >> (value - 64);
UPPER_P(target) = 0;
} else {
clear128(target);
}
}
uint32_t bits128(const uint128_t *const number) {
uint32_t result = 0;
if (UPPER_P(number)) {
result = 64;
uint64_t up = UPPER_P(number);
while (up) {
up >>= 1;
result++;
}
} else {
uint64_t low = LOWER_P(number);
while (low) {
low >>= 1;
result++;
}
}
return result;
}
bool equal128(const uint128_t *const number1, const uint128_t *const number2) {
return (UPPER_P(number1) == UPPER_P(number2)) && (LOWER_P(number1) == LOWER_P(number2));
}
bool gt128(const uint128_t *const number1, const uint128_t *const number2) {
if (UPPER_P(number1) == UPPER_P(number2)) {
return (LOWER_P(number1) > LOWER_P(number2));
}
return (UPPER_P(number1) > UPPER_P(number2));
}
bool gte128(const uint128_t *const number1, const uint128_t *const number2) {
return gt128(number1, number2) || equal128(number1, number2);
}
void add128(const uint128_t *const number1,
const uint128_t *const number2,
uint128_t *const target) {
UPPER_P(target) = UPPER_P(number1) + UPPER_P(number2) +
((LOWER_P(number1) + LOWER_P(number2)) < LOWER_P(number1));
LOWER_P(target) = LOWER_P(number1) + LOWER_P(number2);
}
void sub128(const uint128_t *const number1,
const uint128_t *const number2,
uint128_t *const target) {
UPPER_P(target) = UPPER_P(number1) - UPPER_P(number2) -
((LOWER_P(number1) - LOWER_P(number2)) > LOWER_P(number1));
LOWER_P(target) = LOWER_P(number1) - LOWER_P(number2);
}
void or128(const uint128_t *const number1,
const uint128_t *const number2,
uint128_t *const target) {
UPPER_P(target) = UPPER_P(number1) | UPPER_P(number2);
LOWER_P(target) = LOWER_P(number1) | LOWER_P(number2);
}
void mul128(const uint128_t *const number1,
const uint128_t *const number2,
uint128_t *const target) {
uint64_t top[4] = {UPPER_P(number1) >> 32,
UPPER_P(number1) & 0xffffffff,
LOWER_P(number1) >> 32,
LOWER_P(number1) & 0xffffffff};
uint64_t bottom[4] = {UPPER_P(number2) >> 32,
UPPER_P(number2) & 0xffffffff,
LOWER_P(number2) >> 32,
LOWER_P(number2) & 0xffffffff};
uint64_t products[4][4];
uint128_t tmp, tmp2;
for (int y = 3; y > -1; y--) {
for (int x = 3; x > -1; x--) {
products[3 - x][y] = top[x] * bottom[y];
}
}
uint64_t fourth32 = products[0][3] & 0xffffffff;
uint64_t third32 = (products[0][2] & 0xffffffff) + (products[0][3] >> 32);
uint64_t second32 = (products[0][1] & 0xffffffff) + (products[0][2] >> 32);
uint64_t first32 = (products[0][0] & 0xffffffff) + (products[0][1] >> 32);
third32 += products[1][3] & 0xffffffff;
second32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32);
first32 += (products[1][1] & 0xffffffff) + (products[1][2] >> 32);
second32 += products[2][3] & 0xffffffff;
first32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32);
first32 += products[3][3] & 0xffffffff;
UPPER(tmp) = first32 << 32;
LOWER(tmp) = 0;
UPPER(tmp2) = third32 >> 32;
LOWER(tmp2) = third32 << 32;
add128(&tmp, &tmp2, target);
UPPER(tmp) = second32;
LOWER(tmp) = 0;
add128(&tmp, target, &tmp2);
UPPER(tmp) = 0;
LOWER(tmp) = fourth32;
add128(&tmp, &tmp2, target);
}
void divmod128(const uint128_t *const l,
const uint128_t *const r,
uint128_t *const retDiv,
uint128_t *const retMod) {
uint128_t copyd, adder, resDiv, resMod;
uint128_t one;
UPPER(one) = 0;
LOWER(one) = 1;
uint32_t diffBits = bits128(l) - bits128(r);
clear128(&resDiv);
copy128(&resMod, l);
if (gt128(r, l)) {
copy128(retMod, l);
clear128(retDiv);
} else {
shiftl128(r, diffBits, &copyd);
shiftl128(&one, diffBits, &adder);
if (gt128(&copyd, &resMod)) {
shiftr128(&copyd, 1, &copyd);
shiftr128(&adder, 1, &adder);
}
while (gte128(&resMod, r)) {
if (gte128(&resMod, &copyd)) {
sub128(&resMod, &copyd, &resMod);
or128(&resDiv, &adder, &resDiv);
}
shiftr128(&copyd, 1, &copyd);
shiftr128(&adder, 1, &adder);
}
copy128(retDiv, &resDiv);
copy128(retMod, &resMod);
}
}
bool tostring128(const uint128_t *const number,
uint32_t baseParam,
char *const out,
uint32_t outLength) {
uint128_t rDiv;
uint128_t rMod;
uint128_t base;
copy128(&rDiv, number);
clear128(&rMod);
clear128(&base);
LOWER(base) = baseParam;
uint32_t offset = 0;
if ((baseParam < 2) || (baseParam > 16)) {
return false;
}
do {
if (offset > (outLength - 1)) {
return false;
}
divmod128(&rDiv, &base, &rDiv, &rMod);
out[offset++] = HEXDIGITS[(uint8_t) LOWER(rMod)];
} while (!zero128(&rDiv));
if (offset > (outLength - 1)) {
return false;
}
out[offset] = '\0';
reverseString(out, offset);
return true;
}
/**
* Format a uint128_t into a string as a signed integer
*
* @param[in] number the number to format
* @param[in] base the radix used in formatting
* @param[out] out the output buffer
* @param[in] out_length the length of the output buffer
* @return whether the formatting was successful or not
*/
bool tostring128_signed(const uint128_t *const number,
uint32_t base,
char *const out,
uint32_t out_length) {
uint128_t max_unsigned_val;
uint128_t max_signed_val;
uint128_t one_val;
uint128_t two_val;
uint128_t tmp;
// showing negative numbers only really makes sense in base 10
if (base == 10) {
explicit_bzero(&one_val, sizeof(one_val));
LOWER(one_val) = 1;
explicit_bzero(&two_val, sizeof(two_val));
LOWER(two_val) = 2;
memset(&max_unsigned_val, 0xFF, sizeof(max_unsigned_val));
divmod128(&max_unsigned_val, &two_val, &max_signed_val, &tmp);
if (gt128(number, &max_signed_val)) // negative value
{
sub128(&max_unsigned_val, number, &tmp);
add128(&tmp, &one_val, &tmp);
out[0] = '-';
return tostring128(&tmp, base, out + 1, out_length - 1);
}
}
return tostring128(number, base, out, out_length); // positive value
}

60
src_common/uint128.h Normal file
View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* Ledger Ethereum App
* (c) 2016-2019 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
// Adapted from https://github.com/calccrypto/uint256_t
#ifndef _UINT128_H_
#define _UINT128_H_
#include <stdint.h>
#include <stdbool.h>
typedef struct uint128_t {
uint64_t elements[2];
} uint128_t;
void readu128BE(const uint8_t *const buffer, uint128_t *const target);
bool zero128(const uint128_t *const number);
void copy128(uint128_t *const target, const uint128_t *const number);
void clear128(uint128_t *const target);
void shiftl128(const uint128_t *const number, uint32_t value, uint128_t *const target);
void shiftr128(const uint128_t *const number, uint32_t value, uint128_t *const target);
uint32_t bits128(const uint128_t *const number);
bool equal128(const uint128_t *const number1, const uint128_t *const number2);
bool gt128(const uint128_t *const number1, const uint128_t *const number2);
bool gte128(const uint128_t *const number1, const uint128_t *const number2);
void add128(const uint128_t *const number1,
const uint128_t *const number2,
uint128_t *const target);
void sub128(const uint128_t *const number1,
const uint128_t *const number2,
uint128_t *const target);
void or128(const uint128_t *const number1, const uint128_t *const number2, uint128_t *const target);
void mul128(const uint128_t *const number1,
const uint128_t *const number2,
uint128_t *const target);
void divmod128(const uint128_t *const l,
const uint128_t *const r,
uint128_t *const div,
uint128_t *const mod);
bool tostring128(const uint128_t *const number, uint32_t base, char *const out, uint32_t outLength);
bool tostring128_signed(const uint128_t *const number,
uint32_t base,
char *const out,
uint32_t out_length);
#endif // _UINT128_H_

View File

@@ -19,76 +19,31 @@
#include <stdio.h>
#include <string.h>
#include "uint256.h"
#include "uint_common.h"
#include "ethUstream.h" // INT256_LENGTH
#include "ethUtils.h" // HEXDIGITS
static const char HEXDIGITS[] = "0123456789abcdef";
static uint64_t readUint64BE(uint8_t *buffer) {
return (((uint64_t) buffer[0]) << 56) | (((uint64_t) buffer[1]) << 48) |
(((uint64_t) buffer[2]) << 40) | (((uint64_t) buffer[3]) << 32) |
(((uint64_t) buffer[4]) << 24) | (((uint64_t) buffer[5]) << 16) |
(((uint64_t) buffer[6]) << 8) | (((uint64_t) buffer[7]));
}
void readu128BE(uint8_t *buffer, uint128_t *target) {
UPPER_P(target) = readUint64BE(buffer);
LOWER_P(target) = readUint64BE(buffer + 8);
}
void readu256BE(uint8_t *buffer, uint256_t *target) {
void readu256BE(const uint8_t *const buffer, uint256_t *const target) {
readu128BE(buffer, &UPPER_P(target));
readu128BE(buffer + 16, &LOWER_P(target));
}
bool zero128(uint128_t *number) {
return ((LOWER_P(number) == 0) && (UPPER_P(number) == 0));
}
bool zero256(uint256_t *number) {
bool zero256(const uint256_t *const number) {
return (zero128(&LOWER_P(number)) && zero128(&UPPER_P(number)));
}
void copy128(uint128_t *target, uint128_t *number) {
UPPER_P(target) = UPPER_P(number);
LOWER_P(target) = LOWER_P(number);
}
void copy256(uint256_t *target, uint256_t *number) {
void copy256(uint256_t *const target, const uint256_t *const number) {
copy128(&UPPER_P(target), &UPPER_P(number));
copy128(&LOWER_P(target), &LOWER_P(number));
}
void clear128(uint128_t *target) {
UPPER_P(target) = 0;
LOWER_P(target) = 0;
}
void clear256(uint256_t *target) {
void clear256(uint256_t *const target) {
clear128(&UPPER_P(target));
clear128(&LOWER_P(target));
}
void shiftl128(uint128_t *number, uint32_t value, uint128_t *target) {
if (value >= 128) {
clear128(target);
} else if (value == 64) {
UPPER_P(target) = LOWER_P(number);
LOWER_P(target) = 0;
} else if (value == 0) {
copy128(target, number);
} else if (value < 64) {
UPPER_P(target) = (UPPER_P(number) << value) + (LOWER_P(number) >> (64 - value));
LOWER_P(target) = (LOWER_P(number) << value);
} else if ((128 > value) && (value > 64)) {
UPPER_P(target) = LOWER_P(number) << (value - 64);
LOWER_P(target) = 0;
} else {
clear128(target);
}
}
void shiftl256(uint256_t *number, uint32_t value, uint256_t *target) {
void shiftl256(const uint256_t *const number, uint32_t value, uint256_t *const target) {
if (value >= 256) {
clear256(target);
} else if (value == 128) {
@@ -113,28 +68,7 @@ void shiftl256(uint256_t *number, uint32_t value, uint256_t *target) {
}
}
void shiftr128(uint128_t *number, uint32_t value, uint128_t *target) {
if (value >= 128) {
clear128(target);
} else if (value == 64) {
UPPER_P(target) = 0;
LOWER_P(target) = UPPER_P(number);
} else if (value == 0) {
copy128(target, number);
} else if (value < 64) {
uint128_t result;
UPPER(result) = UPPER_P(number) >> value;
LOWER(result) = (UPPER_P(number) << (64 - value)) + (LOWER_P(number) >> value);
copy128(target, &result);
} else if ((128 > value) && (value > 64)) {
LOWER_P(target) = UPPER_P(number) >> (value - 64);
UPPER_P(target) = 0;
} else {
clear128(target);
}
}
void shiftr256(uint256_t *number, uint32_t value, uint256_t *target) {
void shiftr256(const uint256_t *const number, uint32_t value, uint256_t *const target) {
if (value >= 256) {
clear256(target);
} else if (value == 128) {
@@ -159,26 +93,7 @@ void shiftr256(uint256_t *number, uint32_t value, uint256_t *target) {
}
}
uint32_t bits128(uint128_t *number) {
uint32_t result = 0;
if (UPPER_P(number)) {
result = 64;
uint64_t up = UPPER_P(number);
while (up) {
up >>= 1;
result++;
}
} else {
uint64_t low = LOWER_P(number);
while (low) {
low >>= 1;
result++;
}
}
return result;
}
uint32_t bits256(uint256_t *number) {
uint32_t bits256(const uint256_t *const number) {
uint32_t result = 0;
if (!zero128(&UPPER_P(number))) {
result = 128;
@@ -199,44 +114,25 @@ uint32_t bits256(uint256_t *number) {
return result;
}
bool equal128(uint128_t *number1, uint128_t *number2) {
return (UPPER_P(number1) == UPPER_P(number2)) && (LOWER_P(number1) == LOWER_P(number2));
}
bool equal256(uint256_t *number1, uint256_t *number2) {
bool equal256(const uint256_t *const number1, const uint256_t *const number2) {
return (equal128(&UPPER_P(number1), &UPPER_P(number2)) &&
equal128(&LOWER_P(number1), &LOWER_P(number2)));
}
bool gt128(uint128_t *number1, uint128_t *number2) {
if (UPPER_P(number1) == UPPER_P(number2)) {
return (LOWER_P(number1) > LOWER_P(number2));
}
return (UPPER_P(number1) > UPPER_P(number2));
}
bool gt256(uint256_t *number1, uint256_t *number2) {
bool gt256(const uint256_t *const number1, const uint256_t *const number2) {
if (equal128(&UPPER_P(number1), &UPPER_P(number2))) {
return gt128(&LOWER_P(number1), &LOWER_P(number2));
}
return gt128(&UPPER_P(number1), &UPPER_P(number2));
}
bool gte128(uint128_t *number1, uint128_t *number2) {
return gt128(number1, number2) || equal128(number1, number2);
}
bool gte256(uint256_t *number1, uint256_t *number2) {
bool gte256(const uint256_t *const number1, const uint256_t *const number2) {
return gt256(number1, number2) || equal256(number1, number2);
}
void add128(uint128_t *number1, uint128_t *number2, uint128_t *target) {
UPPER_P(target) = UPPER_P(number1) + UPPER_P(number2) +
((LOWER_P(number1) + LOWER_P(number2)) < LOWER_P(number1));
LOWER_P(target) = LOWER_P(number1) + LOWER_P(number2);
}
void add256(uint256_t *number1, uint256_t *number2, uint256_t *target) {
void add256(const uint256_t *const number1,
const uint256_t *const number2,
uint256_t *const target) {
uint128_t tmp;
add128(&UPPER_P(number1), &UPPER_P(number2), &UPPER_P(target));
add128(&LOWER_P(number1), &LOWER_P(number2), &tmp);
@@ -249,104 +145,31 @@ void add256(uint256_t *number1, uint256_t *number2, uint256_t *target) {
add128(&LOWER_P(number1), &LOWER_P(number2), &LOWER_P(target));
}
void minus128(uint128_t *number1, uint128_t *number2, uint128_t *target) {
UPPER_P(target) = UPPER_P(number1) - UPPER_P(number2) -
((LOWER_P(number1) - LOWER_P(number2)) > LOWER_P(number1));
LOWER_P(target) = LOWER_P(number1) - LOWER_P(number2);
}
void minus256(uint256_t *number1, uint256_t *number2, uint256_t *target) {
void sub256(const uint256_t *const number1,
const uint256_t *const number2,
uint256_t *const target) {
uint128_t tmp;
minus128(&UPPER_P(number1), &UPPER_P(number2), &UPPER_P(target));
minus128(&LOWER_P(number1), &LOWER_P(number2), &tmp);
sub128(&UPPER_P(number1), &UPPER_P(number2), &UPPER_P(target));
sub128(&LOWER_P(number1), &LOWER_P(number2), &tmp);
if (gt128(&tmp, &LOWER_P(number1))) {
uint128_t one;
UPPER(one) = 0;
LOWER(one) = 1;
minus128(&UPPER_P(target), &one, &UPPER_P(target));
sub128(&UPPER_P(target), &one, &UPPER_P(target));
}
minus128(&LOWER_P(number1), &LOWER_P(number2), &LOWER_P(target));
sub128(&LOWER_P(number1), &LOWER_P(number2), &LOWER_P(target));
}
void or128(uint128_t *number1, uint128_t *number2, uint128_t *target) {
UPPER_P(target) = UPPER_P(number1) | UPPER_P(number2);
LOWER_P(target) = LOWER_P(number1) | LOWER_P(number2);
}
void or256(uint256_t *number1, uint256_t *number2, uint256_t *target) {
void or256(const uint256_t *const number1,
const uint256_t *const number2,
uint256_t *const target) {
or128(&UPPER_P(number1), &UPPER_P(number2), &UPPER_P(target));
or128(&LOWER_P(number1), &LOWER_P(number2), &LOWER_P(target));
}
void mul128(uint128_t *number1, uint128_t *number2, uint128_t *target) {
uint64_t top[4] = {UPPER_P(number1) >> 32,
UPPER_P(number1) & 0xffffffff,
LOWER_P(number1) >> 32,
LOWER_P(number1) & 0xffffffff};
uint64_t bottom[4] = {UPPER_P(number2) >> 32,
UPPER_P(number2) & 0xffffffff,
LOWER_P(number2) >> 32,
LOWER_P(number2) & 0xffffffff};
uint64_t products[4][4];
uint128_t tmp, tmp2;
for (int y = 3; y > -1; y--) {
for (int x = 3; x > -1; x--) {
products[3 - x][y] = top[x] * bottom[y];
}
}
uint64_t fourth32 = products[0][3] & 0xffffffff;
uint64_t third32 = (products[0][2] & 0xffffffff) + (products[0][3] >> 32);
uint64_t second32 = (products[0][1] & 0xffffffff) + (products[0][2] >> 32);
uint64_t first32 = (products[0][0] & 0xffffffff) + (products[0][1] >> 32);
third32 += products[1][3] & 0xffffffff;
second32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32);
first32 += (products[1][1] & 0xffffffff) + (products[1][2] >> 32);
second32 += products[2][3] & 0xffffffff;
first32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32);
first32 += products[3][3] & 0xffffffff;
UPPER(tmp) = first32 << 32;
LOWER(tmp) = 0;
UPPER(tmp2) = third32 >> 32;
LOWER(tmp2) = third32 << 32;
add128(&tmp, &tmp2, target);
UPPER(tmp) = second32;
LOWER(tmp) = 0;
add128(&tmp, target, &tmp2);
UPPER(tmp) = 0;
LOWER(tmp) = fourth32;
add128(&tmp, &tmp2, target);
}
void write_u64_be(uint8_t *buffer, uint64_t value) {
buffer[0] = ((value >> 56) & 0xff);
buffer[1] = ((value >> 48) & 0xff);
buffer[2] = ((value >> 40) & 0xff);
buffer[3] = ((value >> 32) & 0xff);
buffer[4] = ((value >> 24) & 0xff);
buffer[5] = ((value >> 16) & 0xff);
buffer[6] = ((value >> 8) & 0xff);
buffer[7] = (value & 0xff);
}
void read_u64_be(uint8_t *in, uint64_t *out) {
uint8_t *out_ptr = (uint8_t *) out;
*out_ptr++ = in[7];
*out_ptr++ = in[6];
*out_ptr++ = in[5];
*out_ptr++ = in[4];
*out_ptr++ = in[3];
*out_ptr++ = in[2];
*out_ptr++ = in[1];
*out_ptr = in[0];
}
void mul256(uint256_t *number1, uint256_t *number2, uint256_t *target) {
void mul256(const uint256_t *const number1,
const uint256_t *const number2,
uint256_t *const target) {
uint8_t num1[INT256_LENGTH], num2[INT256_LENGTH], result[INT256_LENGTH * 2];
memset(&result, 0, sizeof(result));
for (uint8_t i = 0; i < 4; i++) {
@@ -359,38 +182,10 @@ void mul256(uint256_t *number1, uint256_t *number2, uint256_t *target) {
}
}
void divmod128(uint128_t *l, uint128_t *r, uint128_t *retDiv, uint128_t *retMod) {
uint128_t copyd, adder, resDiv, resMod;
uint128_t one;
UPPER(one) = 0;
LOWER(one) = 1;
uint32_t diffBits = bits128(l) - bits128(r);
clear128(&resDiv);
copy128(&resMod, l);
if (gt128(r, l)) {
copy128(retMod, l);
clear128(retDiv);
} else {
shiftl128(r, diffBits, &copyd);
shiftl128(&one, diffBits, &adder);
if (gt128(&copyd, &resMod)) {
shiftr128(&copyd, 1, &copyd);
shiftr128(&adder, 1, &adder);
}
while (gte128(&resMod, r)) {
if (gte128(&resMod, &copyd)) {
minus128(&resMod, &copyd, &resMod);
or128(&resDiv, &adder, &resDiv);
}
shiftr128(&copyd, 1, &copyd);
shiftr128(&adder, 1, &adder);
}
copy128(retDiv, &resDiv);
copy128(retMod, &resMod);
}
}
void divmod256(uint256_t *l, uint256_t *r, uint256_t *retDiv, uint256_t *retMod) {
void divmod256(const uint256_t *const l,
const uint256_t *const r,
uint256_t *const retDiv,
uint256_t *const retMod) {
uint256_t copyd, adder, resDiv, resMod;
uint256_t one;
clear256(&one);
@@ -411,7 +206,7 @@ void divmod256(uint256_t *l, uint256_t *r, uint256_t *retDiv, uint256_t *retMod)
}
while (gte256(&resMod, r)) {
if (gte256(&resMod, &copyd)) {
minus256(&resMod, &copyd, &resMod);
sub256(&resMod, &copyd, &resMod);
or256(&resDiv, &adder, &resDiv);
}
shiftr256(&copyd, 1, &copyd);
@@ -422,41 +217,10 @@ void divmod256(uint256_t *l, uint256_t *r, uint256_t *retDiv, uint256_t *retMod)
}
}
static void reverseString(char *str, uint32_t length) {
uint32_t i, j;
for (i = 0, j = length - 1; i < j; i++, j--) {
uint8_t c;
c = str[i];
str[i] = str[j];
str[j] = c;
}
}
bool tostring128(uint128_t *number, uint32_t baseParam, char *out, uint32_t outLength) {
uint128_t rDiv;
uint128_t rMod;
uint128_t base;
copy128(&rDiv, number);
clear128(&rMod);
clear128(&base);
LOWER(base) = baseParam;
uint32_t offset = 0;
if ((baseParam < 2) || (baseParam > 16)) {
return false;
}
do {
if (offset > (outLength - 1)) {
return false;
}
divmod128(&rDiv, &base, &rDiv, &rMod);
out[offset++] = HEXDIGITS[(uint8_t) LOWER(rMod)];
} while (!zero128(&rDiv));
out[offset] = '\0';
reverseString(out, offset);
return true;
}
bool tostring256(uint256_t *number, uint32_t baseParam, char *out, uint32_t outLength) {
bool tostring256(const uint256_t *const number,
uint32_t baseParam,
char *const out,
uint32_t outLength) {
uint256_t rDiv;
uint256_t rMod;
uint256_t base;
@@ -485,3 +249,42 @@ bool tostring256(uint256_t *number, uint32_t baseParam, char *out, uint32_t outL
reverseString(out, offset);
return true;
}
/**
* Format a uint256_t into a string as a signed integer
*
* @param[in] number the number to format
* @param[in] base the radix used in formatting
* @param[out] out the output buffer
* @param[in] out_length the length of the output buffer
* @return whether the formatting was successful or not
*/
bool tostring256_signed(const uint256_t *const number,
uint32_t base,
char *const out,
uint32_t out_length) {
uint256_t max_unsigned_val;
uint256_t max_signed_val;
uint256_t one_val;
uint256_t two_val;
uint256_t tmp;
// showing negative numbers only really makes sense in base 10
if (base == 10) {
explicit_bzero(&one_val, sizeof(one_val));
LOWER(LOWER(one_val)) = 1;
explicit_bzero(&two_val, sizeof(two_val));
LOWER(LOWER(two_val)) = 2;
memset(&max_unsigned_val, 0xFF, sizeof(max_unsigned_val));
divmod256(&max_unsigned_val, &two_val, &max_signed_val, &tmp);
if (gt256(number, &max_signed_val)) // negative value
{
sub256(&max_unsigned_val, number, &tmp);
add256(&tmp, &one_val, &tmp);
out[0] = '-';
return tostring256(&tmp, base, out + 1, out_length - 1);
}
}
return tostring256(number, base, out, out_length); // positive value
}

View File

@@ -22,55 +22,40 @@
#include <stdint.h>
#include <stdbool.h>
#include "os.h"
#include "cx.h"
#include "ethUstream.h"
typedef struct uint128_t {
uint64_t elements[2];
} uint128_t;
#include "uint128.h"
typedef struct uint256_t {
uint128_t elements[2];
} uint256_t;
#define UPPER_P(x) x->elements[0]
#define LOWER_P(x) x->elements[1]
#define UPPER(x) x.elements[0]
#define LOWER(x) x.elements[1]
void readu128BE(uint8_t *buffer, uint128_t *target);
void readu256BE(uint8_t *buffer, uint256_t *target);
void write_u64_be(uint8_t *buffer, uint64_t value);
bool zero128(uint128_t *number);
bool zero256(uint256_t *number);
void copy128(uint128_t *target, uint128_t *number);
void copy256(uint256_t *target, uint256_t *number);
void clear128(uint128_t *target);
void clear256(uint256_t *target);
void shiftl128(uint128_t *number, uint32_t value, uint128_t *target);
void shiftr128(uint128_t *number, uint32_t value, uint128_t *target);
void shiftl256(uint256_t *number, uint32_t value, uint256_t *target);
void shiftr256(uint256_t *number, uint32_t value, uint256_t *target);
uint32_t bits128(uint128_t *number);
uint32_t bits256(uint256_t *number);
bool equal128(uint128_t *number1, uint128_t *number2);
bool equal256(uint256_t *number1, uint256_t *number2);
bool gt128(uint128_t *number1, uint128_t *number2);
bool gt256(uint256_t *number1, uint256_t *number2);
bool gte128(uint128_t *number1, uint128_t *number2);
bool gte256(uint256_t *number1, uint256_t *number2);
void add128(uint128_t *number1, uint128_t *number2, uint128_t *target);
void add256(uint256_t *number1, uint256_t *number2, uint256_t *target);
void minus128(uint128_t *number1, uint128_t *number2, uint128_t *target);
void minus256(uint256_t *number1, uint256_t *number2, uint256_t *target);
void or128(uint128_t *number1, uint128_t *number2, uint128_t *target);
void or256(uint256_t *number1, uint256_t *number2, uint256_t *target);
void mul128(uint128_t *number1, uint128_t *number2, uint128_t *target);
void mul256(uint256_t *number1, uint256_t *number2, uint256_t *target);
void divmod128(uint128_t *l, uint128_t *r, uint128_t *div, uint128_t *mod);
void divmod256(uint256_t *l, uint256_t *r, uint256_t *div, uint256_t *mod);
bool tostring128(uint128_t *number, uint32_t base, char *out, uint32_t outLength);
bool tostring256(uint256_t *number, uint32_t base, char *out, uint32_t outLength);
void readu256BE(const uint8_t *const buffer, uint256_t *const target);
bool zero256(const uint256_t *const number);
void copy256(uint256_t *const target, const uint256_t *const number);
void clear256(uint256_t *const target);
void shiftl256(const uint256_t *const number, uint32_t value, uint256_t *const target);
void shiftr256(const uint256_t *const number, uint32_t value, uint256_t *const target);
uint32_t bits256(const uint256_t *const number);
bool equal256(const uint256_t *const number1, const uint256_t *const number2);
bool gt256(const uint256_t *const number1, const uint256_t *const number2);
bool gte256(const uint256_t *const number1, const uint256_t *const number2);
void add256(const uint256_t *const number1,
const uint256_t *const number2,
uint256_t *const target);
void sub256(const uint256_t *const number1,
const uint256_t *const number2,
uint256_t *const target);
void or256(const uint256_t *const number1, const uint256_t *const number2, uint256_t *const target);
void mul256(const uint256_t *const number1,
const uint256_t *const number2,
uint256_t *const target);
void divmod256(const uint256_t *const l,
const uint256_t *const r,
uint256_t *const div,
uint256_t *const mod);
bool tostring256(const uint256_t *const number, uint32_t base, char *const out, uint32_t outLength);
bool tostring256_signed(const uint256_t *const number,
uint32_t base,
char *const out,
uint32_t out_length);
#endif // _UINT256_H_

60
src_common/uint_common.c Normal file
View File

@@ -0,0 +1,60 @@
/*******************************************************************************
* Ledger Ethereum App
* (c) 2016-2019 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
// Adapted from https://github.com/calccrypto/uint256_t
#include "uint_common.h"
void write_u64_be(uint8_t *const buffer, uint64_t value) {
buffer[0] = ((value >> 56) & 0xff);
buffer[1] = ((value >> 48) & 0xff);
buffer[2] = ((value >> 40) & 0xff);
buffer[3] = ((value >> 32) & 0xff);
buffer[4] = ((value >> 24) & 0xff);
buffer[5] = ((value >> 16) & 0xff);
buffer[6] = ((value >> 8) & 0xff);
buffer[7] = (value & 0xff);
}
void read_u64_be(const uint8_t *const in, uint64_t *const out) {
uint8_t *out_ptr = (uint8_t *) out;
*out_ptr++ = in[7];
*out_ptr++ = in[6];
*out_ptr++ = in[5];
*out_ptr++ = in[4];
*out_ptr++ = in[3];
*out_ptr++ = in[2];
*out_ptr++ = in[1];
*out_ptr = in[0];
}
uint64_t readUint64BE(const uint8_t *const buffer) {
return (((uint64_t) buffer[0]) << 56) | (((uint64_t) buffer[1]) << 48) |
(((uint64_t) buffer[2]) << 40) | (((uint64_t) buffer[3]) << 32) |
(((uint64_t) buffer[4]) << 24) | (((uint64_t) buffer[5]) << 16) |
(((uint64_t) buffer[6]) << 8) | (((uint64_t) buffer[7]));
}
void reverseString(char *const str, uint32_t length) {
uint32_t i, j;
for (i = 0, j = length - 1; i < j; i++, j--) {
char c;
c = str[i];
str[i] = str[j];
str[j] = c;
}
}

35
src_common/uint_common.h Normal file
View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Ledger Ethereum App
* (c) 2016-2019 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
// Adapted from https://github.com/calccrypto/uint256_t
#ifndef _UINT_COMMON_H_
#define _UINT_COMMON_H_
#include <stdint.h>
#define UPPER_P(x) x->elements[0]
#define LOWER_P(x) x->elements[1]
#define UPPER(x) x.elements[0]
#define LOWER(x) x.elements[1]
void write_u64_be(uint8_t *const buffer, uint64_t value);
void read_u64_be(const uint8_t *const in, uint64_t *const out);
uint64_t readUint64BE(const uint8_t *const buffer);
void reverseString(char *const str, uint32_t length);
#endif //_UINT_COMMON_H_

View File

@@ -4,7 +4,7 @@
void handleGetAppConfiguration(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(p1);

View File

@@ -44,7 +44,7 @@ void getEth2PublicKey(uint32_t *bip32Path, uint8_t bip32PathLength, uint8_t *out
void handleGetEth2PublicKey(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
bip32_path_t bip32;

View File

@@ -9,7 +9,7 @@
void handleGetPublicKey(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
uint8_t privateKeyData[INT256_LENGTH];

View File

@@ -26,7 +26,7 @@ void decodeScalar(const uint8_t *scalarIn, uint8_t *scalarOut) {
void handlePerformPrivacyOperation(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
uint8_t privateKeyData[INT256_LENGTH];

View File

@@ -8,8 +8,8 @@
void handleProvideErc20TokenInformation(uint8_t p1,
uint8_t p2,
uint8_t *workBuffer,
uint16_t dataLength,
const uint8_t *workBuffer,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(p1);
@@ -103,12 +103,13 @@ void handleProvideErc20TokenInformation(uint8_t p1,
void handleProvideErc20TokenInformation(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
__attribute__((unused)) unsigned int *tx) {
unsigned int *tx) {
UNUSED(p1);
UNUSED(p2);
UNUSED(flags);
UNUSED(tx);
uint32_t offset = 0;
uint8_t tickerLength;
uint32_t chainId;

View File

@@ -55,7 +55,7 @@ typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
void handleProvideNFTInformation(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(p1);

View File

@@ -9,7 +9,7 @@
void handleSetExternalPlugin(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(p1);
@@ -91,4 +91,4 @@ void handleSetExternalPlugin(uint8_t p1,
G_io_apdu_buffer[(*tx)++] = 0x90;
G_io_apdu_buffer[(*tx)++] = 0x00;
}
}

View File

@@ -88,7 +88,7 @@ static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) {
void handleSetPlugin(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(p1);

View File

@@ -88,7 +88,7 @@ static void reset_ui_buffer(void) {
* @param[in] length the payload size
* @return pointer to the start of the start of the message; \ref NULL if it failed
*/
static const uint8_t *first_apdu_data(const uint8_t *data, uint16_t *length) {
static const uint8_t *first_apdu_data(const uint8_t *data, uint8_t *length) {
if (appState != APP_STATE_IDLE) {
reset_app_context();
}
@@ -212,12 +212,11 @@ bool handleSignPersonalMessage(uint8_t p1,
const uint8_t *const payload,
uint8_t length) {
const uint8_t *data = payload;
uint16_t u16_length = length;
(void) p2;
processed_size = 0;
if (p1 == P1_FIRST) {
if ((data = first_apdu_data(data, &u16_length)) == NULL) {
if ((data = first_apdu_data(data, &length)) == NULL) {
return false;
}
processed_size = data - payload;
@@ -227,7 +226,7 @@ bool handleSignPersonalMessage(uint8_t p1,
return false;
}
if (!feed_hash(data, u16_length)) {
if (!feed_hash(data, length)) {
return false;
}

View File

@@ -1,36 +0,0 @@
#include "shared_context.h"
#include "apdu_constants.h"
#include "utils.h"
#include "common_ui.h"
void handleSignEIP712Message(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(tx);
if ((p1 != 00) || (p2 != 00)) {
THROW(0x6B00);
}
if (appState != APP_STATE_IDLE) {
reset_app_context();
}
workBuffer = parseBip32(workBuffer, &dataLength, &tmpCtx.messageSigningContext.bip32);
if (workBuffer == NULL || dataLength < 32 + 32) {
THROW(0x6a80);
}
memmove(tmpCtx.messageSigningContext712.domainHash, workBuffer, 32);
memmove(tmpCtx.messageSigningContext712.messageHash, workBuffer + 32, 32);
#ifdef NO_CONSENT
io_seproxyhal_touch_signMessage_ok();
#else // NO_CONSENT
ui_sign_712_v0();
#endif // NO_CONSENT
*flags |= IO_ASYNCH_REPLY;
}

View File

@@ -0,0 +1,216 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "commands_712.h"
#include "apdu_constants.h" // APDU response codes
#include "context_712.h"
#include "field_hash.h"
#include "path.h"
#include "ui_logic.h"
#include "typed_data.h"
#include "schema_hash.h"
#include "filtering.h"
#include "common_712.h"
#include "ethUtils.h" // allzeroes
/**
* Send the response to the previous APDU command
*
* In case of an error it uses the global variable to retrieve the error code and resets
* the app context
*
* @param[in] success whether the command was successful
*/
void handle_eip712_return_code(bool success) {
if (success) {
apdu_response_code = APDU_RESPONSE_OK;
} else if (apdu_response_code == APDU_RESPONSE_OK) { // somehow not set
apdu_response_code = APDU_RESPONSE_ERROR_NO_INFO;
}
G_io_apdu_buffer[0] = (apdu_response_code >> 8) & 0xff;
G_io_apdu_buffer[1] = apdu_response_code & 0xff;
// Send back the response, do not restart the event loop
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
if (!success) {
eip712_context_deinit();
}
}
/**
* Process the EIP712 struct definition command
*
* @param[in] apdu_buf the APDU payload
* @return whether the command was successful or not
*/
bool handle_eip712_struct_def(const uint8_t *const apdu_buf) {
bool ret = true;
if (eip712_context == NULL) {
ret = eip712_context_init();
}
if (struct_state == DEFINED) {
ret = false;
}
if (ret) {
switch (apdu_buf[OFFSET_P2]) {
case P2_DEF_NAME:
ret = set_struct_name(apdu_buf[OFFSET_LC], &apdu_buf[OFFSET_CDATA]);
break;
case P2_DEF_FIELD:
ret = set_struct_field(apdu_buf[OFFSET_LC], &apdu_buf[OFFSET_CDATA]);
break;
default:
PRINTF("Unknown P2 0x%x for APDU 0x%x\n",
apdu_buf[OFFSET_P2],
apdu_buf[OFFSET_INS]);
apdu_response_code = APDU_RESPONSE_INVALID_P1_P2;
ret = false;
}
}
handle_eip712_return_code(ret);
return ret;
}
/**
* Process the EIP712 struct implementation command
*
* @param[in] apdu_buf the APDU payload
* @return whether the command was successful or not
*/
bool handle_eip712_struct_impl(const uint8_t *const apdu_buf) {
bool ret = false;
bool reply_apdu = true;
if (eip712_context == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
} else {
switch (apdu_buf[OFFSET_P2]) {
case P2_IMPL_NAME:
// set root type
ret = path_set_root((char *) &apdu_buf[OFFSET_CDATA], apdu_buf[OFFSET_LC]);
if (ret) {
if (N_storage.verbose_eip712) {
ui_712_review_struct(path_get_root());
reply_apdu = false;
}
ui_712_field_flags_reset();
}
break;
case P2_IMPL_FIELD:
if ((ret = field_hash(&apdu_buf[OFFSET_CDATA],
apdu_buf[OFFSET_LC],
apdu_buf[OFFSET_P1] != P1_COMPLETE))) {
reply_apdu = false;
}
break;
case P2_IMPL_ARRAY:
ret = path_new_array_depth(&apdu_buf[OFFSET_CDATA], apdu_buf[OFFSET_LC]);
break;
default:
PRINTF("Unknown P2 0x%x for APDU 0x%x\n",
apdu_buf[OFFSET_P2],
apdu_buf[OFFSET_INS]);
apdu_response_code = APDU_RESPONSE_INVALID_P1_P2;
}
}
if (reply_apdu) {
handle_eip712_return_code(ret);
}
return ret;
}
/**
* Process the EIP712 filtering command
*
* @param[in] apdu_buf the APDU payload
* @return whether the command was successful or not
*/
bool handle_eip712_filtering(const uint8_t *const apdu_buf) {
bool ret = true;
bool reply_apdu = true;
e_filtering_type type;
if (eip712_context == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
ret = false;
} else {
switch (apdu_buf[OFFSET_P2]) {
case P2_FILT_ACTIVATE:
if (!N_storage.verbose_eip712) {
ui_712_set_filtering_mode(EIP712_FILTERING_FULL);
ret = compute_schema_hash();
}
break;
case P2_FILT_MESSAGE_INFO:
case P2_FILT_SHOW_FIELD:
type = (apdu_buf[OFFSET_P2] == P2_FILT_MESSAGE_INFO)
? FILTERING_PROVIDE_MESSAGE_INFO
: FILTERING_SHOW_FIELD;
if (ui_712_get_filtering_mode() == EIP712_FILTERING_FULL) {
ret =
provide_filtering_info(&apdu_buf[OFFSET_CDATA], apdu_buf[OFFSET_LC], type);
if ((apdu_buf[OFFSET_P2] == P2_FILT_MESSAGE_INFO) && ret) {
reply_apdu = false;
}
}
break;
default:
PRINTF("Unknown P2 0x%x for APDU 0x%x\n",
apdu_buf[OFFSET_P2],
apdu_buf[OFFSET_INS]);
apdu_response_code = APDU_RESPONSE_INVALID_P1_P2;
ret = false;
}
}
if (reply_apdu) {
handle_eip712_return_code(ret);
}
return ret;
}
/**
* Process the EIP712 sign command
*
* @param[in] apdu_buf the APDU payload
* @return whether the command was successful or not
*/
bool handle_eip712_sign(const uint8_t *const apdu_buf) {
bool ret = false;
uint8_t length = apdu_buf[OFFSET_LC];
if (eip712_context == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
}
// if the final hashes are still zero or if there are some unimplemented fields
else if (allzeroes(tmpCtx.messageSigningContext712.domainHash,
sizeof(tmpCtx.messageSigningContext712.domainHash)) ||
allzeroes(tmpCtx.messageSigningContext712.messageHash,
sizeof(tmpCtx.messageSigningContext712.messageHash)) ||
(path_get_field() != NULL)) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
} else if ((ui_712_get_filtering_mode() == EIP712_FILTERING_FULL) &&
(ui_712_remaining_filters() != 0)) {
PRINTF("%d EIP712 filters are missing\n", ui_712_remaining_filters());
apdu_response_code = APDU_RESPONSE_REF_DATA_NOT_FOUND;
} else if (parseBip32(&apdu_buf[OFFSET_CDATA], &length, &tmpCtx.messageSigningContext.bip32) !=
NULL) {
if (!N_storage.verbose_eip712 && (ui_712_get_filtering_mode() == EIP712_FILTERING_BASIC)) {
ui_712_message_hash();
}
ret = true;
ui_712_end_sign();
}
if (!ret) {
handle_eip712_return_code(ret);
}
return ret;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,33 @@
#ifndef EIP712_H_
#define EIP712_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdbool.h>
#include <stdint.h>
// APDUs P1
#define P1_COMPLETE 0x00
#define P1_PARTIAL 0xFF
// APDUs P2
#define P2_DEF_NAME 0x00
#define P2_DEF_FIELD 0xFF
#define P2_IMPL_NAME P2_DEF_NAME
#define P2_IMPL_ARRAY 0x0F
#define P2_IMPL_FIELD P2_DEF_FIELD
#define P2_FILT_ACTIVATE 0x00
#define P2_FILT_MESSAGE_INFO 0x0F
#define P2_FILT_SHOW_FIELD 0xFF
#define DOMAIN_STRUCT_NAME "EIP712Domain"
bool handle_eip712_struct_def(const uint8_t *const apdu_buf);
bool handle_eip712_struct_impl(const uint8_t *const apdu_buf);
bool handle_eip712_sign(const uint8_t *const apdu_buf);
bool handle_eip712_filtering(const uint8_t *const apdu_buf);
void handle_eip712_return_code(bool success);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // EIP712_H_

View File

@@ -0,0 +1,78 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <string.h>
#include <stdint.h>
#include "context_712.h"
#include "mem.h"
#include "mem_utils.h"
#include "sol_typenames.h"
#include "path.h"
#include "field_hash.h"
#include "ui_logic.h"
#include "typed_data.h"
#include "apdu_constants.h" // APDU response codes
#include "shared_context.h" // reset_app_context
#include "common_ui.h" // ui_idle
e_struct_init struct_state = NOT_INITIALIZED;
s_eip712_context *eip712_context = NULL;
/**
* Initialize the EIP712 context
*
* @return a boolean indicating if the initialization was successful or not
*/
bool eip712_context_init(void) {
// init global variables
mem_init();
if ((eip712_context = MEM_ALLOC_AND_ALIGN_TYPE(*eip712_context)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
if (sol_typenames_init() == false) {
return false;
}
if (path_init() == false) {
return false;
}
if (field_hash_init() == false) {
return false;
}
if (ui_712_init() == false) {
return false;
}
if (typed_data_init() == false) // this needs to be initialized last !
{
return false;
}
// Since they are optional, they might not be provided by the JSON data
explicit_bzero(eip712_context->contract_addr, sizeof(eip712_context->contract_addr));
eip712_context->chain_id = 0;
struct_state = NOT_INITIALIZED;
return true;
}
/**
* De-initialize the EIP712 context
*/
void eip712_context_deinit(void) {
typed_data_deinit();
path_deinit();
field_hash_deinit();
ui_712_deinit();
mem_reset();
eip712_context = NULL;
reset_app_context();
ui_idle();
}
#endif

View File

@@ -0,0 +1,25 @@
#ifndef EIP712_CTX_H_
#define EIP712_CTX_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdbool.h>
#include "ethUstream.h" // ADDRESS_LENGTH
typedef struct {
uint8_t contract_addr[ADDRESS_LENGTH];
uint64_t chain_id;
uint8_t schema_hash[224 / 8];
} s_eip712_context;
extern s_eip712_context *eip712_context;
bool eip712_context_init(void);
void eip712_context_deinit(void);
typedef enum { NOT_INITIALIZED, INITIALIZED, DEFINED } e_struct_init;
extern e_struct_init struct_state;
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // EIP712_CTX_H_

View File

@@ -0,0 +1,136 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdlib.h>
#include <string.h>
#include "encode_field.h"
#include "mem.h"
#include "shared_context.h"
#include "apdu_constants.h" // APDU response codes
typedef enum { MSB, LSB } e_padding_type;
/**
* Encode a field value to 32 bytes (padded)
*
* @param[in] value field value to encode
* @param[in] length field length before encoding
* @param[in] ptype padding direction (LSB vs MSB)
* @param[in] pval value used for padding
* @return encoded field value
*/
static void *field_encode(const uint8_t *const value,
uint8_t length,
e_padding_type ptype,
uint8_t pval) {
uint8_t *padded_value;
uint8_t start_idx;
if (length > EIP_712_ENCODED_FIELD_LENGTH) // sanity check
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return NULL;
}
if ((padded_value = mem_alloc(EIP_712_ENCODED_FIELD_LENGTH)) != NULL) {
switch (ptype) {
case MSB:
memset(padded_value, pval, EIP_712_ENCODED_FIELD_LENGTH - length);
start_idx = EIP_712_ENCODED_FIELD_LENGTH - length;
break;
case LSB:
explicit_bzero(padded_value + length, EIP_712_ENCODED_FIELD_LENGTH - length);
start_idx = 0;
break;
default:
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL; // should not be here
}
memcpy(&padded_value[start_idx], value, length);
} else {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
}
return padded_value;
}
/**
* Encode an unsigned integer
*
* @param[in] value pointer to the "packed" integer received
* @param[in] length its byte-length
* @return the encoded value
*/
void *encode_uint(const uint8_t *const value, uint8_t length) {
// no length check here since it will be checked by field_encode
return field_encode(value, length, MSB, 0x00);
}
/**
* Encode a signed integer
*
* @param[in] value pointer to the "packed" integer received
* @param[in] length its byte-length
* @param[in] typesize the type size in bytes
* @return the encoded value
*/
void *encode_int(const uint8_t *const value, uint8_t length, uint8_t typesize) {
uint8_t padding_value;
if (length < 1) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return NULL;
}
if ((length == typesize) && (value[0] & (1 << 7))) // negative number
{
padding_value = 0xFF;
} else {
padding_value = 0x00;
}
// no length check here since it will be checked by field_encode
return field_encode(value, length, MSB, padding_value);
}
/**
* Encode a fixed-size byte array
*
* @param[in] value pointer to the "packed" bytes array
* @param[in] length its byte-length
* @return the encoded value
*/
void *encode_bytes(const uint8_t *const value, uint8_t length) {
// no length check here since it will be checked by field_encode
return field_encode(value, length, LSB, 0x00);
}
/**
* Encode a boolean
*
* @param[in] value pointer to the boolean received
* @param[in] length its byte-length
* @return the encoded value
*/
void *encode_boolean(const bool *const value, uint8_t length) {
if (length != 1) // sanity check
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return NULL;
}
return encode_uint((uint8_t *) value, length);
}
/**
* Encode an address
*
* @param[in] value pointer to the address received
* @param[in] length its byte-length
* @return the encoded value
*/
void *encode_address(const uint8_t *const value, uint8_t length) {
if (length != ADDRESS_LENGTH) // sanity check
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return NULL;
}
return encode_uint(value, length);
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,19 @@
#ifndef ENCODE_FIELD_H_
#define ENCODE_FIELD_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include <stdbool.h>
#define EIP_712_ENCODED_FIELD_LENGTH 32
void *encode_uint(const uint8_t *const value, uint8_t length);
void *encode_int(const uint8_t *const value, uint8_t length, uint8_t typesize);
void *encode_boolean(const bool *const value, uint8_t length);
void *encode_address(const uint8_t *const value, uint8_t length);
void *encode_bytes(const uint8_t *const value, uint8_t length);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // ENCODE_FIELD_H_

View File

@@ -0,0 +1,284 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdlib.h>
#include "field_hash.h"
#include "encode_field.h"
#include "path.h"
#include "mem.h"
#include "mem_utils.h"
#include "shared_context.h"
#include "ui_logic.h"
#include "ethUtils.h" // KECCAK256_HASH_BYTESIZE
#include "context_712.h" // contract_addr
#include "utils.h" // u64_from_BE
#include "apdu_constants.h" // APDU response codes
#include "typed_data.h"
#include "commands_712.h"
#include "hash_bytes.h"
static s_field_hashing *fh = NULL;
/**
* Initialize the field hash context
*
* @return whether the initialization was successful or not
*/
bool field_hash_init(void) {
if (fh == NULL) {
if ((fh = MEM_ALLOC_AND_ALIGN_TYPE(*fh)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
fh->state = FHS_IDLE;
}
return true;
}
/**
* Deinitialize the field hash context
*/
void field_hash_deinit(void) {
fh = NULL;
}
/**
* Special handling of the first chunk received from a field value
*
* @param[in] field_ptr pointer to the struct field definition
* @param[in] data the field value
* @param[in,out] data_length the value length
* @return the data pointer
*/
static const uint8_t *field_hash_prepare(const void *const field_ptr,
const uint8_t *data,
uint8_t *data_length) {
e_type field_type;
field_type = struct_field_type(field_ptr);
fh->remaining_size = __builtin_bswap16(*(uint16_t *) &data[0]); // network byte order
data += sizeof(uint16_t);
*data_length -= sizeof(uint16_t);
fh->state = FHS_WAITING_FOR_MORE;
if (IS_DYN(field_type)) {
cx_keccak_init(&global_sha3, 256); // init hash
ui_712_new_field(field_ptr, data, *data_length);
}
return data;
}
/**
* Finalize static field hash
*
* Encode the field data depending on its type
*
* @param[in] field_ptr pointer to the struct field definition
* @param[in] data the field value
* @param[in] data_length the value length
* @return pointer to the encoded value
*/
static const uint8_t *field_hash_finalize_static(const void *const field_ptr,
const uint8_t *const data,
uint8_t data_length) {
uint8_t *value = NULL;
e_type field_type;
field_type = struct_field_type(field_ptr);
switch (field_type) {
case TYPE_SOL_INT:
value = encode_int(data, data_length, get_struct_field_typesize(field_ptr));
break;
case TYPE_SOL_UINT:
value = encode_uint(data, data_length);
break;
case TYPE_SOL_BYTES_FIX:
value = encode_bytes(data, data_length);
break;
case TYPE_SOL_ADDRESS:
value = encode_address(data, data_length);
break;
case TYPE_SOL_BOOL:
value = encode_boolean((bool *) data, data_length);
break;
case TYPE_CUSTOM:
default:
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
PRINTF("Unknown solidity type!\n");
}
if (value == NULL) {
return NULL;
}
ui_712_new_field(field_ptr, data, data_length);
return value;
}
/**
* Finalize dynamic field hash
*
* Allocate and hash the data
*
* @return pointer to the hash, \ref NULL if it failed
*/
static uint8_t *field_hash_finalize_dynamic(void) {
uint8_t *value;
if ((value = mem_alloc(KECCAK256_HASH_BYTESIZE)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return NULL;
}
// copy hash into memory
cx_hash((cx_hash_t *) &global_sha3, CX_LAST, NULL, 0, value, KECCAK256_HASH_BYTESIZE);
return value;
}
/**
* Feed the newly created field hash into the parent struct's progressive hash
*
* @param[in] field_type the struct field's type
* @param[in] hash the field hash
*/
static void field_hash_feed_parent(e_type field_type, const uint8_t *const hash) {
uint8_t len;
if (IS_DYN(field_type)) {
len = KECCAK256_HASH_BYTESIZE;
} else {
len = EIP_712_ENCODED_FIELD_LENGTH;
}
// last thing in mem is the hash of the previous field
// and just before it is the current hash context
cx_sha3_t *hash_ctx = (cx_sha3_t *) (hash - sizeof(cx_sha3_t));
// continue the progressive hash on it
hash_nbytes(hash, len, (cx_hash_t *) hash_ctx);
// deallocate it
mem_dealloc(len);
}
/**
* Special domain fields handling
*
* Do something special for certain EIP712Domain fields
*
* @param[in] field_ptr pointer to the struct field definition
* @param[in] data the field value
* @param[in] data_length the value length
* @return whether an error occured or not
*/
static bool field_hash_domain_special_fields(const void *const field_ptr,
const uint8_t *const data,
uint8_t data_length) {
const char *key;
uint8_t keylen;
key = get_struct_field_keyname(field_ptr, &keylen);
// copy contract address into context
if (strncmp(key, "verifyingContract", keylen) == 0) {
if (data_length != sizeof(eip712_context->contract_addr)) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
PRINTF("Unexpected verifyingContract length!\n");
return false;
}
memcpy(eip712_context->contract_addr, data, data_length);
} else if (strncmp(key, "chainId", keylen) == 0) {
eip712_context->chain_id = u64_from_BE(data, data_length);
}
return true;
}
/**
* Finalize the data hashing
*
* @param[in] field_ptr pointer to the struct field definition
* @param[in] data the field value
* @param[in] data_length the value length
* @return whether an error occured or not
*/
static bool field_hash_finalize(const void *const field_ptr,
const uint8_t *const data,
uint8_t data_length) {
const uint8_t *value = NULL;
e_type field_type;
field_type = struct_field_type(field_ptr);
if (!IS_DYN(field_type)) {
if ((value = field_hash_finalize_static(field_ptr, data, data_length)) == NULL) {
return false;
}
} else {
if ((value = field_hash_finalize_dynamic()) == NULL) {
return false;
}
}
field_hash_feed_parent(field_type, value);
if (path_get_root_type() == ROOT_DOMAIN) {
if (field_hash_domain_special_fields(field_ptr, data, data_length) == false) {
return false;
}
}
path_advance();
fh->state = FHS_IDLE;
ui_712_finalize_field();
return true;
}
/**
* Hash a field value
*
* @param[in] data the field value
* @param[in] data_length the value length
* @param[in] partial whether there is more of that data coming later or not
* @return whether the data hashing was successful or not
*/
bool field_hash(const uint8_t *data, uint8_t data_length, bool partial) {
const void *field_ptr;
e_type field_type;
if ((fh == NULL) || ((field_ptr = path_get_field()) == NULL)) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
field_type = struct_field_type(field_ptr);
if (fh->state == FHS_IDLE) // first packet for this frame
{
if (data_length < 2) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
data = field_hash_prepare(field_ptr, data, &data_length);
}
if (data_length > fh->remaining_size) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
fh->remaining_size -= data_length;
// if a dynamic type -> continue progressive hash
if (IS_DYN(field_type)) {
hash_nbytes(data, data_length, (cx_hash_t *) &global_sha3);
}
if (fh->remaining_size == 0) {
if (partial) // only makes sense if marked as complete
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if (field_hash_finalize(field_ptr, data, data_length) == false) {
return false;
}
} else {
if (!partial || !IS_DYN(field_type)) // only makes sense if marked as partial
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
handle_eip712_return_code(true);
}
return true;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,24 @@
#ifndef FIELD_HASH_H_
#define FIELD_HASH_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include <stdbool.h>
#define IS_DYN(type) (((type) == TYPE_SOL_STRING) || ((type) == TYPE_SOL_BYTES_DYN))
typedef enum { FHS_IDLE, FHS_WAITING_FOR_MORE } e_field_hashing_state;
typedef struct {
uint16_t remaining_size;
uint8_t state; // e_field_hashing_state
} s_field_hashing;
bool field_hash_init(void);
void field_hash_deinit(void);
bool field_hash(const uint8_t *data, uint8_t data_length, bool partial);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // FIELD_HASH_H_

View File

@@ -0,0 +1,185 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include "filtering.h"
#include "hash_bytes.h"
#include "ethUstream.h" // INT256_LENGTH
#include "apdu_constants.h" // APDU return codes
#include "context_712.h"
#include "commands_712.h"
#include "typed_data.h"
#include "path.h"
#include "ui_logic.h"
/**
* Reconstruct the field path and hash it
*
* @param[in] hash_ctx the hashing context
*/
static void hash_filtering_path(cx_hash_t *const hash_ctx) {
const void *field_ptr;
const char *key;
uint8_t key_len;
for (uint8_t i = 0; i < path_get_depth_count(); ++i) {
if (i > 0) {
hash_byte('.', hash_ctx);
}
if ((field_ptr = path_get_nth_field(i + 1)) != NULL) {
if ((key = get_struct_field_keyname(field_ptr, &key_len)) != NULL) {
// field name
hash_nbytes((uint8_t *) key, key_len, hash_ctx);
// array levels
if (struct_field_is_array(field_ptr)) {
uint8_t lvl_count;
get_struct_field_array_lvls_array(field_ptr, &lvl_count);
for (int j = 0; j < lvl_count; ++j) {
hash_nbytes((uint8_t *) ".[]", 3, hash_ctx);
}
}
}
}
}
}
/**
* Verify the provided signature
*
* @param[in] dname_length length of provided substitution name
* @param[in] dname provided substitution name
* @param[in] sig_length provided signature length
* @param[in] sig pointer to the provided signature
* @param[in] type the type of filtering
* @return whether the signature verification worked or not
*/
static bool verify_filtering_signature(uint8_t dname_length,
const char *const dname,
uint8_t sig_length,
const uint8_t *const sig,
e_filtering_type type) {
uint8_t hash[INT256_LENGTH];
cx_ecfp_public_key_t verifying_key;
cx_sha256_t hash_ctx;
uint64_t chain_id;
cx_sha256_init(&hash_ctx);
// Magic number, makes it so a signature of one type can't be used as another
switch (type) {
case FILTERING_SHOW_FIELD:
hash_byte(FILTERING_MAGIC_STRUCT_FIELD, (cx_hash_t *) &hash_ctx);
break;
case FILTERING_PROVIDE_MESSAGE_INFO:
hash_byte(FILTERING_MAGIC_CONTRACT_NAME, (cx_hash_t *) &hash_ctx);
break;
default:
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
PRINTF("Invalid filtering type when verifying signature!\n");
return false;
}
// Chain ID
chain_id = __builtin_bswap64(eip712_context->chain_id);
hash_nbytes((uint8_t *) &chain_id, sizeof(chain_id), (cx_hash_t *) &hash_ctx);
// Contract address
hash_nbytes(eip712_context->contract_addr,
sizeof(eip712_context->contract_addr),
(cx_hash_t *) &hash_ctx);
// Schema hash
hash_nbytes(eip712_context->schema_hash,
sizeof(eip712_context->schema_hash),
(cx_hash_t *) &hash_ctx);
if (type == FILTERING_SHOW_FIELD) {
hash_filtering_path((cx_hash_t *) &hash_ctx);
} else // FILTERING_PROVIDE_MESSAGE_INFO
{
hash_byte(ui_712_remaining_filters(), (cx_hash_t *) &hash_ctx);
}
// Display name
hash_nbytes((uint8_t *) dname, sizeof(char) * dname_length, (cx_hash_t *) &hash_ctx);
// Finalize hash
cx_hash((cx_hash_t *) &hash_ctx, CX_LAST, NULL, 0, hash, INT256_LENGTH);
cx_ecfp_init_public_key(CX_CURVE_256K1,
LEDGER_SIGNATURE_PUBLIC_KEY,
sizeof(LEDGER_SIGNATURE_PUBLIC_KEY),
&verifying_key);
if (!cx_ecdsa_verify(&verifying_key, CX_LAST, CX_SHA256, hash, sizeof(hash), sig, sig_length)) {
#ifndef HAVE_BYPASS_SIGNATURES
PRINTF("Invalid EIP-712 filtering signature\n");
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
#endif
}
return true;
}
/**
* Provide filtering information about upcoming struct field
*
* @param[in] payload the raw data received
* @param[in] length payload length
* @param[in] type the type of filtering
* @return if everything went well or not
*/
bool provide_filtering_info(const uint8_t *const payload, uint8_t length, e_filtering_type type) {
bool ret = false;
uint8_t dname_len;
const char *dname;
uint8_t sig_len;
const uint8_t *sig;
uint8_t offset = 0;
if (type == FILTERING_PROVIDE_MESSAGE_INFO) {
if (path_get_root_type() != ROOT_DOMAIN) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
} else // FILTERING_SHOW_FIELD
{
if (path_get_root_type() != ROOT_MESSAGE) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
}
if (length > 0) {
dname_len = payload[offset++];
if ((1 + dname_len) < length) {
dname = (char *) &payload[offset];
offset += dname_len;
if (type == FILTERING_PROVIDE_MESSAGE_INFO) {
ui_712_set_filters_count(payload[offset++]);
}
sig_len = payload[offset++];
sig = &payload[offset];
offset += sig_len;
if ((sig_len > 0) && (offset == length)) {
if ((ret = verify_filtering_signature(dname_len, dname, sig_len, sig, type))) {
if (type == FILTERING_PROVIDE_MESSAGE_INFO) {
if (!N_storage.verbose_eip712) {
ui_712_set_title("Contract", 8);
ui_712_set_value(dname, dname_len);
ui_712_redraw_generic_step();
}
} else // FILTERING_SHOW_FIELD
{
if (dname_len > 0) // don't substitute for an empty name
{
ui_712_set_title(dname, dname_len);
}
ui_712_flag_field(true, dname_len > 0);
}
}
}
}
}
return ret;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,18 @@
#ifndef FILTERING_H_
#define FILTERING_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdbool.h>
#include <stdint.h>
#define FILTERING_MAGIC_CONTRACT_NAME 0b10110111 // 183
#define FILTERING_MAGIC_STRUCT_FIELD 0b01001000 // ~183 = 72
typedef enum { FILTERING_PROVIDE_MESSAGE_INFO, FILTERING_SHOW_FIELD } e_filtering_type;
bool provide_filtering_info(const uint8_t *const payload, uint8_t length, e_filtering_type type);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // FILTERING_H_

View File

@@ -0,0 +1,117 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include "format_hash_field_type.h"
#include "mem.h"
#include "mem_utils.h"
#include "commands_712.h"
#include "hash_bytes.h"
#include "apdu_constants.h" // APDU response codes
#include "typed_data.h"
/**
* Format & hash a struct field typesize
*
* @param[in] field_ptr pointer to the struct field
* @param[in] hash_ctx pointer to the hashing context
* @return whether the formatting & hashing were successful or not
*/
static bool format_hash_field_type_size(const void *const field_ptr, cx_hash_t *hash_ctx) {
uint16_t field_size;
char *uint_str_ptr;
uint8_t uint_str_len;
field_size = get_struct_field_typesize(field_ptr);
switch (struct_field_type(field_ptr)) {
case TYPE_SOL_INT:
case TYPE_SOL_UINT:
field_size *= 8; // bytes -> bits
break;
case TYPE_SOL_BYTES_FIX:
break;
default:
// should not be in here :^)
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
uint_str_ptr = mem_alloc_and_format_uint(field_size, &uint_str_len);
if (uint_str_ptr == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
hash_nbytes((uint8_t *) uint_str_ptr, uint_str_len, hash_ctx);
mem_dealloc(uint_str_len);
return true;
}
/**
* Format & hash a struct field array levels
*
* @param[in] field_ptr pointer to the struct field
* @param[in] hash_ctx pointer to the hashing context
* @return whether the formatting & hashing were successful or not
*/
static bool format_hash_field_type_array_levels(const void *const field_ptr, cx_hash_t *hash_ctx) {
uint8_t array_size;
char *uint_str_ptr;
uint8_t uint_str_len;
const void *lvl_ptr;
uint8_t lvls_count;
lvl_ptr = get_struct_field_array_lvls_array(field_ptr, &lvls_count);
while (lvls_count-- > 0) {
hash_byte('[', hash_ctx);
switch (struct_field_array_depth(lvl_ptr, &array_size)) {
case ARRAY_DYNAMIC:
break;
case ARRAY_FIXED_SIZE:
if ((uint_str_ptr = mem_alloc_and_format_uint(array_size, &uint_str_len)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
hash_nbytes((uint8_t *) uint_str_ptr, uint_str_len, hash_ctx);
mem_dealloc(uint_str_len);
break;
default:
// should not be in here :^)
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
hash_byte(']', hash_ctx);
lvl_ptr = get_next_struct_field_array_lvl(lvl_ptr);
}
return true;
}
/**
* Format & hash a struct field type
*
* @param[in] field_ptr pointer to the struct field
* @param[in] hash_ctx pointer to the hashing context
* @return whether the formatting & hashing were successful or not
*/
bool format_hash_field_type(const void *const field_ptr, cx_hash_t *hash_ctx) {
const char *name;
uint8_t length;
// field type name
name = get_struct_field_typename(field_ptr, &length);
hash_nbytes((uint8_t *) name, length, hash_ctx);
// field type size
if (struct_field_has_typesize(field_ptr)) {
if (!format_hash_field_type_size(field_ptr, hash_ctx)) {
return false;
}
}
// field type array levels
if (struct_field_is_array(field_ptr)) {
if (!format_hash_field_type_array_levels(field_ptr, hash_ctx)) {
return false;
}
}
return true;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,12 @@
#ifndef FORMAT_HASH_FIELD_TYPE_H_
#define FORMAT_HASH_FIELD_TYPE_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include "cx.h"
bool format_hash_field_type(const void *const field_ptr, cx_hash_t *hash_ctx);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // FORMAT_HASH_FIELD_TYPE_H_

View File

@@ -0,0 +1,26 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include "hash_bytes.h"
/**
* Continue given progressive hash on given bytes
*
* @param[in] bytes_ptr pointer to bytes
* @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) {
cx_hash(hash_ctx, 0, bytes_ptr, n, NULL, 0);
}
/**
* Continue given progressive hash on given byte
*
* @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) {
hash_nbytes(&byte, 1, hash_ctx);
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

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

View File

@@ -0,0 +1,608 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include <string.h>
#include "path.h"
#include "mem.h"
#include "context_712.h"
#include "commands_712.h"
#include "type_hash.h"
#include "shared_context.h"
#include "ethUtils.h"
#include "mem_utils.h"
#include "ui_logic.h"
#include "apdu_constants.h" // APDU response codes
#include "typed_data.h"
static s_path *path_struct = NULL;
/**
* Get the field pointer to by the first N depths of the path.
*
* @param[out] fields_count_ptr the number of fields in the last evaluated depth
* @param[in] n the number of depths to evaluate
* @return the field which the first Nth depths points to
*/
static const void *get_nth_field(uint8_t *const fields_count_ptr, uint8_t n) {
const void *struct_ptr = NULL;
const void *field_ptr = NULL;
const char *typename;
uint8_t length;
uint8_t fields_count;
if (path_struct == NULL) {
return NULL;
}
struct_ptr = path_struct->root_struct;
if (n > path_struct->depth_count) // sanity check
{
return NULL;
}
for (uint8_t depth = 0; depth < n; ++depth) {
field_ptr = get_struct_fields_array(struct_ptr, &fields_count);
if (fields_count_ptr != NULL) {
*fields_count_ptr = fields_count;
}
// check if the index at this depth makes sense
if (path_struct->depths[depth] > fields_count) {
return NULL;
}
for (uint8_t index = 0; index < path_struct->depths[depth]; ++index) {
field_ptr = get_next_struct_field(field_ptr);
}
if (struct_field_type(field_ptr) == TYPE_CUSTOM) {
typename = get_struct_field_typename(field_ptr, &length);
if ((struct_ptr = get_structn(typename, length)) == NULL) {
return NULL;
}
}
}
return field_ptr;
}
/**
* Get the element the path is pointing to.
*
* @param[out] the number of fields in the depth of the returned field
* @return the field which the path points to
*/
static inline const void *get_field(uint8_t *const fields_count) {
return get_nth_field(fields_count, path_struct->depth_count);
}
/**
* Get Nth struct field from path
*
* @param[in] n nth depth requested
* @return pointer to the matching field, \ref NULL otherwise
*/
const void *path_get_nth_field(uint8_t n) {
return get_nth_field(NULL, n);
}
/**
* Get Nth to last struct field from path
*
* @param[in] n nth to last depth requested
* @return pointer to the matching field, \ref NULL otherwise
*/
const void *path_get_nth_field_to_last(uint8_t n) {
const char *typename;
uint8_t typename_len;
const void *field_ptr;
const void *struct_ptr = NULL;
field_ptr = get_nth_field(NULL, path_struct->depth_count - n);
if (field_ptr != NULL) {
typename = get_struct_field_typename(field_ptr, &typename_len);
struct_ptr = get_structn(typename, typename_len);
}
return struct_ptr;
}
/**
* Get the element the path is pointing to
*
* @return the field which the path points to
*/
const void *path_get_field(void) {
return get_field(NULL);
}
/**
* Go down (add) a depth level.
*
* @return whether the push was succesful
*/
static bool path_depth_list_push(void) {
if (path_struct == NULL) {
return false;
}
if (path_struct->depth_count == MAX_PATH_DEPTH) {
return false;
}
path_struct->depths[path_struct->depth_count] = 0;
path_struct->depth_count += 1;
return true;
}
/**
* Get the last hashing context (corresponding to the current path depth)
*
* @return pointer to the hashing context
*/
static cx_sha3_t *get_last_hash_ctx(void) {
return ((cx_sha3_t *) mem_alloc(0)) - 1;
}
/**
* Finalize the last hashing context
*
* @param[out] hash pointer to buffer where the hash will be stored
*/
static void finalize_hash_depth(uint8_t *hash) {
const cx_sha3_t *hash_ctx;
hash_ctx = get_last_hash_ctx();
// finalize hash
cx_hash((cx_hash_t *) hash_ctx, CX_LAST, NULL, 0, hash, KECCAK256_HASH_BYTESIZE);
mem_dealloc(sizeof(*hash_ctx)); // remove hash context
}
/**
* Continue last progressive hashing context with given hash
*
* @param[in] hash pointer to given hash
*/
static void feed_last_hash_depth(const uint8_t *const hash) {
const cx_sha3_t *hash_ctx;
hash_ctx = get_last_hash_ctx();
// continue progressive hash with the array hash
cx_hash((cx_hash_t *) hash_ctx, 0, hash, KECCAK256_HASH_BYTESIZE, NULL, 0);
}
/**
* Create a new hashing context depth in memory
*
* @param[in] init if the hashing context should be initialized
* @return whether the memory allocation of the hashing context was successful
*/
static bool push_new_hash_depth(bool init) {
cx_sha3_t *hash_ctx;
// allocate new hash context
if ((hash_ctx = MEM_ALLOC_AND_ALIGN_TYPE(*hash_ctx)) == NULL) {
return false;
}
if (init) {
cx_keccak_init(hash_ctx, 256); // initialize it
}
return true;
}
/**
* Go up (remove) a depth level.
*
* @return whether the pop was successful
*/
static bool path_depth_list_pop(void) {
uint8_t hash[KECCAK256_HASH_BYTESIZE];
if (path_struct == NULL) {
return false;
}
if (path_struct->depth_count == 0) {
return false;
}
path_struct->depth_count -= 1;
finalize_hash_depth(hash);
if (path_struct->depth_count > 0) {
feed_last_hash_depth(hash);
} else {
switch (path_struct->root_type) {
case ROOT_DOMAIN:
memcpy(tmpCtx.messageSigningContext712.domainHash, hash, KECCAK256_HASH_BYTESIZE);
break;
case ROOT_MESSAGE:
memcpy(tmpCtx.messageSigningContext712.messageHash, hash, KECCAK256_HASH_BYTESIZE);
break;
default:
break;
}
}
return true;
}
/**
* Go down (add) an array depth level.
*
* @param[in] path_idx the index in the path list
* @param[in] the number of elements contained in that depth
* @return whether the push was successful
*/
static bool array_depth_list_push(uint8_t path_idx, uint8_t size) {
s_array_depth *arr;
if (path_struct == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
if (path_struct->array_depth_count == MAX_ARRAY_DEPTH) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
arr = &path_struct->array_depths[path_struct->array_depth_count];
arr->path_index = path_idx;
arr->size = size;
path_struct->array_depth_count += 1;
return true;
}
/**
* Go up (remove) an array depth level.
*
* @return whether the pop was successful
*/
static bool array_depth_list_pop(void) {
uint8_t hash[KECCAK256_HASH_BYTESIZE];
if (path_struct == NULL) {
return false;
}
if (path_struct->array_depth_count == 0) {
return false;
}
finalize_hash_depth(hash);
feed_last_hash_depth(hash);
path_struct->array_depth_count -= 1;
return true;
}
/**
* Updates the path so that it doesn't point to a struct-type field, but rather
* only to actual fields.
*
* @return whether the path update worked or not
*/
static bool path_update(void) {
uint8_t fields_count;
const void *struct_ptr;
const void *field_ptr;
const char *typename;
uint8_t typename_len;
uint8_t hash[KECCAK256_HASH_BYTESIZE];
if (path_struct == NULL) {
return false;
}
if ((field_ptr = get_field(NULL)) == NULL) {
return false;
}
struct_ptr = path_struct->root_struct;
while (struct_field_type(field_ptr) == TYPE_CUSTOM) {
typename = get_struct_field_typename(field_ptr, &typename_len);
if ((struct_ptr = get_structn(typename, typename_len)) == NULL) {
return false;
}
if ((field_ptr = get_struct_fields_array(struct_ptr, &fields_count)) == NULL) {
return false;
}
if (push_new_hash_depth(true) == false) {
return false;
}
// get the struct typehash
if (type_hash(typename, typename_len, hash) == false) {
return false;
}
feed_last_hash_depth(hash);
ui_712_queue_struct_to_review();
path_depth_list_push();
}
return true;
}
/**
* Set a new struct as the path root type
*
* @param[in] struct_name the root struct name
* @param[in] name_length the root struct name length
* @return boolean indicating if it was successful or not
*/
bool path_set_root(const char *const struct_name, uint8_t name_length) {
uint8_t hash[KECCAK256_HASH_BYTESIZE];
if (path_struct == NULL) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
path_struct->root_struct = get_structn(struct_name, name_length);
if (path_struct->root_struct == NULL) {
PRINTF("Struct name not found (");
for (int i = 0; i < name_length; ++i) {
PRINTF("%c", struct_name[i]);
}
PRINTF(")!\n");
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if (push_new_hash_depth(true) == false) {
return false;
}
if (type_hash(struct_name, name_length, hash) == false) {
return false;
}
feed_last_hash_depth(hash);
//
// init depth, at 0 : empty path
path_struct->depth_count = 0;
path_depth_list_push();
// init array levels at 0
path_struct->array_depth_count = 0;
if ((name_length == strlen(DOMAIN_STRUCT_NAME)) &&
(strncmp(struct_name, DOMAIN_STRUCT_NAME, name_length) == 0)) {
path_struct->root_type = ROOT_DOMAIN;
} else {
path_struct->root_type = ROOT_MESSAGE;
}
struct_state = DEFINED;
// because the first field could be a struct type
path_update();
return true;
}
/**
* Checks the new array depth and adds it to the list
*
* @param[in] depth pointer to the array depth definition
* @param[in] total_count number of array depth contained down to this array depth
* @param[in] pidx path index
* @param[in] size requested array depth size
* @return whether the checks and add were successful or not
*/
static bool check_and_add_array_depth(const void *depth,
uint8_t total_count,
uint8_t pidx,
uint8_t size) {
uint8_t expected_size;
uint8_t arr_idx;
e_array_type expected_type;
arr_idx = (total_count - path_struct->array_depth_count) - 1;
// we skip index 0, since we already have it
for (uint8_t idx = 1; idx < (arr_idx + 1); ++idx) {
if ((depth = get_next_struct_field_array_lvl(depth)) == NULL) {
return false;
}
}
expected_type = struct_field_array_depth(depth, &expected_size);
if ((expected_type == ARRAY_FIXED_SIZE) && (expected_size != size)) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
PRINTF("Unexpected array depth size. (expected %d, got %d)\n", expected_size, size);
return false;
}
// add it
if (!array_depth_list_push(pidx, size)) {
return false;
}
return true;
}
/**
* Add a new array depth with a given size (number of elements).
*
* @param[in] data pointer to the number of elements
* @param[in] length length of data
* @return whether the add was successful or not
*/
bool path_new_array_depth(const uint8_t *const data, uint8_t length) {
const void *field_ptr = NULL;
const void *depth = NULL;
uint8_t depth_count;
uint8_t total_count = 0;
uint8_t pidx;
bool is_custom;
if (path_struct == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
} else if (length != 1) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
for (pidx = 0; pidx < path_struct->depth_count; ++pidx) {
if ((field_ptr = get_nth_field(NULL, pidx + 1)) == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
if (struct_field_is_array(field_ptr)) {
if ((depth = get_struct_field_array_lvls_array(field_ptr, &depth_count)) == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
total_count += depth_count;
if (total_count > path_struct->array_depth_count) {
if (!check_and_add_array_depth(depth, total_count, pidx, *data)) {
return false;
}
break;
}
}
}
if (pidx == path_struct->depth_count) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
PRINTF("Did not find a matching array type.\n");
return false;
}
is_custom = struct_field_type(field_ptr) == TYPE_CUSTOM;
if (push_new_hash_depth(!is_custom) == false) {
return false;
}
if (is_custom) {
cx_sha3_t *hash_ctx = get_last_hash_ctx();
cx_sha3_t *old_ctx = hash_ctx - 1;
memcpy(hash_ctx, old_ctx, sizeof(*old_ctx));
cx_keccak_init(old_ctx, 256); // init hash
}
return true;
}
/**
* Advance within the struct that contains the field the path points to.
*
* @return whether the end of the struct has been reached.
*/
static bool path_advance_in_struct(void) {
bool end_reached = true;
uint8_t *depth = &path_struct->depths[path_struct->depth_count - 1];
uint8_t fields_count;
if (path_struct == NULL) {
return false;
}
if ((get_field(&fields_count)) == NULL) {
return false;
}
if (path_struct->depth_count > 0) {
*depth += 1;
ui_712_notify_filter_change();
end_reached = (*depth == fields_count);
}
if (end_reached) {
path_depth_list_pop();
}
return end_reached;
}
/**
* Advance within the array levels of the current field the path points to.
*
* @return whether the end of the array levels has been reached.
*/
static bool path_advance_in_array(void) {
bool end_reached;
s_array_depth *arr_depth;
if (path_struct == NULL) {
return false;
}
do {
end_reached = false;
arr_depth = &path_struct->array_depths[path_struct->array_depth_count - 1];
if ((path_struct->array_depth_count > 0) &&
(arr_depth->path_index == (path_struct->depth_count - 1))) {
arr_depth->size -= 1;
if (arr_depth->size == 0) {
array_depth_list_pop();
end_reached = true;
} else {
return false;
}
}
} while (end_reached);
return true;
}
/**
* Updates the path to point to the next field in order (DFS).
*
* @return whether the advancement was successful or not
*/
bool path_advance(void) {
bool end_reached;
do {
if (path_advance_in_array()) {
end_reached = path_advance_in_struct();
} else {
end_reached = false;
}
} while (end_reached);
path_update();
return true;
}
/**
* Get root structure type from path (domain or message)
*
* @return enum representing root type
*/
e_root_type path_get_root_type(void) {
if (path_struct == NULL) {
return ROOT_DOMAIN;
}
return path_struct->root_type;
}
/**
* Get root structure from path
*
* @return pointer to the root structure definition
*/
const void *path_get_root(void) {
if (path_struct == NULL) {
return NULL;
}
return path_struct->root_struct;
}
/**
* Get the current amount of depth
*
* @return depth count
*/
uint8_t path_get_depth_count(void) {
if (path_struct == NULL) {
return 0;
}
return path_struct->depth_count;
}
/**
* Initialize the path context with its indexes in memory and sets it with a depth of 0.
*
* @return whether the memory allocation were successful.
*/
bool path_init(void) {
if (path_struct == NULL) {
if ((path_struct = MEM_ALLOC_AND_ALIGN_TYPE(*path_struct)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
} else {
path_struct->depth_count = 0;
}
}
return path_struct != NULL;
}
/**
* De-initialize the path context
*/
void path_deinit(void) {
path_struct = NULL;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,42 @@
#ifndef PATH_H_
#define PATH_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include <stdbool.h>
#define MAX_PATH_DEPTH 16
#define MAX_ARRAY_DEPTH 8
typedef struct {
uint8_t path_index;
uint8_t size;
} s_array_depth;
typedef enum { ROOT_DOMAIN, ROOT_MESSAGE } e_root_type;
typedef struct {
uint8_t depth_count;
uint8_t depths[MAX_PATH_DEPTH];
uint8_t array_depth_count;
s_array_depth array_depths[MAX_ARRAY_DEPTH];
const void *root_struct;
e_root_type root_type;
} s_path;
bool path_set_root(const char *const struct_name, uint8_t length);
const void *path_get_field(void);
bool path_advance(void);
bool path_init(void);
void path_deinit(void);
bool path_new_array_depth(const uint8_t *const data, uint8_t length);
e_root_type path_get_root_type(void);
const void *path_get_root(void);
const void *path_get_nth_field(uint8_t n);
const void *path_get_nth_field_to_last(uint8_t n);
uint8_t path_get_depth_count(void);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // PATH_H_

View File

@@ -0,0 +1,73 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include "schema_hash.h"
#include "hash_bytes.h"
#include "typed_data.h"
#include "format_hash_field_type.h"
#include "context_712.h"
// the SDK does not define a SHA-224 type, define it here so it's easier
// to understand in the code
typedef cx_sha256_t cx_sha224_t;
/**
* Compute the schema hash
*
* The schema hash is the value of the root field "types" in the JSON data,
* stripped of all its spaces and newlines. This function reconstructs the JSON syntax
* from the stored typed data.
*
* @return whether the schema hash was successful or not
*/
bool compute_schema_hash(void) {
const void *struct_ptr;
uint8_t structs_count;
const void *field_ptr;
uint8_t fields_count;
const char *name;
uint8_t name_length;
cx_sha224_t hash_ctx;
cx_sha224_init(&hash_ctx);
struct_ptr = get_structs_array(&structs_count);
hash_byte('{', (cx_hash_t *) &hash_ctx);
while (structs_count-- > 0) {
name = get_struct_name(struct_ptr, &name_length);
hash_byte('"', (cx_hash_t *) &hash_ctx);
hash_nbytes((uint8_t *) name, name_length, (cx_hash_t *) &hash_ctx);
hash_nbytes((uint8_t *) "\":[", 3, (cx_hash_t *) &hash_ctx);
field_ptr = get_struct_fields_array(struct_ptr, &fields_count);
while (fields_count-- > 0) {
hash_nbytes((uint8_t *) "{\"name\":\"", 9, (cx_hash_t *) &hash_ctx);
name = get_struct_field_keyname(field_ptr, &name_length);
hash_nbytes((uint8_t *) name, name_length, (cx_hash_t *) &hash_ctx);
hash_nbytes((uint8_t *) "\",\"type\":\"", 10, (cx_hash_t *) &hash_ctx);
if (!format_hash_field_type(field_ptr, (cx_hash_t *) &hash_ctx)) {
return false;
}
hash_nbytes((uint8_t *) "\"}", 2, (cx_hash_t *) &hash_ctx);
if (fields_count > 0) {
hash_byte(',', (cx_hash_t *) &hash_ctx);
}
field_ptr = get_next_struct_field(field_ptr);
}
hash_byte(']', (cx_hash_t *) &hash_ctx);
if (structs_count > 0) {
hash_byte(',', (cx_hash_t *) &hash_ctx);
}
struct_ptr = get_next_struct(struct_ptr);
}
hash_byte('}', (cx_hash_t *) &hash_ctx);
// copy hash into context struct
cx_hash((cx_hash_t *) &hash_ctx,
CX_LAST,
NULL,
0,
eip712_context->schema_hash,
sizeof(eip712_context->schema_hash));
return true;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,12 @@
#ifndef SCHEMA_HASH_H_
#define SCHEMA_HASH_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdbool.h>
bool compute_schema_hash(void);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // SCHEMA_HASH_H_

View File

@@ -0,0 +1,136 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include <string.h>
#include "sol_typenames.h"
#include "mem.h"
#include "os_pic.h"
#include "apdu_constants.h" // APDU response codes
#include "typed_data.h"
#include "utils.h" // ARRAY_SIZE
// Bit indicating they are more types associated to this typename
#define TYPENAME_MORE_TYPE (1 << 7)
static uint8_t *sol_typenames = NULL;
enum { IDX_ENUM = 0, IDX_STR_IDX, IDX_COUNT };
/**
* Find a match between a typename index and all the type enums associated to it
*
* @param[in] enum_to_idx the type enum to typename index table
* @param[in] t_idx typename index
* @return whether at least one match was found
*/
static bool find_enum_matches(const uint8_t enum_to_idx[TYPES_COUNT - 1][IDX_COUNT],
uint8_t t_idx) {
uint8_t *enum_match = NULL;
// loop over enum/typename pairs
for (uint8_t e_idx = 0; e_idx < (TYPES_COUNT - 1); ++e_idx) {
if (t_idx == enum_to_idx[e_idx][IDX_STR_IDX]) // match
{
if (enum_match != NULL) // in case of a previous match, mark it
{
*enum_match |= TYPENAME_MORE_TYPE;
}
if ((enum_match = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*enum_match = enum_to_idx[e_idx][IDX_ENUM];
}
}
return (enum_match != NULL);
}
/**
* Initialize solidity typenames in memory
*
* @return whether the initialization went well or not
*/
bool sol_typenames_init(void) {
const char *const typenames[] = {
"int", // 0
"uint", // 1
"address", // 2
"bool", // 3
"string", // 4
"bytes" // 5
};
// \ref TYPES_COUNT - 1 since we don't include \ref TYPE_CUSTOM
const uint8_t enum_to_idx[TYPES_COUNT - 1][IDX_COUNT] = {{TYPE_SOL_INT, 0},
{TYPE_SOL_UINT, 1},
{TYPE_SOL_ADDRESS, 2},
{TYPE_SOL_BOOL, 3},
{TYPE_SOL_STRING, 4},
{TYPE_SOL_BYTES_FIX, 5},
{TYPE_SOL_BYTES_DYN, 5}};
uint8_t *typename_len_ptr;
char *typename_ptr;
if ((sol_typenames = mem_alloc(sizeof(uint8_t))) == NULL) {
return false;
}
*(sol_typenames) = 0;
// loop over typenames
for (uint8_t t_idx = 0; t_idx < ARRAY_SIZE(typenames); ++t_idx) {
// if at least one match was found
if (find_enum_matches(enum_to_idx, t_idx)) {
if ((typename_len_ptr = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
// get pointer to the allocated space just above
*typename_len_ptr = strlen(PIC(typenames[t_idx]));
if ((typename_ptr = mem_alloc(sizeof(char) * *typename_len_ptr)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
// copy typename
memcpy(typename_ptr, PIC(typenames[t_idx]), *typename_len_ptr);
}
// increment array size
*(sol_typenames) += 1;
}
return true;
}
/**
* Get typename from a given field
*
* @param[in] field_ptr pointer to a struct field
* @param[out] length length of the returned typename
* @return typename or \ref NULL in case it wasn't found
*/
const char *get_struct_field_sol_typename(const uint8_t *field_ptr, uint8_t *const length) {
e_type field_type;
const uint8_t *typename_ptr;
uint8_t typenames_count;
bool more_type;
bool typename_found;
field_type = struct_field_type(field_ptr);
typename_ptr = get_array_in_mem(sol_typenames, &typenames_count);
typename_found = false;
while (typenames_count-- > 0) {
more_type = true;
while (more_type) {
more_type = *typename_ptr & TYPENAME_MORE_TYPE;
e_type type_enum = *typename_ptr & TYPENAME_ENUM;
if (type_enum == field_type) {
typename_found = true;
}
typename_ptr += 1;
}
typename_ptr = (uint8_t *) get_string_in_mem(typename_ptr, length);
if (typename_found) return (char *) typename_ptr;
typename_ptr += *length;
}
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return NULL; // Not found
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,15 @@
#ifndef SOL_TYPENAMES_H_
#define SOL_TYPENAMES_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdbool.h>
#include <stdint.h>
bool sol_typenames_init(void);
const char *get_struct_field_sol_typename(const uint8_t *ptr, uint8_t *const length);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // SOL_TYPENAMES_H_

View File

@@ -0,0 +1,199 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "mem.h"
#include "mem_utils.h"
#include "type_hash.h"
#include "shared_context.h"
#include "ethUtils.h" // KECCAK256_HASH_BYTESIZE
#include "format_hash_field_type.h"
#include "hash_bytes.h"
#include "apdu_constants.h" // APDU response codes
#include "typed_data.h"
/**
* Encode & hash the given structure field
*
* @param[in] field_ptr pointer to the struct field
* @return \ref true it finished correctly, \ref false if it didn't (memory allocation)
*/
static bool encode_and_hash_field(const void *const field_ptr) {
const char *name;
uint8_t length;
if (!format_hash_field_type(field_ptr, (cx_hash_t *) &global_sha3)) {
return false;
}
// space between field type name and field name
hash_byte(' ', (cx_hash_t *) &global_sha3);
// field name
name = get_struct_field_keyname(field_ptr, &length);
hash_nbytes((uint8_t *) name, length, (cx_hash_t *) &global_sha3);
return true;
}
/**
* Encode & hash the a given structure type
*
* @param[in] struct_ptr pointer to the structure we want the typestring of
* @param[in] str_length length of the formatted string in memory
* @return pointer of the string in memory, \ref NULL in case of an error
*/
static bool encode_and_hash_type(const void *const struct_ptr) {
const char *struct_name;
uint8_t struct_name_length;
const uint8_t *field_ptr;
uint8_t fields_count;
// struct name
struct_name = get_struct_name(struct_ptr, &struct_name_length);
hash_nbytes((uint8_t *) struct_name, struct_name_length, (cx_hash_t *) &global_sha3);
// opening struct parenthese
hash_byte('(', (cx_hash_t *) &global_sha3);
field_ptr = get_struct_fields_array(struct_ptr, &fields_count);
for (uint8_t idx = 0; idx < fields_count; ++idx) {
// comma separating struct fields
if (idx > 0) {
hash_byte(',', (cx_hash_t *) &global_sha3);
}
if (encode_and_hash_field(field_ptr) == false) {
return NULL;
}
field_ptr = get_next_struct_field(field_ptr);
}
// closing struct parenthese
hash_byte(')', (cx_hash_t *) &global_sha3);
return true;
}
/**
* Sort the given structs based by alphabetical order
*
* @param[in] deps_count count of how many struct dependencies pointers
* @param[in,out] deps pointer to the first dependency pointer
*/
static void sort_dependencies(uint8_t deps_count, const void **deps) {
bool changed;
const void *tmp_ptr;
const char *name1, *name2;
uint8_t namelen1, namelen2;
int str_cmp_result;
do {
changed = false;
for (size_t idx = 0; (idx + 1) < deps_count; ++idx) {
name1 = get_struct_name(*(deps + idx), &namelen1);
name2 = get_struct_name(*(deps + idx + 1), &namelen2);
str_cmp_result = strncmp(name1, name2, MIN(namelen1, namelen2));
if ((str_cmp_result > 0) || ((str_cmp_result == 0) && (namelen1 > namelen2))) {
tmp_ptr = *(deps + idx);
*(deps + idx) = *(deps + idx + 1);
*(deps + idx + 1) = tmp_ptr;
changed = true;
}
}
} while (changed);
}
/**
* Find all the dependencies from a given structure
*
* @param[out] deps_count count of how many struct dependencie pointers
* @param[in] first_dep pointer to the first dependency pointer
* @param[in] struct_ptr pointer to the struct we are getting the dependencies of
* @return pointer to the first found dependency, \ref NULL otherwise
*/
static const void **get_struct_dependencies(uint8_t *const deps_count,
const void **first_dep,
const void *const struct_ptr) {
uint8_t fields_count;
const void *field_ptr;
const char *arg_structname;
uint8_t arg_structname_length;
const void *arg_struct_ptr;
size_t dep_idx;
const void **new_dep;
field_ptr = get_struct_fields_array(struct_ptr, &fields_count);
for (uint8_t idx = 0; idx < fields_count; ++idx) {
if (struct_field_type(field_ptr) == TYPE_CUSTOM) {
// get struct name
arg_structname = get_struct_field_typename(field_ptr, &arg_structname_length);
// from its name, get the pointer to its definition
arg_struct_ptr = get_structn(arg_structname, arg_structname_length);
// check if it is not already present in the dependencies array
for (dep_idx = 0; dep_idx < *deps_count; ++dep_idx) {
// it's a match!
if (*(first_dep + dep_idx) == arg_struct_ptr) {
break;
}
}
// if it's not present in the array, add it and recurse into it
if (dep_idx == *deps_count) {
*deps_count += 1;
if ((new_dep = MEM_ALLOC_AND_ALIGN_TYPE(void *)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return NULL;
}
if (*deps_count == 1) {
first_dep = new_dep;
}
*new_dep = arg_struct_ptr;
// TODO: Move away from recursive calls
get_struct_dependencies(deps_count, first_dep, arg_struct_ptr);
}
}
field_ptr = get_next_struct_field(field_ptr);
}
return first_dep;
}
/**
* Encode the structure's type and hash it
*
* @param[in] struct_name name of the given struct
* @param[in] struct_name_length length of the name of the given struct
* @param[out] hash_buf buffer containing the resulting type_hash
* @return whether the type_hash was successful or not
*/
bool type_hash(const char *const struct_name, const uint8_t struct_name_length, uint8_t *hash_buf) {
const void *const struct_ptr = get_structn(struct_name, struct_name_length);
uint8_t deps_count = 0;
const void **deps;
void *mem_loc_bak = mem_alloc(0);
cx_keccak_init(&global_sha3, 256); // init hash
deps = get_struct_dependencies(&deps_count, NULL, struct_ptr);
if ((deps_count > 0) && (deps == NULL)) {
return false;
}
sort_dependencies(deps_count, deps);
if (encode_and_hash_type(struct_ptr) == false) {
return false;
}
// loop over each struct and generate string
for (int idx = 0; idx < deps_count; ++idx) {
if (encode_and_hash_type(*deps) == false) {
return false;
}
deps += 1;
}
mem_dealloc(mem_alloc(0) - mem_loc_bak);
// copy hash into memory
cx_hash((cx_hash_t *) &global_sha3, CX_LAST, NULL, 0, hash_buf, KECCAK256_HASH_BYTESIZE);
return true;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,13 @@
#ifndef TYPE_HASH_H_
#define TYPE_HASH_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include <stdbool.h>
bool type_hash(const char *const struct_name, const uint8_t struct_name_length, uint8_t *hash_buf);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // TYPE_HASH_H_

View File

@@ -0,0 +1,749 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdlib.h>
#include <string.h>
#include "typed_data.h"
#include "sol_typenames.h"
#include "apdu_constants.h" // APDU response codes
#include "context_712.h"
#include "mem.h"
#include "mem_utils.h"
static s_typed_data *typed_data = NULL;
/**
* Initialize the typed data context
*
* @return whether the memory allocation was successful
*/
bool typed_data_init(void) {
if (typed_data == NULL) {
if ((typed_data = MEM_ALLOC_AND_ALIGN_TYPE(*typed_data)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
// set types pointer
if ((typed_data->structs_array = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
// create len(types)
*(typed_data->structs_array) = 0;
}
return true;
}
void typed_data_deinit(void) {
typed_data = NULL;
}
/**
* Skip TypeDesc from a structure field
*
* @param[in] field_ptr pointer to the beginning of the struct field
* @param[in] ptr pointer to the current location within the struct field
* @return pointer to the data right after
*/
static const uint8_t *field_skip_typedesc(const uint8_t *field_ptr, const uint8_t *ptr) {
(void) ptr;
return field_ptr + sizeof(typedesc_t);
}
/**
* Skip the type name from a structure field
*
* @param[in] field_ptr pointer to the beginning of the struct field
* @param[in] ptr pointer to the current location within the struct field
* @return pointer to the data right after
*/
static const uint8_t *field_skip_typename(const uint8_t *field_ptr, const uint8_t *ptr) {
uint8_t size;
if (struct_field_type(field_ptr) == TYPE_CUSTOM) {
get_string_in_mem(ptr, &size);
ptr += (sizeof(size) + size);
}
return ptr;
}
/**
* Skip the type size from a structure field
*
* @param[in] field_ptr pointer to the beginning of the struct field
* @param[in] ptr pointer to the current location within the struct field
* @return pointer to the data right after
*/
static const uint8_t *field_skip_typesize(const uint8_t *field_ptr, const uint8_t *ptr) {
if (struct_field_has_typesize(field_ptr)) {
ptr += sizeof(typesize_t);
}
return ptr;
}
/**
* Skip the array levels from a structure field
*
* @param[in] field_ptr pointer to the beginning of the struct field
* @param[in] ptr pointer to the current location within the struct field
* @return pointer to the data right after
*/
static const uint8_t *field_skip_array_levels(const uint8_t *field_ptr, const uint8_t *ptr) {
uint8_t size;
if (struct_field_is_array(field_ptr)) {
ptr = get_array_in_mem(ptr, &size);
while (size-- > 0) {
ptr = get_next_struct_field_array_lvl(ptr);
}
}
return ptr;
}
/**
* Skip the key name from a structure field
*
* @param[in] field_ptr pointer to the beginning of the struct field
* @param[in] ptr pointer to the current location within the struct field
* @return pointer to the data right after
*/
static const uint8_t *field_skip_keyname(const uint8_t *field_ptr, const uint8_t *ptr) {
uint8_t size;
(void) field_ptr;
ptr = get_array_in_mem(ptr, &size);
return ptr + size;
}
/**
* Get data pointer & array size from a given pointer
*
* @param[in] ptr given pointer
* @param[out] array_size pointer to array size
* @return pointer to data
*/
const void *get_array_in_mem(const void *ptr, uint8_t *const array_size) {
if (ptr == NULL) {
return NULL;
}
if (array_size) {
*array_size = *(uint8_t *) ptr;
}
return (ptr + sizeof(*array_size));
}
/**
* Get pointer to beginning of string & its length from a given pointer
*
* @param[in] ptr given pointer
* @param[out] string_length pointer to string length
* @return pointer to beginning of the string
*/
const char *get_string_in_mem(const uint8_t *ptr, uint8_t *const string_length) {
return (char *) get_array_in_mem(ptr, string_length);
}
/**
* Get the TypeDesc from a given struct field pointer
*
* @param[in] field_ptr struct field pointer
* @return TypeDesc
*/
static inline typedesc_t get_struct_field_typedesc(const uint8_t *const field_ptr) {
if (field_ptr == NULL) {
return 0;
}
return *field_ptr;
}
/**
* Check whether a struct field is an array
*
* @param[in] field_ptr struct field pointer
* @return bool whether it is the case
*/
bool struct_field_is_array(const uint8_t *const field_ptr) {
return (get_struct_field_typedesc(field_ptr) & ARRAY_MASK);
}
/**
* Check whether a struct field has a type size associated to it
*
* @param[in] field_ptr struct field pointer
* @return bool whether it is the case
*/
bool struct_field_has_typesize(const uint8_t *const field_ptr) {
return (get_struct_field_typedesc(field_ptr) & TYPESIZE_MASK);
}
/**
* Get type from a struct field
*
* @param[in] field_ptr struct field pointer
* @return its type enum
*/
e_type struct_field_type(const uint8_t *const field_ptr) {
return (get_struct_field_typedesc(field_ptr) & TYPE_MASK);
}
/**
* Get type size from a struct field
*
* @param[in] field_ptr struct field pointer
* @return its type size
*/
uint8_t get_struct_field_typesize(const uint8_t *const field_ptr) {
if (field_ptr == NULL) {
return 0;
}
return *field_skip_typedesc(field_ptr, NULL);
}
/**
* Get custom type name from a struct field
*
* @param[in] field_ptr struct field pointer
* @param[out] length the type name length
* @return type name pointer
*/
const char *get_struct_field_custom_typename(const uint8_t *field_ptr, uint8_t *const length) {
const uint8_t *ptr;
if (field_ptr == NULL) {
return NULL;
}
ptr = field_skip_typedesc(field_ptr, NULL);
return get_string_in_mem(ptr, length);
}
/**
* Get type name from a struct field
*
* @param[in] field_ptr struct field pointer
* @param[out] length the type name length
* @return type name pointer
*/
const char *get_struct_field_typename(const uint8_t *field_ptr, uint8_t *const length) {
if (field_ptr == NULL) {
return NULL;
}
if (struct_field_type(field_ptr) == TYPE_CUSTOM) {
return get_struct_field_custom_typename(field_ptr, length);
}
return get_struct_field_sol_typename(field_ptr, length);
}
/**
* Get array type of a given struct field's array depth
*
* @param[in] array_depth_ptr given array depth
* @param[out] array_size pointer to array size
* @return array type of that depth
*/
e_array_type struct_field_array_depth(const uint8_t *array_depth_ptr, uint8_t *const array_size) {
if (array_depth_ptr == NULL) {
return 0;
}
if (*array_depth_ptr == ARRAY_FIXED_SIZE) {
if (array_size != NULL) {
*array_size = *(array_depth_ptr + sizeof(uint8_t));
}
}
return *array_depth_ptr;
}
/**
* Get next array depth form a given struct field's array depth
*
* @param[in] array_depth_ptr given array depth
* @return next array depth
*/
const uint8_t *get_next_struct_field_array_lvl(const uint8_t *const array_depth_ptr) {
const uint8_t *ptr;
if (array_depth_ptr == NULL) {
return NULL;
}
switch (*array_depth_ptr) {
case ARRAY_DYNAMIC:
ptr = array_depth_ptr;
break;
case ARRAY_FIXED_SIZE:
ptr = array_depth_ptr + 1;
break;
default:
// should not be in here :^)
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
return ptr + 1;
}
/**
* Get the array levels from a given struct field
*
* @param[in] field_ptr given struct field
* @param[out] length number of array levels
* @return pointer to the first array level
*/
const uint8_t *get_struct_field_array_lvls_array(const uint8_t *const field_ptr,
uint8_t *const length) {
const uint8_t *ptr;
if (field_ptr == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
ptr = field_skip_typedesc(field_ptr, NULL);
ptr = field_skip_typename(field_ptr, ptr);
ptr = field_skip_typesize(field_ptr, ptr);
return get_array_in_mem(ptr, length);
}
/**
* Get key name from a given struct field
*
* @param[in] field_ptr given struct field
* @param[out] length name length
* @return key name
*/
const char *get_struct_field_keyname(const uint8_t *field_ptr, uint8_t *const length) {
const uint8_t *ptr;
if (field_ptr == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
ptr = field_skip_typedesc(field_ptr, NULL);
ptr = field_skip_typename(field_ptr, ptr);
ptr = field_skip_typesize(field_ptr, ptr);
ptr = field_skip_array_levels(field_ptr, ptr);
return get_string_in_mem(ptr, length);
}
/**
* Get next struct field from a given field
*
* @param[in] field_ptr given struct field
* @return pointer to the next field
*/
const uint8_t *get_next_struct_field(const void *const field_ptr) {
const void *ptr;
if (field_ptr == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
ptr = field_skip_typedesc(field_ptr, NULL);
ptr = field_skip_typename(field_ptr, ptr);
ptr = field_skip_typesize(field_ptr, ptr);
ptr = field_skip_array_levels(field_ptr, ptr);
return field_skip_keyname(field_ptr, ptr);
}
/**
* Get name from a given struct
*
* @param[in] struct_ptr given struct
* @param[out] length name length
* @return struct name
*/
const char *get_struct_name(const uint8_t *const struct_ptr, uint8_t *const length) {
if (struct_ptr == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
return (char *) get_string_in_mem(struct_ptr, length);
}
/**
* Get struct fields from a given struct
*
* @param[in] struct_ptr given struct
* @param[out] length number of fields
* @return struct name
*/
const uint8_t *get_struct_fields_array(const uint8_t *const struct_ptr, uint8_t *const length) {
const void *ptr;
uint8_t name_length;
if (struct_ptr == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
ptr = struct_ptr;
get_struct_name(struct_ptr, &name_length);
ptr += (sizeof(name_length) + name_length); // skip length
return get_array_in_mem(ptr, length);
}
/**
* Get next struct from a given struct
*
* @param[in] struct_ptr given struct
* @return pointer to next struct
*/
const uint8_t *get_next_struct(const uint8_t *const struct_ptr) {
uint8_t fields_count;
const void *ptr;
if (struct_ptr == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
ptr = get_struct_fields_array(struct_ptr, &fields_count);
while (fields_count-- > 0) {
ptr = get_next_struct_field(ptr);
}
return ptr;
}
/**
* Get structs array
*
* @param[out] length number of structs
* @return pointer to the first struct
*/
const uint8_t *get_structs_array(uint8_t *const length) {
return get_array_in_mem(typed_data->structs_array, length);
}
/**
* Find struct with a given name
*
* @param[in] name struct name
* @param[in] length name length
* @return pointer to struct
*/
const uint8_t *get_structn(const char *const name, const uint8_t length) {
uint8_t structs_count;
const uint8_t *struct_ptr;
const char *struct_name;
uint8_t name_length;
if (name == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
struct_ptr = get_structs_array(&structs_count);
while (structs_count-- > 0) {
struct_name = get_struct_name(struct_ptr, &name_length);
if ((length == name_length) && (memcmp(name, struct_name, length) == 0)) {
return struct_ptr;
}
struct_ptr = get_next_struct(struct_ptr);
}
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return NULL;
}
/**
* Set struct name
*
* @param[in] length name length
* @param[in] name name
* @return whether it was successful
*/
bool set_struct_name(uint8_t length, const uint8_t *const name) {
uint8_t *length_ptr;
char *name_ptr;
if ((name == NULL) || (typed_data == NULL)) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
// increment number of structs
if ((*(typed_data->structs_array) += 1) == 0) {
PRINTF("EIP712 Structs count overflow!\n");
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
// copy length
if ((length_ptr = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*length_ptr = length;
// copy name
if ((name_ptr = mem_alloc(sizeof(char) * length)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
memmove(name_ptr, name, length);
// initialize number of fields
if ((typed_data->current_struct_fields_array = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*(typed_data->current_struct_fields_array) = 0;
struct_state = INITIALIZED;
return true;
}
/**
* Set struct field TypeDesc
*
* @param[in] data the field data
* @param[in] data_idx the data index
* @return pointer to the TypeDesc in memory
*/
static const typedesc_t *set_struct_field_typedesc(const uint8_t *const data,
uint8_t *data_idx,
uint8_t length) {
typedesc_t *typedesc_ptr;
// copy TypeDesc
if ((*data_idx + sizeof(*typedesc_ptr)) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return NULL;
}
if ((typedesc_ptr = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return NULL;
}
*typedesc_ptr = data[(*data_idx)++];
return typedesc_ptr;
}
/**
* Set struct field custom typename
*
* @param[in] data the field data
* @param[in] data_idx the data index
* @return whether it was successful
*/
static bool set_struct_field_custom_typename(const uint8_t *const data,
uint8_t *data_idx,
uint8_t length) {
uint8_t *typename_len_ptr;
char *typename;
// copy custom struct name length
if ((*data_idx + sizeof(*typename_len_ptr)) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((typename_len_ptr = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*typename_len_ptr = data[(*data_idx)++];
// copy name
if ((*data_idx + *typename_len_ptr) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((typename = mem_alloc(sizeof(char) * *typename_len_ptr)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
memmove(typename, &data[*data_idx], *typename_len_ptr);
*data_idx += *typename_len_ptr;
return true;
}
/**
* Set struct field's array levels
*
* @param[in] data the field data
* @param[in] data_idx the data index
* @return whether it was successful
*/
static bool set_struct_field_array(const uint8_t *const data, uint8_t *data_idx, uint8_t length) {
uint8_t *array_levels_count;
uint8_t *array_level;
uint8_t *array_level_size;
if ((*data_idx + sizeof(*array_levels_count)) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((array_levels_count = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*array_levels_count = data[(*data_idx)++];
for (int idx = 0; idx < *array_levels_count; ++idx) {
if ((*data_idx + sizeof(*array_level)) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((array_level = mem_alloc(sizeof(*array_level))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*array_level = data[(*data_idx)++];
if (*array_level >= ARRAY_TYPES_COUNT) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
switch (*array_level) {
case ARRAY_DYNAMIC: // nothing to do
break;
case ARRAY_FIXED_SIZE:
if ((*data_idx + sizeof(*array_level_size)) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((array_level_size = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*array_level_size = data[(*data_idx)++];
break;
default:
// should not be in here :^)
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
}
return true;
}
/**
* Set struct field's type size
*
* @param[in] data the field data
* @param[in,out] data_idx the data index
* @return whether it was successful
*/
static bool set_struct_field_typesize(const uint8_t *const data,
uint8_t *data_idx,
uint8_t length) {
uint8_t *typesize_ptr;
// copy TypeSize
if ((*data_idx + sizeof(*typesize_ptr)) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((typesize_ptr = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*typesize_ptr = data[(*data_idx)++];
return true;
}
/**
* Set struct field's key name
*
* @param[in] data the field data
* @param[in,out] data_idx the data index
* @return whether it was successful
*/
static bool set_struct_field_keyname(const uint8_t *const data, uint8_t *data_idx, uint8_t length) {
uint8_t *keyname_len_ptr;
char *keyname_ptr;
// copy length
if ((*data_idx + sizeof(*keyname_len_ptr)) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((keyname_len_ptr = mem_alloc(sizeof(uint8_t))) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
*keyname_len_ptr = data[(*data_idx)++];
// copy name
if ((*data_idx + *keyname_len_ptr) > length) // check buffer bound
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if ((keyname_ptr = mem_alloc(sizeof(char) * *keyname_len_ptr)) == NULL) {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
return false;
}
memmove(keyname_ptr, &data[*data_idx], *keyname_len_ptr);
*data_idx += *keyname_len_ptr;
return true;
}
/**
* Set struct field
*
* @param[in] length data length
* @param[in] data the field data
* @return whether it was successful
*/
bool set_struct_field(uint8_t length, const uint8_t *const data) {
const typedesc_t *typedesc_ptr;
uint8_t data_idx = 0;
if ((data == NULL) || (length == 0)) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
} else if (typed_data == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
if (struct_state == NOT_INITIALIZED) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
// increment number of struct fields
if ((*(typed_data->current_struct_fields_array) += 1) == 0) {
PRINTF("EIP712 Struct fields count overflow!\n");
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
if ((typedesc_ptr = set_struct_field_typedesc(data, &data_idx, length)) == NULL) {
return false;
}
// check TypeSize flag in TypeDesc
if (*typedesc_ptr & TYPESIZE_MASK) {
// TYPESIZE and TYPE_CUSTOM are mutually exclusive
if ((*typedesc_ptr & TYPE_MASK) == TYPE_CUSTOM) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
if (set_struct_field_typesize(data, &data_idx, length) == false) {
return false;
}
} else if ((*typedesc_ptr & TYPE_MASK) == TYPE_CUSTOM) {
if (set_struct_field_custom_typename(data, &data_idx, length) == false) {
return false;
}
}
if (*typedesc_ptr & ARRAY_MASK) {
if (set_struct_field_array(data, &data_idx, length) == false) {
return false;
}
}
if (set_struct_field_keyname(data, &data_idx, length) == false) {
return false;
}
if (data_idx != length) // check that there is no more
{
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
return true;
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,65 @@
#ifndef TYPED_DATA_H_
#define TYPED_DATA_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include <stdbool.h>
// TypeDesc masks
#define TYPE_MASK (0xF)
#define ARRAY_MASK (1 << 7)
#define TYPESIZE_MASK (1 << 6)
#define TYPENAME_ENUM (0xF)
typedef enum { ARRAY_DYNAMIC = 0, ARRAY_FIXED_SIZE, ARRAY_TYPES_COUNT } e_array_type;
typedef enum {
// contract defined struct
TYPE_CUSTOM = 0,
// native types
TYPE_SOL_INT,
TYPE_SOL_UINT,
TYPE_SOL_ADDRESS,
TYPE_SOL_BOOL,
TYPE_SOL_STRING,
TYPE_SOL_BYTES_FIX,
TYPE_SOL_BYTES_DYN,
TYPES_COUNT
} e_type;
typedef struct {
uint8_t *structs_array;
uint8_t *current_struct_fields_array;
} s_typed_data;
typedef uint8_t typedesc_t;
typedef uint8_t typesize_t;
const void *get_array_in_mem(const void *ptr, uint8_t *const array_size);
const char *get_string_in_mem(const uint8_t *ptr, uint8_t *const string_length);
bool struct_field_is_array(const uint8_t *ptr);
bool struct_field_has_typesize(const uint8_t *ptr);
e_type struct_field_type(const uint8_t *ptr);
uint8_t get_struct_field_typesize(const uint8_t *ptr);
const char *get_struct_field_custom_typename(const uint8_t *ptr, uint8_t *const length);
const char *get_struct_field_typename(const uint8_t *ptr, uint8_t *const length);
e_array_type struct_field_array_depth(const uint8_t *ptr, uint8_t *const array_size);
const uint8_t *get_next_struct_field_array_lvl(const uint8_t *const ptr);
const uint8_t *struct_field_half_skip(const uint8_t *ptr);
const uint8_t *get_struct_field_array_lvls_array(const uint8_t *const ptr, uint8_t *const length);
const char *get_struct_field_keyname(const uint8_t *ptr, uint8_t *const length);
const uint8_t *get_next_struct_field(const void *ptr);
const char *get_struct_name(const uint8_t *ptr, uint8_t *const length);
const uint8_t *get_struct_fields_array(const uint8_t *ptr, uint8_t *const length);
const uint8_t *get_next_struct(const uint8_t *ptr);
const uint8_t *get_structs_array(uint8_t *const length);
const uint8_t *get_structn(const char *const name_ptr, const uint8_t name_length);
bool set_struct_name(uint8_t length, const uint8_t *const name);
bool set_struct_field(uint8_t length, const uint8_t *const data);
bool typed_data_init(void);
void typed_data_deinit(void);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // TYPED_DATA_H_

View File

@@ -0,0 +1,548 @@
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdlib.h>
#include <stdbool.h>
#include "ui_logic.h"
#include "mem.h"
#include "mem_utils.h"
#include "os_io.h"
#include "shared_context.h"
#include "ethUtils.h" // getEthDisplayableAddress
#include "utils.h" // uint256_to_decimal
#include "common_712.h"
#include "context_712.h" // eip712_context_deinit
#include "uint256.h" // tostring256 && tostring256_signed
#include "path.h" // path_get_root_type
#include "apdu_constants.h" // APDU response codes
#include "typed_data.h"
#include "commands_712.h"
#include "common_ui.h"
static t_ui_context *ui_ctx = NULL;
/**
* Checks on the UI context to determine if the next EIP 712 field should be shown
*
* @return whether the next field should be shown
*/
static bool ui_712_field_shown(void) {
bool ret = false;
if (ui_ctx->filtering_mode == EIP712_FILTERING_BASIC) {
if (N_storage.verbose_eip712 || (path_get_root_type() == ROOT_DOMAIN)) {
ret = true;
}
} else // EIP712_FILTERING_FULL
{
if (ui_ctx->field_flags & UI_712_FIELD_SHOWN) {
ret = true;
}
}
return ret;
}
/**
* Set UI buffer
*
* @param[in] src source buffer
* @param[in] src_length source buffer size
* @param[in] dst destination buffer
* @param[in] dst_length destination buffer length
* @param[in] explicit_trunc if truncation should be explicitely shown
*/
static void ui_712_set_buf(const char *const src,
size_t src_length,
char *const dst,
size_t dst_length,
bool explicit_trunc) {
uint8_t cpy_length;
if (src_length < dst_length) {
cpy_length = src_length;
} else {
cpy_length = dst_length - 1;
}
memcpy(dst, src, cpy_length);
dst[cpy_length] = '\0';
if (explicit_trunc && (cpy_length < src_length)) {
memcpy(dst + cpy_length - 3, "...", 3);
}
}
/**
* Skip the field if needed and reset its UI flags
*/
void ui_712_finalize_field(void) {
if (!ui_712_field_shown()) {
ui_712_next_field();
}
ui_712_field_flags_reset();
}
/**
* Set a new title for the EIP-712 generic UX_STEP
*
* @param[in] str the new title
* @param[in] length its length
*/
void ui_712_set_title(const char *const str, uint8_t length) {
ui_712_set_buf(str, length, strings.tmp.tmp2, sizeof(strings.tmp.tmp2), false);
}
/**
* Set a new value for the EIP-712 generic UX_STEP
*
* @param[in] str the new value
* @param[in] length its length
*/
void ui_712_set_value(const char *const str, uint8_t length) {
ui_712_set_buf(str, length, strings.tmp.tmp, sizeof(strings.tmp.tmp), true);
}
/**
* Redraw the dynamic UI step that shows EIP712 information
*/
void ui_712_redraw_generic_step(void) {
if (!ui_ctx->shown) // Initialize if it is not already
{
ui_712_start();
ui_ctx->shown = true;
} else {
ui_712_switch_to_message();
}
}
/**
* Called to fetch the next field if they have not all been processed yet
*
* @return whether there will be a next field
*/
bool ui_712_next_field(void) {
bool next = false;
if (ui_ctx == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
} else {
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) {
handle_eip712_return_code(true);
next = true;
}
}
return next;
}
/**
* Used to notify of a new struct to review
*
* @param[in] struct_ptr pointer to the structure to be shown
*/
void ui_712_review_struct(const void *const struct_ptr) {
const char *struct_name;
uint8_t struct_name_length;
const char *const title = "Review struct";
if (ui_ctx == NULL) {
return;
}
ui_712_set_title(title, strlen(title));
if ((struct_name = get_struct_name(struct_ptr, &struct_name_length)) != NULL) {
ui_712_set_value(struct_name, struct_name_length);
}
ui_712_redraw_generic_step();
}
/**
* Show the hash of the message on the generic UI step
*/
void ui_712_message_hash(void) {
const char *const title = "Message hash";
ui_712_set_title(title, strlen(title));
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"0x%.*H",
KECCAK256_HASH_BYTESIZE,
tmpCtx.messageSigningContext712.messageHash);
ui_712_redraw_generic_step();
}
/**
* Format a given data as a string
*
* @param[in] data the data that needs formatting
* @param[in] length its length
*/
static void ui_712_format_str(const uint8_t *const data, uint8_t length) {
if (ui_712_field_shown()) {
ui_712_set_value((char *) data, length);
}
}
/**
* Format a given data as a string representation of an address
*
* @param[in] data the data that needs formatting
* @param[in] length its length
* @return if the formatting was successful
*/
static bool ui_712_format_addr(const uint8_t *const data, uint8_t length) {
if (length != ADDRESS_LENGTH) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
if (ui_712_field_shown()) {
getEthDisplayableAddress((uint8_t *) data,
strings.tmp.tmp,
sizeof(strings.tmp.tmp),
&global_sha3,
chainConfig->chainId);
}
return true;
}
/**
* Format given data as a string representation of a boolean
*
* @param[in] data the data that needs formatting
* @param[in] length its length
* @return if the formatting was successful
*/
static bool ui_712_format_bool(const uint8_t *const data, uint8_t length) {
const char *const true_str = "true";
const char *const false_str = "false";
const char *str;
if (length != 1) {
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
str = *data ? true_str : false_str;
if (ui_712_field_shown()) {
ui_712_set_value(str, strlen(str));
}
return true;
}
/**
* Format given data as a string representation of bytes
*
* @param[in] data the data that needs formatting
* @param[in] length its length
*/
static void ui_712_format_bytes(const uint8_t *const data, uint8_t length) {
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "0x%.*H", length, data);
// +2 for the "0x"
// x2 for each byte value is represented by 2 ASCII characters
if ((2 + (length * 2)) > (sizeof(strings.tmp.tmp) - 1)) {
strings.tmp.tmp[sizeof(strings.tmp.tmp) - 1 - 3] = '\0';
strcat(strings.tmp.tmp, "...");
}
}
}
/**
* Format given data as a string representation of an integer
*
* @param[in] data the data that needs formatting
* @param[in] length its length
* @return if the formatting was successful
*/
static bool ui_712_format_int(const uint8_t *const data,
uint8_t length,
const void *const field_ptr) {
uint256_t value256;
uint128_t value128;
int32_t value32;
int16_t value16;
switch (get_struct_field_typesize(field_ptr) * 8) {
case 256:
convertUint256BE(data, length, &value256);
if (ui_712_field_shown()) {
tostring256_signed(&value256, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
break;
case 128:
convertUint128BE(data, length, &value128);
if (ui_712_field_shown()) {
tostring128_signed(&value128, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
break;
case 64:
convertUint64BEto128(data, length, &value128);
if (ui_712_field_shown()) {
tostring128_signed(&value128, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
break;
case 32:
value32 = 0;
for (int i = 0; i < length; ++i) {
((uint8_t *) &value32)[length - 1 - i] = data[i];
}
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "%d", value32);
}
break;
case 16:
value16 = 0;
for (int i = 0; i < length; ++i) {
((uint8_t *) &value16)[length - 1 - i] = data[i];
}
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"%d",
value16); // expanded to 32 bits
}
break;
case 8:
if (ui_712_field_shown()) {
snprintf(strings.tmp.tmp,
sizeof(strings.tmp.tmp),
"%d",
((int8_t *) data)[0]); // expanded to 32 bits
}
break;
default:
PRINTF("Unhandled field typesize\n");
apdu_response_code = APDU_RESPONSE_INVALID_DATA;
return false;
}
return true;
}
/**
* Format given data as a string representation of an unsigned integer
*
* @param[in] data the data that needs formatting
* @param[in] length its length
*/
static void ui_712_format_uint(const uint8_t *const data, uint8_t length) {
uint256_t value256;
convertUint256BE(data, length, &value256);
if (ui_712_field_shown()) {
tostring256(&value256, 10, strings.tmp.tmp, sizeof(strings.tmp.tmp));
}
}
/**
* Used to notify of a new field to review in the current struct (key + value)
*
* @param[in] field_ptr pointer to the new struct field
* @param[in] data pointer to the field's raw value
* @param[in] length field's raw value byte-length
*/
bool ui_712_new_field(const void *const field_ptr, const uint8_t *const data, uint8_t length) {
const char *key;
uint8_t key_len;
if (ui_ctx == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
// Key
if ((key = get_struct_field_keyname(field_ptr, &key_len)) == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return false;
}
if (ui_712_field_shown() && !(ui_ctx->field_flags & UI_712_FIELD_NAME_PROVIDED)) {
ui_712_set_title(key, key_len);
}
// Value
switch (struct_field_type(field_ptr)) {
case TYPE_SOL_STRING:
ui_712_format_str(data, length);
break;
case TYPE_SOL_ADDRESS:
if (ui_712_format_addr(data, length) == false) {
return false;
}
break;
case TYPE_SOL_BOOL:
if (ui_712_format_bool(data, length) == false) {
return false;
}
break;
case TYPE_SOL_BYTES_FIX:
case TYPE_SOL_BYTES_DYN:
ui_712_format_bytes(data, length);
break;
case TYPE_SOL_INT:
if (ui_712_format_int(data, length, field_ptr) == false) {
return false;
}
break;
case TYPE_SOL_UINT:
ui_712_format_uint(data, length);
break;
default:
PRINTF("Unhandled type\n");
return false;
}
// Check if this field is supposed to be displayed
if (ui_712_field_shown()) {
ui_712_redraw_generic_step();
}
return true;
}
/**
* Used to signal that we are done with reviewing the structs and we can now have
* the option to approve or reject the signature
*/
void ui_712_end_sign(void) {
if (ui_ctx == NULL) {
apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED;
return;
}
ui_ctx->end_reached = true;
if (N_storage.verbose_eip712 || (ui_ctx->filtering_mode == EIP712_FILTERING_FULL)) {
ui_712_switch_to_sign();
}
}
/**
* Initializes the UI context structure in memory
*/
bool ui_712_init(void) {
if ((ui_ctx = MEM_ALLOC_AND_ALIGN_TYPE(*ui_ctx))) {
ui_ctx->shown = false;
ui_ctx->end_reached = false;
ui_ctx->filtering_mode = EIP712_FILTERING_BASIC;
} else {
apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY;
}
return ui_ctx != NULL;
}
/**
* Deinit function that simply unsets the struct pointer to NULL
*/
void ui_712_deinit(void) {
ui_ctx = NULL;
}
/**
* Approve button handling, calls the common handler function then
* deinitializes the EIP712 context altogether.
* @param[in] e unused here, just needed to match the UI function signature
* @return unused here, just needed to match the UI function signature
*/
unsigned int ui_712_approve() {
ui_712_approve_cb();
eip712_context_deinit();
return 0;
}
/**
* Reject button handling, calls the common handler function then
* deinitializes the EIP712 context altogether.
* @param[in] e unused here, just needed to match the UI function signature
* @return unused here, just needed to match the UI function signature
*/
unsigned int ui_712_reject() {
ui_712_reject_cb();
eip712_context_deinit();
return 0;
}
/**
* Set a structure field's UI flags
*
* @param[in] show if this field should be shown on the device
* @param[in] name_provided if a substitution name has been provided
*/
void ui_712_flag_field(bool show, bool name_provided) {
if (show) {
ui_ctx->field_flags |= UI_712_FIELD_SHOWN;
}
if (name_provided) {
ui_ctx->field_flags |= UI_712_FIELD_NAME_PROVIDED;
}
}
/**
* Set the UI filtering mode
*
* @param[in] the new filtering mode
*/
void ui_712_set_filtering_mode(e_eip712_filtering_mode mode) {
ui_ctx->filtering_mode = mode;
}
/**
* Get the UI filtering mode
*
* @return current filtering mode
*/
e_eip712_filtering_mode ui_712_get_filtering_mode(void) {
return ui_ctx->filtering_mode;
}
/**
* Set the number of filters this message should process
*
* @param[in] count number of filters
*/
void ui_712_set_filters_count(uint8_t count) {
ui_ctx->filters_to_process = count;
}
/**
* Get the number of filters left to process
*
* @return number of filters
*/
uint8_t ui_712_remaining_filters(void) {
return ui_ctx->filters_to_process;
}
/**
* Reset all the UI struct field flags
*/
void ui_712_field_flags_reset(void) {
ui_ctx->field_flags = 0;
}
/**
* Add a struct to the UI review queue
*
* Makes it so the user will have to go through a "Review struct" screen
*/
void ui_712_queue_struct_to_review(void) {
if (N_storage.verbose_eip712) {
ui_ctx->structs_to_review += 1;
}
}
/**
* Notify of a filter change from a path advance
*
* This function figures out by itself if there is anything to do
*/
void ui_712_notify_filter_change(void) {
if (path_get_root_type() == ROOT_MESSAGE) {
if (ui_ctx->filtering_mode == EIP712_FILTERING_FULL) {
if (ui_ctx->filters_to_process > 0) {
if (ui_ctx->field_flags & UI_712_FIELD_SHOWN) {
ui_ctx->filters_to_process -= 1;
}
}
}
}
}
#endif // HAVE_EIP712_FULL_SUPPORT

View File

@@ -0,0 +1,47 @@
#ifndef UI_LOGIC_712_H_
#define UI_LOGIC_712_H_
#ifdef HAVE_EIP712_FULL_SUPPORT
#include <stdint.h>
#include "ux.h"
#define UI_712_FIELD_SHOWN (1 << 0)
#define UI_712_FIELD_NAME_PROVIDED (1 << 1)
typedef enum { EIP712_FILTERING_BASIC, EIP712_FILTERING_FULL } e_eip712_filtering_mode;
typedef struct {
bool shown;
bool end_reached;
uint8_t filtering_mode;
uint8_t filters_to_process;
uint8_t field_flags;
uint8_t structs_to_review;
} t_ui_context;
bool ui_712_init(void);
void ui_712_deinit(void);
bool 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);
unsigned int ui_712_approve();
unsigned int ui_712_reject();
void ui_712_set_title(const char *const str, uint8_t length);
void ui_712_set_value(const char *const str, uint8_t length);
void ui_712_message_hash(void);
void ui_712_redraw_generic_step(void);
void ui_712_flag_field(bool show, bool name_provided);
void ui_712_field_flags_reset(void);
void ui_712_finalize_field(void);
void ui_712_set_filtering_mode(e_eip712_filtering_mode mode);
e_eip712_filtering_mode ui_712_get_filtering_mode(void);
void ui_712_set_filters_count(uint8_t count);
uint8_t ui_712_remaining_filters(void);
void ui_712_queue_struct_to_review(void);
void ui_712_notify_filter_change(void);
#endif // HAVE_EIP712_FULL_SUPPORT
#endif // UI_LOGIC_712_H_

View File

@@ -1,16 +1,19 @@
#include "os_io_seproxyhal.h"
#include "shared_context.h"
#include "os_io_seproxyhal.h"
#include "ui_callbacks.h"
#include "common_712.h"
#include "ui_callbacks.h"
#include "common_ui.h"
static const uint8_t EIP_712_MAGIC[] = {0x19, 0x01};
unsigned int io_seproxyhal_touch_signMessage712_v0_ok(__attribute__((unused))
const bagl_element_t *e) {
unsigned int ui_712_approve_cb() {
uint8_t privateKeyData[INT256_LENGTH];
uint8_t hash[INT256_LENGTH];
uint8_t signature[100];
cx_ecfp_private_key_t privateKey;
uint32_t tx = 0;
io_seproxyhal_io_heartbeat();
cx_keccak_init(&global_sha3, 256);
cx_hash((cx_hash_t *) &global_sha3,
@@ -31,7 +34,8 @@ unsigned int io_seproxyhal_touch_signMessage712_v0_ok(__attribute__((unused))
sizeof(tmpCtx.messageSigningContext712.messageHash),
hash,
sizeof(hash));
PRINTF("EIP712 hash to sign %.*H\n", 32, hash);
PRINTF("EIP712 Domain hash 0x%.*h\n", 32, tmpCtx.messageSigningContext712.domainHash);
PRINTF("EIP712 Message hash 0x%.*h\n", 32, tmpCtx.messageSigningContext712.messageHash);
io_seproxyhal_io_heartbeat();
os_perso_derive_node_bip32(CX_CURVE_256K1,
tmpCtx.messageSigningContext712.bip32.path,
@@ -71,8 +75,7 @@ unsigned int io_seproxyhal_touch_signMessage712_v0_ok(__attribute__((unused))
return 0; // do not redraw the widget
}
unsigned int io_seproxyhal_touch_signMessage712_v0_cancel(__attribute__((unused))
const bagl_element_t *e) {
unsigned int ui_712_reject_cb() {
reset_app_context();
G_io_apdu_buffer[0] = 0x69;
G_io_apdu_buffer[1] = 0x85;

View File

@@ -0,0 +1,10 @@
#ifndef COMMON_EIP712_H_
#define COMMON_EIP712_H_
#include <stdint.h>
#include "ux.h"
unsigned int ui_712_approve_cb();
unsigned int ui_712_reject_cb();
#endif // COMMON_EIP712_H_

View File

@@ -0,0 +1,40 @@
#include "shared_context.h"
#include "apdu_constants.h"
#include "utils.h"
#include "ui_flow.h"
#include "common_712.h"
#include "ethUtils.h"
void handleSignEIP712Message_v0(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
(void) tx;
(void) p2;
if (p1 != 00) {
THROW(APDU_RESPONSE_INVALID_P1_P2);
}
if (appState != APP_STATE_IDLE) {
reset_app_context();
}
workBuffer = parseBip32(workBuffer, &dataLength, &tmpCtx.messageSigningContext.bip32);
if ((workBuffer == NULL) || (dataLength < (KECCAK256_HASH_BYTESIZE * 2))) {
THROW(APDU_RESPONSE_INVALID_DATA);
}
memmove(tmpCtx.messageSigningContext712.domainHash, workBuffer, KECCAK256_HASH_BYTESIZE);
memmove(tmpCtx.messageSigningContext712.messageHash,
workBuffer + KECCAK256_HASH_BYTESIZE,
KECCAK256_HASH_BYTESIZE);
#ifdef NO_CONSENT
io_seproxyhal_touch_signMessage_ok(NULL);
#else // NO_CONSENT
ux_flow_init(0, ux_sign_712_v0_flow, NULL);
#endif // NO_CONSENT
*flags |= IO_ASYNCH_REPLY;
}

View File

@@ -6,7 +6,7 @@
void handleSign(uint8_t p1,
uint8_t p2,
const uint8_t *workBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(tx);

View File

@@ -10,7 +10,7 @@
void handleStarkwareGetPublicKey(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
unsigned int *tx) {
bip32_path_t bip32;

View File

@@ -8,7 +8,7 @@
void handleStarkwareProvideQuantum(uint8_t p1,
__attribute__((unused)) uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
__attribute__((unused)) unsigned int *flags,
__attribute__((unused)) unsigned int *tx) {
size_t i = 0;

View File

@@ -15,7 +15,7 @@
void handleStarkwareSignMessage(uint8_t p1,
uint8_t p2,
uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
__attribute__((unused)) unsigned int *tx) {
uint8_t privateKeyData[INT256_LENGTH];

View File

@@ -9,7 +9,7 @@
void handleStarkwareUnsafeSign(uint8_t p1,
uint8_t p2,
const uint8_t *dataBuffer,
uint16_t dataLength,
uint8_t dataLength,
unsigned int *flags,
__attribute__((unused)) unsigned int *tx) {
uint8_t privateKeyData[INT256_LENGTH];

2
tests/ragger/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
venv/
__pycache__/

68
tests/ragger/conftest.py Normal file
View File

@@ -0,0 +1,68 @@
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
# 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)

View File

@@ -0,0 +1,362 @@
#!/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
# global variables
app_client: EthereumClient = None
filtering_paths = None
current_path = list()
sig_ctx = {}
# From a string typename, extract the type and all the array depth
# Input = "uint8[2][][4]" | "bool"
# Output = ('uint8', [2, None, 4]) | ('bool', [])
def get_array_levels(typename):
array_lvls = list()
regex = re.compile("(.*)\[([0-9]*)\]$")
while True:
result = regex.search(typename)
if not result:
break
typename = result.group(1)
level_size = result.group(2)
if len(level_size) == 0:
level_size = None
else:
level_size = int(level_size)
array_lvls.insert(0, level_size)
return (typename, array_lvls)
# From a string typename, extract the type and its size
# Input = "uint64" | "string"
# Output = ('uint', 64) | ('string', None)
def get_typesize(typename):
regex = re.compile("^(\w+?)(\d*)$")
result = regex.search(typename)
typename = result.group(1)
typesize = result.group(2)
if len(typesize) == 0:
typesize = None
else:
typesize = int(typesize)
return (typename, typesize)
def parse_int(typesize):
return (EIP712FieldType.INT, int(typesize / 8))
def parse_uint(typesize):
return (EIP712FieldType.UINT, int(typesize / 8))
def parse_address(typesize):
return (EIP712FieldType.ADDRESS, None)
def parse_bool(typesize):
return (EIP712FieldType.BOOL, None)
def parse_string(typesize):
return (EIP712FieldType.STRING, None)
def parse_bytes(typesize):
if typesize != None:
return (EIP712FieldType.FIX_BYTES, typesize)
return (EIP712FieldType.DYN_BYTES, None)
# set functions for each type
parsing_type_functions = {};
parsing_type_functions["int"] = parse_int
parsing_type_functions["uint"] = parse_uint
parsing_type_functions["address"] = parse_address
parsing_type_functions["bool"] = parse_bool
parsing_type_functions["string"] = parse_string
parsing_type_functions["bytes"] = parse_bytes
def send_struct_def_field(typename, keyname):
type_enum = None
(typename, array_lvls) = get_array_levels(typename)
(typename, typesize) = get_typesize(typename)
if typename in parsing_type_functions.keys():
(type_enum, typesize) = parsing_type_functions[typename](typesize)
else:
type_enum = EIP712FieldType.CUSTOM
typesize = None
app_client.eip712_send_struct_def_struct_field(type_enum,
typename,
typesize,
array_lvls,
keyname)
return (typename, type_enum, typesize, array_lvls)
def encode_integer(value, typesize):
data = bytearray()
# Some are already represented as integers in the JSON, but most as strings
if isinstance(value, str):
base = 10
if value.startswith("0x"):
base = 16
value = int(value, base)
if value == 0:
data.append(0)
else:
if value < 0: # negative number, send it as unsigned
mask = 0
for i in range(typesize): # make a mask as big as the typesize
mask = (mask << 8) | 0xff
value &= mask
while value > 0:
data.append(value & 0xff)
value >>= 8
data.reverse()
return data
def encode_int(value, typesize):
return encode_integer(value, typesize)
def encode_uint(value, typesize):
return encode_integer(value, typesize)
def encode_hex_string(value, size):
data = bytearray()
value = value[2:] # skip 0x
byte_idx = 0
while byte_idx < size:
data.append(int(value[(byte_idx * 2):(byte_idx * 2 + 2)], 16))
byte_idx += 1
return data
def encode_address(value, typesize):
return encode_hex_string(value, 20)
def encode_bool(value, typesize):
return encode_integer(value, typesize)
def encode_string(value, typesize):
data = bytearray()
for char in value:
data.append(ord(char))
return data
def encode_bytes_fix(value, typesize):
return encode_hex_string(value, typesize)
def encode_bytes_dyn(value, typesize):
# length of the value string
# - the length of 0x (2)
# / by the length of one byte in a hex string (2)
return encode_hex_string(value, int((len(value) - 2) / 2))
# set functions for each type
encoding_functions = {}
encoding_functions[EIP712FieldType.INT] = encode_int
encoding_functions[EIP712FieldType.UINT] = encode_uint
encoding_functions[EIP712FieldType.ADDRESS] = encode_address
encoding_functions[EIP712FieldType.BOOL] = encode_bool
encoding_functions[EIP712FieldType.STRING] = encode_string
encoding_functions[EIP712FieldType.FIX_BYTES] = encode_bytes_fix
encoding_functions[EIP712FieldType.DYN_BYTES] = encode_bytes_dyn
def send_struct_impl_field(value, field):
# Something wrong happened if this triggers
if isinstance(value, list) or (field["enum"] == EIP712FieldType.CUSTOM):
breakpoint()
data = encoding_functions[field["enum"]](value, field["typesize"])
if filtering_paths:
path = ".".join(current_path)
if path in filtering_paths.keys():
send_filtering_show_field(filtering_paths[path])
app_client.eip712_send_struct_impl_struct_field(data)
def evaluate_field(structs, data, field, lvls_left, new_level = True):
array_lvls = field["array_lvls"]
if new_level:
current_path.append(field["name"])
if len(array_lvls) > 0 and lvls_left > 0:
app_client.eip712_send_struct_impl_array(len(data))
idx = 0
for subdata in data:
current_path.append("[]")
if not evaluate_field(structs, subdata, field, lvls_left - 1, False):
return False
current_path.pop()
idx += 1
if array_lvls[lvls_left - 1] != None:
if array_lvls[lvls_left - 1] != idx:
print("Mismatch in array size! Got %d, expected %d\n" %
(idx, array_lvls[lvls_left - 1]),
file=sys.stderr)
return False
else:
if field["enum"] == EIP712FieldType.CUSTOM:
if not send_struct_impl(structs, data, field["type"]):
return False
else:
send_struct_impl_field(data, field)
if new_level:
current_path.pop()
return True
def send_struct_impl(structs, data, structname):
# Check if it is a struct we don't known
if structname not in structs.keys():
return False
struct = structs[structname]
for f in struct:
if not evaluate_field(structs, data[f["name"]], f, len(f["array_lvls"])):
return False
return True
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
def send_filtering_message_info(display_name: str, filters_count: int):
global sig_ctx
to_sign = bytearray()
to_sign.append(183)
to_sign += sig_ctx["chainid"]
to_sign += sig_ctx["caddr"]
to_sign += sig_ctx["schema_hash"]
to_sign.append(filters_count)
for char in display_name:
to_sign.append(ord(char))
sig = sig_ctx["key"].sign_deterministic(to_sign, sigencode=sigencode_der)
app_client.eip712_filtering_message_info(display_name, filters_count, sig)
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
def send_filtering_show_field(display_name):
global sig_ctx
path_str = ".".join(current_path)
to_sign = bytearray()
to_sign.append(72)
to_sign += sig_ctx["chainid"]
to_sign += sig_ctx["caddr"]
to_sign += sig_ctx["schema_hash"]
for char in path_str:
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)
app_client.eip712_filtering_show_field(display_name, sig)
def read_filtering_file(domain, message, filtering_file_path):
data_json = None
with open(filtering_file_path) as data:
data_json = json.load(data)
return data_json
def prepare_filtering(filtr_data, message):
global filtering_paths
if "fields" in filtr_data:
filtering_paths = filtr_data["fields"]
else:
filtering_paths = {}
def handle_optional_domain_values(domain):
if "chainId" not in domain.keys():
domain["chainId"] = 0
if "verifyingContract" not in domain.keys():
domain["verifyingContract"] = "0x0000000000000000000000000000000000000000"
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:]
sig_ctx["caddr"] = bytearray.fromhex(caddr)
chainid = domain["chainId"]
sig_ctx["chainid"] = bytearray()
for i in range(8):
sig_ctx["chainid"].append(chainid & (0xff << (i * 8)))
sig_ctx["chainid"].reverse()
schema_str = json.dumps(types).replace(" ","")
schema_hash = hashlib.sha224(schema_str.encode())
sig_ctx["schema_hash"] = bytearray.fromhex(schema_hash.hexdigest())
def process_file(aclient: EthereumClient, input_file_path: str, filtering_file_path = None) -> bool:
global sig_ctx
global app_client
app_client = aclient
with open(input_file_path, "r") as data:
data_json = json.load(data)
domain_typename = "EIP712Domain"
message_typename = data_json["primaryType"]
types = data_json["types"]
domain = data_json["domain"]
message = data_json["message"]
if filtering_file_path:
init_signature_context(types, domain)
filtr = read_filtering_file(domain, message, filtering_file_path)
# send types definition
for key in types.keys():
app_client.eip712_send_struct_def_struct_name(key)
for f in types[key]:
(f["type"], f["enum"], f["typesize"], f["array_lvls"]) = \
send_struct_def_field(f["type"], f["name"])
if filtering_file_path:
app_client.eip712_filtering_activate()
prepare_filtering(filtr, message)
# send domain implementation
app_client.eip712_send_struct_impl_root_struct(domain_typename)
if not send_struct_impl(types, domain, domain_typename):
return False
if filtering_file_path:
if filtr and "name" in filtr:
send_filtering_message_info(filtr["name"], len(filtering_paths))
else:
send_filtering_message_info(domain["name"], len(filtering_paths))
# send message implementation
app_client.eip712_send_struct_impl_root_struct(message_typename)
if not send_struct_impl(types, message, message_typename):
return False
return True

View File

View File

@@ -0,0 +1,44 @@
{
"domain": {
"chainId": 1,
"name": "Simple Mail",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
"version": "1"
},
"message": {
"contents": "Hello, Bob!",
"from": {
"name": "Cow",
"wallets": [
"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"
]
},
"to": {
"name": "Bob",
"wallets": [
"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
"0xB0B0b0b0b0b0B000000000000000000000000000"
]
}
},
"primaryType": "Mail",
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallets", "type": "address[]" }
]
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "Test JSON",
"fields": {
"from.name": "From",
"to.name" : "To"
}
}

View File

@@ -0,0 +1,4 @@
[signature]
v = 1b
r = 23599abd6c4b631e42770c112b5955907fe91339f1ea1e102f7682262ca178b9
s = 29fc94518588165114b4c4acb4d73e6d028dfb051d90e517b3b4746e04eb0f5f

View File

@@ -0,0 +1,31 @@
{
"domain": {
"chainId": 1,
"name": "Addresses Array Mail",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
"version": "1"
},
"message": {
"contents": "Hello, Bob!",
"from": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
"to": [
"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
"0xb1a22cc48f6784f629a994917cd6474923630c48"
],
"id": 7
},
"primaryType": "Mail",
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "address" },
{ "name": "to", "type": "address[]" },
{ "name": "contents", "type": "string" }
]
}
}

View File

@@ -0,0 +1,4 @@
[signature]
v = 1c
r = 3f084a471e6158bce792287500d62d40061acc1864180ed2da7a704bf3aced0f
s = 3b799ced9e48cda152b4b9a4b7f45e3119dc7acdf16710a73800b4e336fa1b40

View File

@@ -0,0 +1,55 @@
{
"domain": {
"chainId": 1,
"name": "Recipients Array Mail",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
"version": "1"
},
"message": {
"contents": "Hello, Bob!",
"from": {
"name": "Cow",
"wallets": [
"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"
]
},
"to": [
{
"name": "Alice",
"wallets": [
"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"0xB0B0b0b0b0b0B000000000000000000000000000"
]
},
{
"name": "Bob",
"wallets": [
"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57"
]
}
]
},
"primaryType": "Mail",
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Group": [
{ "name": "name", "type": "string" },
{ "name": "members", "type": "Person[]" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person[]" },
{ "name": "contents", "type": "string" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallets", "type": "address[]" }
]
}
}

View File

@@ -0,0 +1,4 @@
[signature]
v = 1b
r = 49dd2aa96d7494e0cd9111f19f87ac50194e4bbc61ea9f4bb86d674da0ae7721
s = 7a12ddd9083b4caaabd2fb80df6de1d5d926c0e8a73bf371a45e231d409d79d6

View File

@@ -0,0 +1,50 @@
{
"domain": {
"chainId": 1,
"name": "Long String Mail",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
"version": "1"
},
"message": {
"contents": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam fermentum interdum tortor, nec elementum enim dignissim ac. Proin at leo sit amet nisl ultrices mollis quis a nunc. Aliquam lobortis a libero non lobortis. Morbi elementum eleifend ante et malesuada. Proin eget fermentum risus. Vestibulum cursus dignissim mollis. In viverra, mi ac accumsan elementum, tellus metus dictum nisi, vel tincidunt odio erat in odio. Aenean nec lorem auctor, tempor ante eget, aliquet purus. Donec fringilla felis iaculis, venenatis ligula egestas, dignissim orci. Aliquam id rhoncus ante, cursus consectetur mauris. Nunc porttitor urna urna, et tristique eros maximus vestibulum. Curabitur pretium a est non porttitor. Cras mollis efficitur sem ut porta. Proin commodo volutpat iaculis. Maecenas ut nulla mi. Aenean ultrices sollicitudin enim, non luctus magna efficitur et. Sed varius sem odio, in sodales erat porta in. Nunc blandit finibus maximus. Mauris nunc tellus, interdum non laoreet sed, aliquet non.",
"from": {
"name": "Cow",
"wallets": [
"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"
]
},
"to": [
{
"name": "Bob",
"wallets": [
"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
"0xB0B0b0b0b0b0B000000000000000000000000000"
]
}
]
},
"primaryType": "Mail",
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Group": [
{ "name": "name", "type": "string" },
{ "name": "members", "type": "Person[]" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person[]" },
{ "name": "contents", "type": "string" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallets", "type": "address[]" }
]
}
}

View File

@@ -0,0 +1,4 @@
[signature]
v = 1c
r = b23ffac2cb350fd6e7d06ec4b981fe016d33426d753c870e7e753797cc43bb1f
s = 37948a656fa3403e21956ef10c8d3152f7ce22cc252d958c9f9249435090f426

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