Add support for ERC-721 and ERC-1155 (v3) (#218)

* First draft for erc721 token allowance

* Split ui and provide parameters into their own files

* Print txtype when not supported

* fix compilation for erc721

* Use pluginType

* Add debug statement in compound plugin

* add debug error msg in plugin error

* Add parameter parsing for all methods

* Remove debug logs

* Add SET_APPROVAL_FOR_ALL; Add correct parsing method on contract init

* Add dst_size parameter to copy functions

* Add query contract id code

* format

* Add UIs

* update ethapp.asc

* Change setExternalPlugin to setPlugin; Add support for ERC721

* clang-format

* Fix typo Unconsistent -> Inconsistent

* Add support for 721; use extraInfo

* Add extraInfo to ethpluginQueryConractUI

* Rename extraInfo to item

* Add txFromEtherscan to tests

* Add nft key and temp padding

* Remove comments around HAVE_BYPASS_SIGNATURES

* Rename TESTING_KEY to NFT_TESTING_KEY

* Add comments regarding value of queryContractUI->item

* Fix comment regarding method selector

* Rename provideToken to provideInfo; Update plugin doc

* fix caps of eth_plugin_prepare_provide_info

* fix caps of handle_provide_info

* Use verificationFn insead of hardcoded cx_ecdsa_verify

* Add comments about nftInfo_t and tokenDefinition_t

* Add erc721 test

* Remove comment from plugin interface version

* Fix network_ticker duplicate

* Add setPlugin and provideNFTInfo to doc.asc

* Add back setExternalPlugin; implement new setPlugin

* Update plugin sdk

* Call setPlugin instead of setExternalPlugin

* setPlugin work without checking sig

* Remove printf of displayed fees

* Add working 721 test

* Finalize ERC721 and add simple test

* Display NFT address on set approval and operator

* Support set approval for all for erc721

* Finish UI for set approval for all erc721

* Move copy_parameter and copy_address to eth_plugin_internal; Add tests for erc721

* update plugin sdk

* Add erc1155 plugin and 1155 tests placeholder

* Add restriction for AWS key and setPlugin

* Add NOT_OLD_INTERNAL variant; Add erc_1155_plugin_call

* Fixed compilation warnings (function pointer casting)

Co-authored-by: pscott <scott.piriou@ledger.fr>
This commit is contained in:
apaillier-ledger
2021-11-22 14:39:36 +01:00
committed by GitHub
parent a490532605
commit fcc3dd6d31
94 changed files with 2026 additions and 349 deletions

View File

@@ -300,12 +300,18 @@ ifneq ($(ALLOW_DATA),0)
DEFINES += HAVE_ALLOW_DATA
endif
# Bypass the signature verification for setExternalPlugin and provideERC20TokenInfo calls
# Bypass the signature verification for setExternalPlugin, setPlugin, provideERC20TokenInfo and provideNFTInfo calls
BYPASS_SIGNATURES:=0
ifneq ($(BYPASS_SIGNATURES),0)
DEFINES += HAVE_BYPASS_SIGNATURES
endif
# Enable the NFT testing key
NFT_TESTING_KEY:=0
ifneq ($(NFT_TESTING_KEY),0)
DEFINES += HAVE_NFT_TESTING_KEY
endif
# Enabling debug PRINTF
DEBUG:=0

View File

@@ -23,6 +23,9 @@ Application version 1.5.0 - 25th of September 2020
## 1.7.6
- Add SET EXTERNAL PLUGIN
## 1.9.13
- Add SET PLUGIN
## About
This application describes the APDU messages interface to communicate with the Ethereum application.
@@ -240,6 +243,49 @@ signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f
None
### PROVIDE NFT INFORMATION
#### Description
This commands provides a trusted description of an NFT to associate a contract address with a collectionName.
It shall be run immediately before performing a transaction involving a contract calling this contract address to display the proper nft information to the user if necessary, as marked in GET APP CONFIGURATION flags.
The signature is computed on
type || version || len(collectionName) || collectionName || address || chainId || keyId || algorithmId || len(signature) || signature
#### Coding
'Command'
[width="80%"]
|==============================================================================================================================
| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le*
| E0 | 14 | 00 | 00 | variable | 00
|==============================================================================================================================
'Input data'
[width="80%"]
|==============================================================================================================================
| *Description* | *Length*
| Type | 1
| Version | 1
| Collection Name Length | 1
| Collection Name | variable
| Address | 20
| Chain ID | 8
| KeyID | 1
| Algorithm ID | 1
| Signature Length | 1
| Signature | variable
|==============================================================================================================================
'Output data'
None
### SET EXTERNAL PLUGIN
@@ -283,6 +329,56 @@ signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f
None
### SET PLUGIN
#### Description
This commands provides the name of a trusted binding of a plugin with a contract address and a supported method selector. This plugin will be called to interpret contract data in the following transaction signing command.
It can be used to set both internal and external plugins.
It shall be run immediately before performing a transaction involving a contract supported by this plugin to display the proper information to the user if necessary.
The function returns an error sw (0x6984) if the plugin requested is not installed on the device, 0x9000 otherwise.
The plugin names `ERC20`, `ERC721` and `ERC1155` are reserved. Additional plugin names might be added to this list in the future.
The signature is computed on
type || version || len(pluginName) || pluginName || address || selector || chainId || keyId || algorithmId || len(signature) || signature
#### Coding
'Command'
[width="80%"]
|==============================================================================================================================
| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le*
| E0 | 16 | 00 | 00 | variable | 00
|==============================================================================================================================
'Input data'
[width="80%"]
|==============================================================================================================================
| *Description* | *Length*
| Type | 1
| Version | 1
| Plugin Name Length | 1
| Plugin Name | variable
| Address | 20
| Selector | 4
| Chain ID | 8
| KeyID | 1
| Algorithm ID | 1
| Signature Length | 1
| Signature | variable
|==============================================================================================================================
'Output data'
None
### GET APP CONFIGURATION
#### Description

View File

@@ -145,8 +145,8 @@ typedef struct ethPluginFinalize_t {
ethPluginSharedRO_t *pluginSharedRO;
uint8_t *pluginContext;
uint8_t *tokenLookup1; // set by the plugin if a token should be looked up
uint8_t *tokenLookup2;
uint8_t *itemLookup1; // set by the plugin if a token or an nft should be looked up
uint8_t *itemLookup2;
uint8_t *amount; // set an uint256 pointer if uiType is UI_AMOUNT_ADDRESS
uint8_t *address; // set to the destination address if uiType is UI_AMOUNT_ADDRESS. Set to the user's address if uiType is UI_TYPE_GENERIC
@@ -161,8 +161,8 @@ typedef struct ethPluginFinalize_t {
This message is sent when the data field has been fully parsed. The following specific fields can be filled by the plugin :
* tokenLookup1 : the pointer shall be set to a 20 bytes address to look up an ERC 20 token descriptor if needed by the plugin
* tokenLookup2 : the pointer shall be set to a 20 bytes address to look up an ERC 20 token descriptor if needed by the plugin
* itemLookup1 : the pointer shall be set to a 20 bytes address to look up an ERC20 token or NFT if needed by the plugin
* itemLookup2 : the pointer shall be set to a 20 bytes address to look up an ERC20 token or NFT if needed by the plugin
* uiType : set to either ETH_UI_TYPE_AMOUNT_ADDRESS for an amount/address UI or ETH_UI_TYPE_GENERIC for a generic UI
The following specific fields are filled by the plugin when returning an amount/address UI :
@@ -179,32 +179,32 @@ The following return codes are expected, any other will abort the signing proces
* ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized
* ETH_PLUGIN_RESULT_FALLBACK : if the signing logic should fallback to the generic one
### ETH_PLUGIN_PROVIDE_TOKEN
### ETH_PLUGIN_PROVIDE_INFO
[source,C]
----
typedef struct ethPluginProvideToken_t {
typedef struct ethPluginProvideInfo_t {
ethPluginSharedRW_t *pluginSharedRW;
ethPluginSharedRO_t *pluginSharedRO;
uint8_t *pluginContext;
tokenDefinition_t *token1; // set by the ETH application, to be saved by the plugin
tokenDefinition_t *token2;
union extraInfo *item1; // set by the ETH application, to be saved by the plugin
union extraInfo *item2;
uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens based on the information received from the token definitions.
uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens based on the information received.
uint8_t result;
} ethPluginProvideToken_t;
} ethPluginProvideInfo_t;
----
This message is sent if a token lookup was required by the plugin when parsing a finalize message. The following specific fields are filled when the plugin is called :
This message is sent if an information lookup was required by the plugin when parsing a finalize message. The following specific fields are filled when the plugin is called :
* token1 : pointer to a token definition matching tokenLookup1, or NULL if not found
* token2 : pointer to a token definition matching tokenLookup2, or NULL if not found or not requested
* item1 : pointer to an union matching itemLookup1, or NULL if not found
* item2 : pointer to an union matching itemLookup2, or NULL if not found
The following return codes are expected, any other will abort the signing process :
@@ -233,7 +233,7 @@ typedef struct ethQueryContractID_t {
----
This message is sent after the parsing finalization and token lookups if requested if a generic UI is used. The following specific fields are provided when the plugin is called :
This message is sent after the parsing finalization and information lookups if requested if a generic UI is used. The following specific fields are provided when the plugin is called :
* name : pointer to the name of the plugin, to be filled by the plugin
* nameLength : maximum name length
@@ -253,6 +253,9 @@ typedef struct ethQueryContractUI_t {
ethPluginSharedRW_t *pluginSharedRW;
ethPluginSharedRO_t *pluginSharedRO;
union extraInfo_t *item1;
union extraInfo_t *item2;
char network_ticker[MAX_TICKER_LEN];
uint8_t *pluginContext;
uint8_t screenIndex;
char *title;
@@ -268,6 +271,10 @@ typedef struct ethQueryContractUI_t {
This message is sent when a plugin screen shall be displayed if a generic UI is used. The following specific fields are provided when the plugin is called :
* item1 : pointer to token / nft information
* item2 : pointer to token / nft information
* network_ticker : string that holds the network ticker
* screenIndex : index of the screen to display, starting from 0
* title : pointer to the first line of the screen, to be filled by the plugin
* titleLength : maximum title length

View File

@@ -15,6 +15,8 @@
#define INS_GET_ETH2_PUBLIC_KEY 0x0E
#define INS_SET_ETH2_WITHDRAWAL_INDEX 0x10
#define INS_SET_EXTERNAL_PLUGIN 0x12
#define INS_PROVIDE_NFT_INFORMATION 0x14
#define INS_SET_PLUGIN 0x16
#define P1_CONFIRM 0x01
#define P1_NON_CONFIRM 0x00
#define P2_NO_CHAINCODE 0x00
@@ -64,6 +66,12 @@ void handleProvideErc20TokenInformation(uint8_t p1,
uint16_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleProvideNFTInformation(uint8_t p1,
uint8_t p2,
uint8_t *dataBuffer,
uint16_t dataLength,
unsigned int *flags,
unsigned int *tx);
void handleSign(uint8_t p1,
uint8_t p2,
uint8_t *dataBuffer,
@@ -96,6 +104,13 @@ void handleSetExternalPlugin(uint8_t p1,
unsigned int *flags,
unsigned int *tx);
void handleSetPlugin(uint8_t p1,
uint8_t p2,
uint8_t *workBuffer,
uint16_t dataLength,
unsigned int *flags,
unsigned int *tx);
#ifdef HAVE_ETH2
void handleGetEth2PublicKey(uint8_t p1,

View File

@@ -22,8 +22,8 @@ void eth_plugin_prepare_finalize(ethPluginFinalize_t *finalize) {
memset((uint8_t *) finalize, 0, sizeof(ethPluginFinalize_t));
}
void eth_plugin_prepare_provide_token(ethPluginProvideToken_t *provideToken) {
memset((uint8_t *) provideToken, 0, sizeof(ethPluginProvideToken_t));
void eth_plugin_prepare_provide_info(ethPluginProvideInfo_t *provideToken) {
memset((uint8_t *) provideToken, 0, sizeof(ethPluginProvideInfo_t));
}
void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID,
@@ -45,6 +45,23 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI,
char *msg,
uint32_t msgLength) {
memset((uint8_t *) queryContractUI, 0, sizeof(ethQueryContractUI_t));
// If no extra information was found, set the pointer to NULL
if (allzeroes(&tmpCtx.transactionContext.extraInfo[1], sizeof(union extraInfo_t))) {
queryContractUI->item1 = NULL;
} else {
queryContractUI->item1 = &tmpCtx.transactionContext.extraInfo[1];
}
// If no extra information was found, set the pointer to NULL
if (allzeroes(&tmpCtx.transactionContext.extraInfo[0], sizeof(union extraInfo_t))) {
queryContractUI->item2 = NULL;
} else {
queryContractUI->item2 = &tmpCtx.transactionContext.extraInfo[0];
}
strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN);
queryContractUI->screenIndex = screenIndex;
strlcpy(queryContractUI->network_ticker,
get_network_ticker(),
@@ -62,52 +79,66 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE;
PRINTF("Selector %.*H\n", 4, init->selector);
if (externalPluginIsSet) {
// check if the registered external plugin matches the TX contract address / method selector
if (memcmp(contractAddress,
dataContext.tokenContext.contract_address,
sizeof(dataContext.tokenContext.contract_address)) != 0) {
PRINTF("Got contract: %.*H\n", ADDRESS_LENGTH, contractAddress);
PRINTF("Expected contract: %.*H\n",
ADDRESS_LENGTH,
dataContext.tokenContext.contract_address);
os_sched_exit(0);
}
if (memcmp(init->selector,
dataContext.tokenContext.method_selector,
sizeof(dataContext.tokenContext.method_selector)) != 0) {
PRINTF("Got selector: %.*H\n", SELECTOR_SIZE, init->selector);
PRINTF("Expected selector: %.*H\n",
SELECTOR_SIZE,
dataContext.tokenContext.method_selector);
os_sched_exit(0);
}
PRINTF("External plugin will be used\n");
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
contractAddress = NULL;
} else {
// Search internal plugin list
for (i = 0;; i++) {
uint8_t j;
selectors = (const uint8_t **) PIC(INTERNAL_ETH_PLUGINS[i].selectors);
if (selectors == NULL) {
break;
switch (pluginType) {
case NOT_OLD_INTERNAL:
case ERC1155:
case ERC721:
case EXTERNAL: {
// check if the registered external plugin matches the TX contract address / selector
if (memcmp(contractAddress,
dataContext.tokenContext.contractAddress,
sizeof(dataContext.tokenContext.contractAddress)) != 0) {
PRINTF("Got contract: %.*H\n", ADDRESS_LENGTH, contractAddress);
PRINTF("Expected contract: %.*H\n",
ADDRESS_LENGTH,
dataContext.tokenContext.contractAddress);
os_sched_exit(0);
}
for (j = 0; ((j < INTERNAL_ETH_PLUGINS[i].num_selectors) && (contractAddress != NULL));
j++) {
if (memcmp(init->selector, (const void *) PIC(selectors[j]), SELECTOR_SIZE) == 0) {
if ((INTERNAL_ETH_PLUGINS[i].availableCheck == NULL) ||
((PluginAvailableCheck) PIC(INTERNAL_ETH_PLUGINS[i].availableCheck))()) {
strlcpy(dataContext.tokenContext.pluginName,
INTERNAL_ETH_PLUGINS[i].alias,
PLUGIN_ID_LENGTH);
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
contractAddress = NULL;
break;
if (memcmp(init->selector,
dataContext.tokenContext.methodSelector,
sizeof(dataContext.tokenContext.methodSelector)) != 0) {
PRINTF("Got selector: %.*H\n", SELECTOR_SIZE, init->selector);
PRINTF("Expected selector: %.*H\n",
SELECTOR_SIZE,
dataContext.tokenContext.methodSelector);
os_sched_exit(0);
}
PRINTF("Plugin will be used\n");
// TODO: Add check for chainid.
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
contractAddress = NULL;
} break;
case OLD_INTERNAL: {
// Search internal plugin list
for (i = 0;; i++) {
uint8_t j;
selectors = (const uint8_t **) PIC(INTERNAL_ETH_PLUGINS[i].selectors);
if (selectors == NULL) {
break;
}
for (j = 0;
((j < INTERNAL_ETH_PLUGINS[i].num_selectors) && (contractAddress != NULL));
j++) {
if (memcmp(init->selector, (const void *) PIC(selectors[j]), SELECTOR_SIZE) ==
0) {
if ((INTERNAL_ETH_PLUGINS[i].availableCheck == NULL) ||
((PluginAvailableCheck) PIC(
INTERNAL_ETH_PLUGINS[i].availableCheck))()) {
strlcpy(dataContext.tokenContext.pluginName,
INTERNAL_ETH_PLUGINS[i].alias,
PLUGIN_ID_LENGTH);
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
contractAddress = NULL;
break;
}
}
}
}
}
} break;
default:
PRINTF("Unsupported pluginType %d\n", pluginType);
os_sched_exit(0);
break;
}
// Do not handle a plugin if running in swap mode
@@ -140,7 +171,6 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
ethPluginSharedRO_t pluginRO;
char *alias;
uint8_t i;
uint8_t internalPlugin = 0;
pluginRW.sha3 = &global_sha3;
pluginRO.txContent = &tmpContent.txContent;
@@ -183,12 +213,12 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
((ethPluginFinalize_t *) parameter)->pluginContext =
(uint8_t *) &dataContext.tokenContext.pluginContext;
break;
case ETH_PLUGIN_PROVIDE_TOKEN:
PRINTF("-- PLUGIN PROVIDE TOKEN --\n");
((ethPluginProvideToken_t *) parameter)->result = ETH_PLUGIN_RESULT_UNAVAILABLE;
((ethPluginProvideToken_t *) parameter)->pluginSharedRW = &pluginRW;
((ethPluginProvideToken_t *) parameter)->pluginSharedRO = &pluginRO;
((ethPluginProvideToken_t *) parameter)->pluginContext =
case ETH_PLUGIN_PROVIDE_INFO:
PRINTF("-- PLUGIN PROVIDE INFO --\n");
((ethPluginProvideInfo_t *) parameter)->result = ETH_PLUGIN_RESULT_UNAVAILABLE;
((ethPluginProvideInfo_t *) parameter)->pluginSharedRW = &pluginRW;
((ethPluginProvideInfo_t *) parameter)->pluginSharedRO = &pluginRO;
((ethPluginProvideInfo_t *) parameter)->pluginContext =
(uint8_t *) &dataContext.tokenContext.pluginContext;
break;
case ETH_PLUGIN_QUERY_CONTRACT_ID:
@@ -211,35 +241,52 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
return ETH_PLUGIN_RESULT_UNAVAILABLE;
}
// Perform the call
for (i = 0;; i++) {
if (INTERNAL_ETH_PLUGINS[i].alias[0] == 0) {
switch (pluginType) {
case NOT_OLD_INTERNAL:
break;
case EXTERNAL: {
uint32_t params[3];
params[0] = (uint32_t) alias;
params[1] = method;
params[2] = (uint32_t) parameter;
BEGIN_TRY {
TRY {
os_lib_call(params);
}
CATCH_OTHER(e) {
PRINTF("Plugin call exception for %s\n", alias);
}
FINALLY {
}
}
END_TRY;
break;
}
if (strcmp(alias, INTERNAL_ETH_PLUGINS[i].alias) == 0) {
internalPlugin = 1;
((PluginCall) PIC(INTERNAL_ETH_PLUGINS[i].impl))(method, parameter);
case ERC721: {
erc721_plugin_call(method, parameter);
break;
}
}
if (!internalPlugin) {
uint32_t params[3];
params[0] = (uint32_t) alias;
params[1] = method;
params[2] = (uint32_t) parameter;
BEGIN_TRY {
TRY {
os_lib_call(params);
}
CATCH_OTHER(e) {
PRINTF("Plugin call exception for %s\n", alias);
}
FINALLY {
}
case ERC1155: {
erc1155_plugin_call(method, parameter);
break;
}
case OLD_INTERNAL: {
// Perform the call
for (i = 0;; i++) {
if (INTERNAL_ETH_PLUGINS[i].alias[0] == 0) {
break;
}
if (strcmp(alias, INTERNAL_ETH_PLUGINS[i].alias) == 0) {
((PluginCall) PIC(INTERNAL_ETH_PLUGINS[i].impl))(method, parameter);
break;
}
}
break;
}
default: {
PRINTF("Error with pluginType: %d\n", pluginType);
return ETH_PLUGIN_RESULT_ERROR;
}
END_TRY;
}
// Check the call result
@@ -277,9 +324,9 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
return ETH_PLUGIN_RESULT_UNAVAILABLE;
}
break;
case ETH_PLUGIN_PROVIDE_TOKEN:
PRINTF("RESULT: %d\n", ((ethPluginProvideToken_t *) parameter)->result);
switch (((ethPluginProvideToken_t *) parameter)->result) {
case ETH_PLUGIN_PROVIDE_INFO:
PRINTF("RESULT: %d\n", ((ethPluginProvideInfo_t *) parameter)->result);
switch (((ethPluginProvideInfo_t *) parameter)->result) {
case ETH_PLUGIN_RESULT_OK:
case ETH_PLUGIN_RESULT_FALLBACK:
break;

View File

@@ -7,7 +7,7 @@ void eth_plugin_prepare_provide_parameter(ethPluginProvideParameter_t *providePa
uint8_t *parameter,
uint32_t parameterOffset);
void eth_plugin_prepare_finalize(ethPluginFinalize_t *finalize);
void eth_plugin_prepare_provide_token(ethPluginProvideToken_t *provideToken);
void eth_plugin_prepare_provide_info(ethPluginProvideInfo_t *provideToken);
void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID,
char *name,
uint32_t nameLength,
@@ -24,7 +24,6 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
ethPluginInitContract_t *init);
// NULL for cached address, or base contract address
eth_plugin_result_t eth_plugin_call(int method, void *parameter);
int compound_plugin_call(uint8_t *contractAddress, int method, void *parameter);
void plugin_ui_start(void);

View File

@@ -6,14 +6,14 @@
#include "cx.h"
#include "ethUstream.h"
#include "tokens.h"
#define PLUGIN_ID_LENGTH 30
#include "shared_context.h"
// Interface version. To be updated everytime we introduce breaking changes to the plugin interface.
typedef enum {
ETH_PLUGIN_INTERFACE_VERSION_1 = 1, // Version 1
ETH_PLUGIN_INTERFACE_VERSION_1 = 1,
ETH_PLUGIN_INTERFACE_VERSION_2 = 2,
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 3,
ETH_PLUGIN_INTERFACE_VERSION_3 = 3,
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 4,
} eth_plugin_interface_version_t;
typedef enum {
@@ -21,7 +21,7 @@ typedef enum {
ETH_PLUGIN_INIT_CONTRACT = 0x0101,
ETH_PLUGIN_PROVIDE_PARAMETER = 0x0102,
ETH_PLUGIN_FINALIZE = 0x0103,
ETH_PLUGIN_PROVIDE_TOKEN = 0x0104,
ETH_PLUGIN_PROVIDE_INFO = 0x0104,
ETH_PLUGIN_QUERY_CONTRACT_ID = 0x0105,
ETH_PLUGIN_QUERY_CONTRACT_UI = 0x0106,
ETH_PLUGIN_CHECK_PRESENCE = 0x01FF
@@ -126,20 +126,20 @@ typedef struct ethPluginFinalize_t {
// Provide token
typedef struct ethPluginProvideToken_t {
typedef struct ethPluginProvideInfo_t {
ethPluginSharedRW_t *pluginSharedRW;
ethPluginSharedRO_t *pluginSharedRO;
uint8_t *pluginContext;
tokenDefinition_t *token1; // set by the ETH application, to be saved by the plugin
tokenDefinition_t *token2;
union extraInfo_t *item1; // set by the ETH application, to be saved by the plugin
union extraInfo_t *item2;
uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens
// based on the information received from the token definitions.
uint8_t result;
} ethPluginProvideToken_t;
} ethPluginProvideInfo_t;
// Query Contract name and version
@@ -164,9 +164,11 @@ typedef struct ethQueryContractID_t {
typedef struct ethQueryContractUI_t {
ethPluginSharedRW_t *pluginSharedRW;
ethPluginSharedRO_t *pluginSharedRO;
union extraInfo_t *item1;
union extraInfo_t *item2;
char network_ticker[MAX_TICKER_LEN];
uint8_t *pluginContext;
uint8_t screenIndex;
char network_ticker[MAX_TICKER_LEN];
char *title;
size_t titleLength;

View File

@@ -1,11 +1,20 @@
#include "eth_plugin_internal.h"
bool erc20_plugin_available_check(void);
bool erc721_plugin_available_check(void);
void erc20_plugin_call(int message, void* parameters);
void erc721_plugin_call(int message, void* parameters);
void compound_plugin_call(int message, void* parameters);
void copy_address(uint8_t* dst, uint8_t* parameter, uint8_t dst_size) {
uint8_t copy_size = MIN(dst_size, ADDRESS_LENGTH);
memmove(dst, parameter + PARAMETER_LENGTH - copy_size, copy_size);
}
void copy_parameter(uint8_t* dst, uint8_t* parameter, uint8_t dst_size) {
uint8_t copy_size = MIN(dst_size, PARAMETER_LENGTH);
memmove(dst, parameter, copy_size);
}
#ifdef HAVE_STARKWARE
void starkware_plugin_call(int message, void* parameters);
#endif
@@ -19,10 +28,6 @@ static const uint8_t ERC20_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7,
const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS] = {ERC20_TRANSFER_SELECTOR,
ERC20_APPROVE_SELECTOR};
static const uint8_t ERC721_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7, 0xb3};
const uint8_t* const ERC721_SELECTORS[NUM_ERC721_SELECTORS] = {ERC721_APPROVE_SELECTOR};
static const uint8_t COMPOUND_REDEEM_UNDERLYING_SELECTOR[SELECTOR_SIZE] = {0x85, 0x2a, 0x12, 0xe3};
static const uint8_t COMPOUND_REDEEM_SELECTOR[SELECTOR_SIZE] = {0xdb, 0x00, 0x6a, 0x75};
static const uint8_t COMPOUND_MINT_SELECTOR[SELECTOR_SIZE] = {0xa0, 0x71, 0x2d, 0x68};
@@ -105,12 +110,6 @@ const internalEthPlugin_t INTERNAL_ETH_PLUGINS[] = {
"-erc20",
erc20_plugin_call},
{erc721_plugin_available_check,
(const uint8_t**) ERC721_SELECTORS,
NUM_ERC721_SELECTORS,
"-er721",
erc721_plugin_call},
{NULL,
(const uint8_t**) COMPOUND_SELECTORS,
NUM_COMPOUND_SELECTORS,

View File

@@ -6,6 +6,13 @@
#define PARAMETER_LENGTH 32
#define RUN_APPLICATION 1
void copy_address(uint8_t* dst, uint8_t* parameter, uint8_t dst_size);
void copy_parameter(uint8_t* dst, uint8_t* parameter, uint8_t dst_size);
void erc721_plugin_call(int message, void* parameters);
void erc1155_plugin_call(int message, void* parameters);
typedef bool (*PluginAvailableCheck)(void);
typedef struct internalEthPlugin_t {
@@ -19,9 +26,6 @@ typedef struct internalEthPlugin_t {
#define NUM_ERC20_SELECTORS 2
extern const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS];
#define NUM_ERC721_SELECTORS 1
extern const uint8_t* const ERC721_SELECTORS[NUM_ERC721_SELECTORS];
#define NUM_COMPOUND_SELECTORS 4
extern const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS];

View File

@@ -50,7 +50,7 @@ cx_sha3_t global_sha3;
uint8_t appState;
bool called_from_swap;
bool externalPluginIsSet;
pluginType_t pluginType;
#ifdef HAVE_STARKWARE
bool quantumSet;
#endif
@@ -72,7 +72,7 @@ void reset_app_context() {
// PRINTF("!!RESET_APP_CONTEXT\n");
appState = APP_STATE_IDLE;
called_from_swap = false;
externalPluginIsSet = false;
pluginType = OLD_INTERNAL;
#ifdef HAVE_STARKWARE
quantumSet = false;
#endif
@@ -155,8 +155,8 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) {
return 0;
}
tokenDefinition_t *getKnownToken(uint8_t *contractAddress) {
tokenDefinition_t *currentToken = NULL;
extraInfo_t *getKnownToken(uint8_t *contractAddress) {
union extraInfo_t *currentItem = NULL;
#ifdef HAVE_TOKENS_LIST
uint32_t numTokens = 0;
uint32_t i;
@@ -380,12 +380,22 @@ tokenDefinition_t *getKnownToken(uint8_t *contractAddress) {
}
}
#endif
for (size_t i = 0; i < MAX_TOKEN; i++) {
currentToken = &tmpCtx.transactionContext.tokens[i];
//
for (uint8_t i = 0; i < MAX_ITEMS; i++) {
currentItem = (union extraInfo_t *) &tmpCtx.transactionContext.extraInfo[i].token;
if (tmpCtx.transactionContext.tokenSet[i] &&
(memcmp(currentToken->address, contractAddress, ADDRESS_LENGTH) == 0)) {
(memcmp(currentItem->token.address, contractAddress, ADDRESS_LENGTH) == 0)) {
PRINTF("Token found at index %d\n", i);
return currentToken;
return currentItem;
}
}
for (uint8_t i = 0; i < MAX_ITEMS; i++) {
currentItem = (union extraInfo_t *) &tmpCtx.transactionContext.extraInfo[i].token;
if (tmpCtx.transactionContext.tokenSet[i] &&
(memcmp(currentItem->nft.contractAddress, contractAddress, ADDRESS_LENGTH) == 0)) {
PRINTF("Token found at index %d\n", i);
return currentItem;
}
}
@@ -485,7 +495,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
switch (G_io_apdu_buffer[OFFSET_INS]) {
case INS_GET_PUBLIC_KEY:
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
handleGetPublicKey(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
@@ -503,6 +513,15 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
tx);
break;
case INS_PROVIDE_NFT_INFORMATION:
handleProvideNFTInformation(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;
case INS_SET_EXTERNAL_PLUGIN:
handleSetExternalPlugin(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
@@ -512,6 +531,15 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
tx);
break;
case INS_SET_PLUGIN:
handleSetPlugin(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;
case INS_SIGN:
handleSign(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
@@ -531,7 +559,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
break;
case INS_SIGN_PERSONAL_MESSAGE:
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
@@ -541,7 +569,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
break;
case INS_SIGN_EIP_712_MESSAGE:
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
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,
@@ -553,7 +581,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
#ifdef HAVE_ETH2
case INS_GET_ETH2_PUBLIC_KEY:
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
handleGetEth2PublicKey(G_io_apdu_buffer[OFFSET_P1],
G_io_apdu_buffer[OFFSET_P2],
G_io_apdu_buffer + OFFSET_CDATA,
@@ -762,7 +790,7 @@ void coin_main(chain_config_t *coin_config) {
chainConfig = coin_config;
}
reset_app_context();
tmpCtx.transactionContext.currentTokenIndex = 0;
tmpCtx.transactionContext.currentItemIndex = 0;
for (;;) {
UX_INIT();

14
src/nft.h Normal file
View File

@@ -0,0 +1,14 @@
#include "tokens.h"
// An `nftInfo_t` must be the same size as a `tokenDefinition_t`. This is because both will be held
// in a `extraInfo_t` which is a union of a `nftInfo_t` and a `tokenDefinition_t`. By having both
// struct the same size, we know they will be aligned, which facilitates accessing the items.
// We defined the collection name max length to be the size of a `tokenDefinition_t` and remove the
// `ADDRESS_LENGTH` which corresponds to `sizeof(contractAddress`).
#define COLLECTION_NAME_MAX_LEN sizeof(tokenDefinition_t) - ADDRESS_LENGTH
typedef struct nftInfo_t {
char collectionName[COLLECTION_NAME_MAX_LEN];
char contractAddress[ADDRESS_LENGTH];
} nftInfo_t;

View File

@@ -14,16 +14,16 @@
#include "uint256.h"
#include "tokens.h"
#include "chainConfig.h"
#include "eth_plugin_interface.h"
#include "nft.h"
#define MAX_BIP32_PATH 10
#define MAX_TOKEN 2
#define WEI_TO_ETHER 18
#define SELECTOR_LENGTH 4
#define PLUGIN_ID_LENGTH 30
#define N_storage (*(volatile internalStorage_t *) PIC(&N_storage_real))
typedef struct internalStorage_t {
@@ -62,8 +62,8 @@ typedef struct tokenContext_t {
union {
struct {
uint8_t contract_address[ADDRESS_LENGTH];
uint8_t method_selector[SELECTOR_LENGTH];
uint8_t contractAddress[ADDRESS_LENGTH];
uint8_t methodSelector[SELECTOR_LENGTH];
};
uint8_t pluginContext[5 * INT256_LENGTH];
};
@@ -84,13 +84,18 @@ typedef struct publicKeyContext_t {
bool getChaincode;
} publicKeyContext_t;
typedef union extraInfo_t {
tokenDefinition_t token;
nftInfo_t nft;
} extraInfo_t;
typedef struct transactionContext_t {
uint8_t pathLength;
uint32_t bip32Path[MAX_BIP32_PATH];
uint8_t hash[INT256_LENGTH];
tokenDefinition_t tokens[MAX_TOKEN];
uint8_t tokenSet[MAX_TOKEN];
uint8_t currentTokenIndex;
union extraInfo_t extraInfo[MAX_ITEMS];
uint8_t tokenSet[MAX_ITEMS];
uint8_t currentItemIndex;
} transactionContext_t;
typedef struct messageSigningContext_t {
@@ -137,6 +142,7 @@ typedef struct starkContext_t {
typedef union {
tokenContext_t tokenContext;
#ifdef HAVE_STARKWARE
starkContext_t starkContext;
#endif
@@ -166,7 +172,7 @@ typedef enum {
typedef struct txStringProperties_t {
char fullAddress[43];
char fullAmount[67];
char fullAmount[79]; // 2^256 is 78 digits long
char maxFee[50];
char nonce[8]; // 10M tx per account ought to be enough for everybody
char network_name[NETWORK_STRING_MAX_SIZE];
@@ -196,7 +202,17 @@ extern cx_sha3_t global_sha3;
extern const internalStorage_t N_storage_real;
extern bool called_from_swap;
extern bool externalPluginIsSet;
typedef enum {
EXTERNAL, // External plugin, set by setExternalPlugin.
ERC721, // Specific ERC721 internal plugin, set by setPlugin.
ERC1155, // Specific ERC1155 internal plugin, set by setPlugin
OLD_INTERNAL, // Old internal plugin, not set by any command.
NOT_OLD_INTERNAL, // Do not treat this tx as an old internal transaction.
} pluginType_t;
extern pluginType_t pluginType;
extern uint8_t appState;
#ifdef HAVE_STARKWARE
extern bool quantumSet;

View File

@@ -68,7 +68,7 @@ void stark_get_amount_string(uint8_t *contractAddress,
decimals = WEI_TO_ETHER;
PRINTF("stark_get_amount_string - ETH\n");
} else {
tokenDefinition_t *token = getKnownToken(contractAddress);
tokenDefinition_t *token = &getKnownToken(contractAddress)->token;
if (token == NULL) { // caught earlier
THROW(0x6A80);
}

View File

@@ -22,6 +22,7 @@
#include "ethUstream.h"
#define MAX_TICKER_LEN 12 // 10 characters + ' ' + '\0'
#define MAX_ITEMS 2
typedef struct tokenDefinition_t {
#ifdef HAVE_CONTRACT_NAME_IN_DESCRIPTOR
@@ -29,6 +30,9 @@ typedef struct tokenDefinition_t {
#endif
uint8_t address[ADDRESS_LENGTH];
char ticker[MAX_TICKER_LEN];
char nft_pad[20]; // Adding some padding because the `nftInfo_t` is based on the size of a
// `tokenDefinition_t`. By adding some padding here we give more space to the
// collection name in the `nftInfo_t`. See `nftInfo_t` for more information.
uint8_t decimals;
} tokenDefinition_t;

View File

@@ -21,4 +21,4 @@ void ui_warning_contract_data(void);
void io_seproxyhal_send_status(uint32_t sw);
void format_signature_out(const uint8_t *signature);
void finalizeParsing(bool direct);
tokenDefinition_t *getKnownToken(uint8_t *contractAddress);
extraInfo_t *getKnownToken(uint8_t *contractAddress);

View File

@@ -254,6 +254,7 @@ static void processTo(txContext_t *context) {
}
static void processData(txContext_t *context) {
PRINTF("PROCESS DATA\n");
if (context->currentFieldIsList) {
PRINTF("Invalid type for RLP_DATA\n");
THROW(EXCEPTION);
@@ -268,6 +269,7 @@ static void processData(txContext_t *context) {
copyTxData(context, NULL, copySize);
}
if (context->currentFieldPos == context->currentFieldLength) {
PRINTF("incrementing field\n");
context->currentField++;
context->processingField = false;
}
@@ -506,6 +508,7 @@ static parserStatus_e processTxInternal(txContext_t *context) {
customStatus_e customStatus = CUSTOM_NOT_HANDLED;
// EIP 155 style transaction
if (PARSING_IS_DONE(context)) {
PRINTF("parsing is done\n");
return USTREAM_FINISHED;
}
// Old style transaction (pre EIP-155). Transations could just skip `v,r,s` so we needed to
@@ -518,9 +521,11 @@ static parserStatus_e processTxInternal(txContext_t *context) {
if ((context->txType == LEGACY && context->currentField == LEGACY_RLP_V) &&
(context->commandLength == 0)) {
context->content->vLength = 0;
PRINTF("finished\n");
return USTREAM_FINISHED;
}
if (context->commandLength == 0) {
PRINTF("Command length done\n");
return USTREAM_PROCESSING;
}
if (!context->processingField) {
@@ -531,6 +536,7 @@ static parserStatus_e processTxInternal(txContext_t *context) {
}
if (context->customProcessor != NULL) {
customStatus = context->customProcessor(context);
PRINTF("After customprocessor\n");
switch (customStatus) {
case CUSTOM_NOT_HANDLED:
case CUSTOM_HANDLED:
@@ -571,11 +577,12 @@ static parserStatus_e processTxInternal(txContext_t *context) {
break;
}
default:
PRINTF("Transaction type %u is not supported\n", context->txType);
PRINTF("Transaction type %d is not supported\n", context->txType);
return USTREAM_FAULT;
}
}
}
PRINTF("end of here\n");
}
parserStatus_e processTx(txContext_t *context,
@@ -589,6 +596,7 @@ parserStatus_e processTx(txContext_t *context,
context->commandLength = length;
context->processingFlags = processingFlags;
result = processTxInternal(context);
PRINTF("result: %d\n");
}
CATCH_OTHER(e) {
result = USTREAM_FAULT;

View File

@@ -23,10 +23,10 @@ void handleProvideErc20TokenInformation(uint8_t p1,
cx_sha256_init(&sha256);
tmpCtx.transactionContext.currentTokenIndex =
(tmpCtx.transactionContext.currentTokenIndex + 1) % MAX_TOKEN;
tmpCtx.transactionContext.currentItemIndex =
(tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentTokenIndex];
&tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentItemIndex];
if (dataLength < 1) {
THROW(0x6A80);
@@ -93,7 +93,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
THROW(0x6A80);
#endif
}
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentTokenIndex] = 1;
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
THROW(0x9000);
}
@@ -114,12 +114,12 @@ void handleProvideErc20TokenInformation(uint8_t p1,
uint8_t hash[INT256_LENGTH];
cx_ecfp_public_key_t tokenKey;
tmpCtx.transactionContext.currentTokenIndex =
(tmpCtx.transactionContext.currentTokenIndex + 1) % MAX_TOKEN;
tmpCtx.transactionContext.currentItemIndex =
(tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentTokenIndex];
&tmpCtx.transactionContext.extraInfo[tmpCtx.transactionContext.currentItemIndex].token;
PRINTF("Provisioning currentTokenIndex %d\n", tmpCtx.transactionContext.currentTokenIndex);
PRINTF("Provisioning currentItemIndex %d\n", tmpCtx.transactionContext.currentItemIndex);
if (dataLength < 1) {
THROW(0x6A80);
@@ -204,7 +204,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
}
#endif
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentTokenIndex] = 1;
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
THROW(0x9000);
}

View File

@@ -0,0 +1,219 @@
#include "shared_context.h"
#include "apdu_constants.h"
#include "ui_flow.h"
#include "tokens.h"
#include "utils.h"
#define TYPE_SIZE 1
#define VERSION_SIZE 1
#define NAME_LENGTH_SIZE 1
#define HEADER_SIZE TYPE_SIZE + VERSION_SIZE + NAME_LENGTH_SIZE
#define CHAIN_ID_SIZE 8
#define KEY_ID_SIZE 1
#define ALGORITHM_ID_SIZE 1
#define SIGNATURE_LENGTH_SIZE 1
#define MIN_DER_SIG_SIZE 67
#define MAX_DER_SIG_SIZE 72
#define TESTING_KEY 0
#define NFT_METADATA_KEY_1 1
#define ALGORITHM_ID_1 1
#define TYPE_1 1
#define VERSION_1 1
#ifdef HAVE_NFT_TESTING_KEY
static const uint8_t LEDGER_NFT_PUBLIC_KEY[] = {
0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7,
0xe3, 0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8,
0x30, 0x00, 0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d,
0xa5, 0xaa, 0xb0, 0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1,
0xdd, 0xa6, 0xec, 0xe9, 0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4};
#else
static const uint8_t LEDGER_NFT_PUBLIC_KEY[] = {};
#endif
typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
int,
cx_md_t,
const unsigned char *,
unsigned int,
unsigned char *,
unsigned int);
void handleProvideNFTInformation(uint8_t p1,
uint8_t p2,
uint8_t *workBuffer,
uint16_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(p1);
UNUSED(p2);
UNUSED(tx);
UNUSED(flags);
uint8_t hash[INT256_LENGTH];
cx_ecfp_public_key_t nftKey;
PRINTF("In handle provide NFTInformation");
tmpCtx.transactionContext.currentItemIndex =
(tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
nftInfo_t *nft =
&tmpCtx.transactionContext.extraInfo[tmpCtx.transactionContext.currentItemIndex].nft;
PRINTF("Provisioning currentItemIndex %d\n", tmpCtx.transactionContext.currentItemIndex);
uint8_t offset = 0;
if (dataLength <= HEADER_SIZE) {
PRINTF("Data too small for headers: expected at least %d, got %d\n",
HEADER_SIZE,
dataLength);
THROW(0x6A80);
}
uint8_t type = workBuffer[offset];
switch (type) {
case TYPE_1:
break;
default:
PRINTF("Unsupported type %d\n", type);
THROW(0x6a80);
break;
}
offset += TYPE_SIZE;
uint8_t version = workBuffer[offset];
switch (version) {
case VERSION_1:
break;
default:
PRINTF("Unsupported version %d\n", version);
THROW(0x6a80);
break;
}
offset += VERSION_SIZE;
uint8_t collectionNameLength = workBuffer[offset];
offset += NAME_LENGTH_SIZE;
// Size of the payload (everything except the signature)
uint8_t payloadSize = HEADER_SIZE + collectionNameLength + ADDRESS_LENGTH + CHAIN_ID_SIZE +
KEY_ID_SIZE + ALGORITHM_ID_SIZE;
if (dataLength < payloadSize) {
PRINTF("Data too small for payload: expected at least %d, got %d\n",
payloadSize,
dataLength);
THROW(0x6A80);
}
if (collectionNameLength + 1 > sizeof(nft->collectionName)) {
PRINTF("CollectionName too big: expected max %d, got %d\n",
sizeof(nft->collectionName),
collectionNameLength + 1);
THROW(0x6A80);
}
// Safe because we've checked the size before.
memcpy(nft->collectionName, workBuffer + offset, collectionNameLength);
nft->collectionName[collectionNameLength] = '\0';
PRINTF("Length: %d\n", collectionNameLength);
PRINTF("CollectionName: %s\n", nft->collectionName);
offset += collectionNameLength;
memcpy(nft->contractAddress, workBuffer + offset, ADDRESS_LENGTH);
PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset);
offset += ADDRESS_LENGTH;
// TODO: store chainID and assert that tx is using the same chainid.
// uint64_t chainid = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
// PRINTF("ChainID: %.*H\n", sizeof(chainid), &chainid);
offset += CHAIN_ID_SIZE;
uint8_t keyId = workBuffer[offset];
uint8_t *rawKey;
uint8_t rawKeyLen;
PRINTF("KeyID: %d\n", keyId);
switch (keyId) {
#ifdef HAVE_NFT_TESTING_KEY
case TESTING_KEY:
#endif
case NFT_METADATA_KEY_1:
rawKey = (uint8_t *) LEDGER_NFT_PUBLIC_KEY;
rawKeyLen = sizeof(LEDGER_NFT_PUBLIC_KEY);
break;
default:
PRINTF("KeyID %d not supported\n", keyId);
THROW(0x6A80);
break;
}
PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey);
offset += KEY_ID_SIZE;
uint8_t algorithmId = workBuffer[offset];
PRINTF("Algorithm: %d\n", algorithmId);
cx_curve_t curve;
verificationAlgo *verificationFn;
cx_md_t hashId;
switch (algorithmId) {
case ALGORITHM_ID_1:
curve = CX_CURVE_256K1;
verificationFn = (verificationAlgo*)cx_ecdsa_verify;
hashId = CX_SHA256;
break;
default:
PRINTF("Incorrect algorithmId %d\n", algorithmId);
THROW(0x6a80);
break;
}
offset += ALGORITHM_ID_SIZE;
PRINTF("hashing: %.*H\n", payloadSize, workBuffer);
cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash));
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) {
PRINTF("Data too short to hold signature length\n");
THROW(0x6a80);
}
uint8_t signatureLen = workBuffer[offset];
PRINTF("Sigature len: %d\n", signatureLen);
if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) {
PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n",
MIN_DER_SIG_SIZE,
MAX_DER_SIG_SIZE,
signatureLen);
THROW(0x6a80);
}
offset += SIGNATURE_LENGTH_SIZE;
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) {
PRINTF("Signature could not fit in data\n");
THROW(0x6a80);
}
cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &nftKey);
if (!verificationFn(&nftKey,
CX_LAST,
hashId,
hash,
sizeof(hash),
workBuffer + offset,
signatureLen)) {
#ifndef HAVE_BYPASS_SIGNATURES
PRINTF("Invalid NFT signature\n");
THROW(0x6A80);
#endif
}
// Set this to `NOT_OLD_INTERNAL` because otherwise the tx might be treated as an
// internal plugin and we might get a collision and hence some BIG problems.
pluginType = NOT_OLD_INTERNAL;
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
THROW(0x9000);
}

View File

@@ -2,8 +2,8 @@
#include "apdu_constants.h"
#include "ui_flow.h"
#include "tokens.h"
#define SELECTOR_SIZE 4
#include "eth_plugin_interface.h"
#include "eth_plugin_internal.h"
void handleSetExternalPlugin(uint8_t p1,
uint8_t p2,
@@ -14,17 +14,22 @@ void handleSetExternalPlugin(uint8_t p1,
UNUSED(p1);
UNUSED(p2);
UNUSED(flags);
PRINTF("Handling set External Plugin\n");
uint8_t hash[32];
PRINTF("Handling set Plugin\n");
uint8_t hash[INT256_LENGTH];
cx_ecfp_public_key_t tokenKey;
uint8_t pluginNameLength = *workBuffer;
PRINTF("plugin Name Length: %d\n", pluginNameLength);
const size_t payload_size = 1 + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE;
if (dataLength <= payload_size) {
PRINTF("data too small: expected at least %d got %d\n", payload_size, dataLength);
THROW(0x6A80);
}
if (pluginNameLength + 1 > sizeof(dataContext.tokenContext.pluginName)) {
PRINTF("name length too big: expected max %d, got %d\n",
sizeof(dataContext.tokenContext.pluginName),
pluginNameLength + 1);
THROW(0x6A80);
}
@@ -41,8 +46,8 @@ void handleSetExternalPlugin(uint8_t p1,
sizeof(hash),
workBuffer + payload_size,
dataLength - payload_size)) {
PRINTF("Invalid external plugin signature %.*H\n", payload_size, workBuffer);
#ifndef HAVE_BYPASS_SIGNATURES
PRINTF("Invalid plugin signature %.*H\n", payload_size, workBuffer);
THROW(0x6A80);
#endif
}
@@ -77,10 +82,11 @@ void handleSetExternalPlugin(uint8_t p1,
PRINTF("Plugin found\n");
memmove(dataContext.tokenContext.contract_address, workBuffer, ADDRESS_LENGTH);
memmove(dataContext.tokenContext.contractAddress, workBuffer, ADDRESS_LENGTH);
workBuffer += ADDRESS_LENGTH;
memmove(dataContext.tokenContext.method_selector, workBuffer, SELECTOR_SIZE);
externalPluginIsSet = true;
memmove(dataContext.tokenContext.methodSelector, workBuffer, SELECTOR_SIZE);
pluginType = EXTERNAL;
G_io_apdu_buffer[(*tx)++] = 0x90;
G_io_apdu_buffer[(*tx)++] = 0x00;

View File

@@ -0,0 +1,294 @@
#include "shared_context.h"
#include "apdu_constants.h"
#include "ui_flow.h"
#include "tokens.h"
#include "eth_plugin_interface.h"
#include "eth_plugin_internal.h"
// Supported internal plugins
#define ERC721_STR "ERC721"
#define ERC1155_STR "ERC1155"
#define TYPE_SIZE 1
#define VERSION_SIZE 1
#define PLUGIN_NAME_LENGTH_SIZE 1
#define CHAIN_ID_SIZE 8
#define KEY_ID_SIZE 1
#define ALGORITHM_ID_SIZE 1
#define SIGNATURE_LENGTH_SIZE 1
#define HEADER_SIZE TYPE_SIZE + VERSION_SIZE + PLUGIN_NAME_LENGTH_SIZE
#define MIN_DER_SIG_SIZE 67
#define MAX_DER_SIG_SIZE 72
typedef enum Type {
ETH_PLUGIN = 0x01,
} Type;
typedef enum Version {
VERSION_1 = 0x01,
} Version;
typedef enum KeyId {
TEST_KEY = 0x00,
PERSO_V2_KEY_1 = 0x01,
// Must ONLY be used with ERC721 and ERC1155 plugin
AWS_PLUGIN_KEY_1 = 0x02,
} KeyId;
// Algorithm Id consists of a Key spec and an algorithm spec.
// Format is: KEYSPEC__ALGOSPEC
typedef enum AlgorithmID {
ECC_SECG_P256K1__ECDSA_SHA_256 = 0x01,
} AlgorithmID;
#ifdef HAVE_NFT_TESTING_KEY
static const uint8_t LEDGER_TESTING_KEY[] = {
0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7,
0xe3, 0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8,
0x30, 0x00, 0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d,
0xa5, 0xaa, 0xb0, 0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1,
0xdd, 0xa6, 0xec, 0xe9, 0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4};
#endif
static const uint8_t LEDGER_PERSO_V2_PUBLIC_KEY[] = {};
// Only used for signing NFT plugins (ERC721 and ERC1155)
static const uint8_t LEDGER_NFT_SELECTOR_PUBLIC_KEY[] = {};
// Verification function used to verify the signature
typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
int,
cx_md_t,
const unsigned char *,
unsigned int,
unsigned char *,
unsigned int);
// Returns the plugin type of a given plugin name.
// If the plugin name is not a specific known internal plugin, this function default return value is
// `EXERNAL`.
static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) {
if (pluginNameLength == sizeof(ERC721_STR) - 1 &&
strncmp(pluginName, ERC721_STR, pluginNameLength) == 0) {
return ERC721;
} else if (pluginNameLength == sizeof(ERC1155_STR) - 1 &&
strncmp(pluginName, ERC1155_STR, pluginNameLength) == 0) {
return ERC1155;
} else {
return EXTERNAL;
}
}
void handleSetPlugin(uint8_t p1,
uint8_t p2,
uint8_t *workBuffer,
uint16_t dataLength,
unsigned int *flags,
unsigned int *tx) {
UNUSED(p1);
UNUSED(p2);
UNUSED(flags);
PRINTF("Handling set Plugin\n");
uint8_t hash[INT256_LENGTH] = {0};
cx_ecfp_public_key_t pluginKey = {0};
tokenContext_t *tokenContext = &dataContext.tokenContext;
uint8_t offset = 0;
if (dataLength <= HEADER_SIZE) {
PRINTF("Data too small for headers: expected at least %d, got %d\n",
HEADER_SIZE,
dataLength);
THROW(0x6A80);
}
enum Type type = workBuffer[offset];
PRINTF("Type: %d\n", type);
switch (type) {
case ETH_PLUGIN:
break;
default:
PRINTF("Unsupported type %d\n", type);
THROW(0x6a80);
break;
}
offset += TYPE_SIZE;
uint8_t version = workBuffer[offset];
PRINTF("version: %d\n", version);
switch (version) {
case VERSION_1:
break;
default:
PRINTF("Unsupported version %d\n", version);
THROW(0x6a80);
break;
}
offset += VERSION_SIZE;
uint8_t pluginNameLength = workBuffer[offset];
offset += PLUGIN_NAME_LENGTH_SIZE;
// Size of the payload (everything except the signature)
uint8_t payloadSize = HEADER_SIZE + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE +
CHAIN_ID_SIZE + KEY_ID_SIZE + ALGORITHM_ID_SIZE;
if (dataLength < payloadSize) {
PRINTF("Data too small for payload: expected at least %d, got %d\n",
payloadSize,
dataLength);
THROW(0x6A80);
}
// `+ 1` because we want to add a null terminating character.
if (pluginNameLength + 1 > sizeof(tokenContext->pluginName)) {
PRINTF("plugin name too big: expected max %d, got %d\n",
sizeof(dataContext.tokenContext.pluginName),
pluginNameLength + 1);
THROW(0x6A80);
}
// Safe because we've checked the size before.
memcpy(tokenContext->pluginName, workBuffer + offset, pluginNameLength);
tokenContext->pluginName[pluginNameLength] = '\0';
PRINTF("Length: %d\n", pluginNameLength);
PRINTF("plugin name: %s\n", tokenContext->pluginName);
offset += pluginNameLength;
memcpy(tokenContext->contractAddress, workBuffer + offset, ADDRESS_LENGTH);
PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset);
offset += ADDRESS_LENGTH;
memcpy(tokenContext->methodSelector, workBuffer + offset, SELECTOR_SIZE);
PRINTF("Selector: %.*H\n", SELECTOR_SIZE, tokenContext->methodSelector);
offset += SELECTOR_SIZE;
// TODO: store chainID and assert that tx is using the same chainid.
// uint64_t chainid = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
// PRINTF("ChainID: %.*H\n", sizeof(chainid), &chainid);
offset += CHAIN_ID_SIZE;
enum KeyId keyId = workBuffer[offset];
uint8_t const *rawKey;
uint8_t rawKeyLen;
PRINTF("KeyID: %d\n", keyId);
switch (keyId) {
#ifdef HAVE_NFT_TESTING_KEY
case TEST_KEY:
rawKey = LEDGER_TESTING_KEY;
rawKeyLen = sizeof(LEDGER_TESTING_KEY);
break;
#endif
case PERSO_V2_KEY_1:
rawKey = LEDGER_PERSO_V2_PUBLIC_KEY;
rawKeyLen = sizeof(LEDGER_PERSO_V2_PUBLIC_KEY);
break;
case AWS_PLUGIN_KEY_1:
rawKey = LEDGER_NFT_SELECTOR_PUBLIC_KEY;
rawKeyLen = sizeof(LEDGER_NFT_SELECTOR_PUBLIC_KEY);
break;
default:
PRINTF("KeyID %d not supported\n", keyId);
THROW(0x6A80);
break;
}
PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey);
offset += KEY_ID_SIZE;
uint8_t algorithmId = workBuffer[offset];
PRINTF("Algorithm: %d\n", algorithmId);
cx_curve_t curve;
verificationAlgo *verificationFn;
cx_md_t hashId;
switch (algorithmId) {
case ECC_SECG_P256K1__ECDSA_SHA_256:
curve = CX_CURVE_256K1;
verificationFn = (verificationAlgo*)cx_ecdsa_verify;
hashId = CX_SHA256;
break;
default:
PRINTF("Incorrect algorithmId %d\n", algorithmId);
THROW(0x6a80);
break;
}
offset += ALGORITHM_ID_SIZE;
PRINTF("hashing: %.*H\n", payloadSize, workBuffer);
cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash));
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) {
PRINTF("Data too short to hold signature length\n");
THROW(0x6a80);
}
uint8_t signatureLen = workBuffer[offset];
PRINTF("Sigature len: %d\n", signatureLen);
if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) {
PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n",
MIN_DER_SIG_SIZE,
MAX_DER_SIG_SIZE,
signatureLen);
THROW(0x6a80);
}
offset += SIGNATURE_LENGTH_SIZE;
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) {
PRINTF("Signature could not fit in data\n");
THROW(0x6a80);
}
cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &pluginKey);
if (!verificationFn(&pluginKey,
CX_LAST,
hashId,
hash,
sizeof(hash),
workBuffer + offset,
signatureLen)) {
#ifndef HAVE_BYPASS_SIGNATURES
PRINTF("Invalid NFT signature\n");
THROW(0x6A80);
#endif
}
pluginType = getPluginType(tokenContext->pluginName, pluginNameLength);
if (keyId == AWS_PLUGIN_KEY_1) {
if (pluginType != ERC721 && pluginType != ERC1155) {
PRINTF("AWS key must only be used to set NFT internal plugins\n");
THROW(0x6A80);
}
}
switch (pluginType) {
case EXTERNAL: {
PRINTF("Check external plugin %s\n", tokenContext->pluginName);
// Check if the plugin is present on the device
uint32_t params[2];
params[0] = (uint32_t) tokenContext->pluginName;
params[1] = ETH_PLUGIN_CHECK_PRESENCE;
BEGIN_TRY {
TRY {
os_lib_call(params);
}
CATCH_OTHER(e) {
PRINTF("%s external plugin is not present\n", tokenContext->pluginName);
memset(tokenContext->pluginName, 0, sizeof(tokenContext->pluginName));
THROW(0x6984);
}
FINALLY {
}
}
END_TRY;
break;
}
default:
break;
}
G_io_apdu_buffer[(*tx)++] = 0x90;
G_io_apdu_buffer[(*tx)++] = 0x00;
}

View File

@@ -2,6 +2,7 @@
#include "apdu_constants.h"
#include "ui_flow.h"
#include "feature_signTx.h"
#include "eth_plugin_interface.h"
void handleSign(uint8_t p1,
uint8_t p2,
@@ -53,7 +54,7 @@ void handleSign(uint8_t p1,
workBuffer++;
dataLength--;
} else {
PRINTF("Transaction type not supported\n");
PRINTF("Transaction type %d not supported\n", txType);
THROW(0x6501);
}
} else {

View File

@@ -65,6 +65,7 @@ customStatus_e customProcessor(txContext_t *context) {
PRINTF("pluginstatus %d\n", dataContext.tokenContext.pluginStatus);
eth_plugin_result_t status = dataContext.tokenContext.pluginStatus;
if (status == ETH_PLUGIN_RESULT_ERROR) {
PRINTF("Plugin error\n");
return CUSTOM_FAULT;
} else if (status >= ETH_PLUGIN_RESULT_SUCCESSFUL) {
dataContext.tokenContext.fieldIndex = 0;
@@ -113,6 +114,7 @@ customStatus_e customProcessor(txContext_t *context) {
copySize);
if (context->currentFieldPos == context->currentFieldLength) {
PRINTF("\n\nIncrementing one\n");
context->currentField++;
context->processingField = false;
}
@@ -238,7 +240,6 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
i++;
}
displayBuffer[tickerOffset + i] = '\0';
PRINTF("Displayed fees: %s\n", displayBuffer);
}
// Compute the fees, transform it to a string, prepend a ticker to it and copy everything to
@@ -301,7 +302,6 @@ void finalizeParsing(bool direct) {
// Verify the chain
if (chainConfig->chainId != ETHEREUM_MAINNET_CHAINID) {
// TODO: Could we remove above check?
uint64_t id = get_chain_id();
if (chainConfig->chainId != id) {
@@ -338,24 +338,24 @@ void finalizeParsing(bool direct) {
}
}
// Lookup tokens if requested
ethPluginProvideToken_t pluginProvideToken;
eth_plugin_prepare_provide_token(&pluginProvideToken);
ethPluginProvideInfo_t pluginProvideInfo;
eth_plugin_prepare_provide_info(&pluginProvideInfo);
if ((pluginFinalize.tokenLookup1 != NULL) || (pluginFinalize.tokenLookup2 != NULL)) {
if (pluginFinalize.tokenLookup1 != NULL) {
PRINTF("Lookup1: %.*H\n", ADDRESS_LENGTH, pluginFinalize.tokenLookup1);
pluginProvideToken.token1 = getKnownToken(pluginFinalize.tokenLookup1);
if (pluginProvideToken.token1 != NULL) {
PRINTF("Token1 ticker: %s\n", pluginProvideToken.token1->ticker);
pluginProvideInfo.item1 = getKnownToken(pluginFinalize.tokenLookup1);
if (pluginProvideInfo.item1 != NULL) {
PRINTF("Token1 ticker: %s\n", pluginProvideInfo.item1->token.ticker);
}
}
if (pluginFinalize.tokenLookup2 != NULL) {
PRINTF("Lookup2: %.*H\n", ADDRESS_LENGTH, pluginFinalize.tokenLookup2);
pluginProvideToken.token2 = getKnownToken(pluginFinalize.tokenLookup2);
if (pluginProvideToken.token2 != NULL) {
PRINTF("Token2 ticker: %s\n", pluginProvideToken.token2->ticker);
pluginProvideInfo.item2 = getKnownToken(pluginFinalize.tokenLookup2);
if (pluginProvideInfo.item2 != NULL) {
PRINTF("Token2 ticker: %s\n", pluginProvideInfo.item2->token.ticker);
}
}
if (eth_plugin_call(ETH_PLUGIN_PROVIDE_TOKEN, (void *) &pluginProvideToken) <=
if (eth_plugin_call(ETH_PLUGIN_PROVIDE_INFO, (void *) &pluginProvideInfo) <=
ETH_PLUGIN_RESULT_UNSUCCESSFUL) {
PRINTF("Plugin provide token call failed\n");
reportFinalizeError(direct);
@@ -363,7 +363,7 @@ void finalizeParsing(bool direct) {
return;
}
}
pluginFinalize.result = pluginProvideToken.result;
pluginFinalize.result = pluginProvideInfo.result;
}
if (pluginFinalize.result != ETH_PLUGIN_RESULT_FALLBACK) {
// Handle the right interface
@@ -373,7 +373,7 @@ void finalizeParsing(bool direct) {
// Add the number of screens + the number of additional screens to get the total
// number of screens needed.
dataContext.tokenContext.pluginUiMaxItems =
pluginFinalize.numScreens + pluginProvideToken.additionalScreens;
pluginFinalize.numScreens + pluginProvideInfo.additionalScreens;
break;
case ETH_UI_TYPE_AMOUNT_ADDRESS:
genericUI = true;
@@ -389,9 +389,9 @@ void finalizeParsing(bool direct) {
tmpContent.txContent.value.length = 32;
memmove(tmpContent.txContent.destination, pluginFinalize.address, 20);
tmpContent.txContent.destinationLength = 20;
if (pluginProvideToken.token1 != NULL) {
decimals = pluginProvideToken.token1->decimals;
ticker = pluginProvideToken.token1->ticker;
if (pluginProvideInfo.item1 != NULL) {
decimals = pluginProvideInfo.item1->token.decimals;
ticker = pluginProvideInfo.item1->token.ticker;
}
break;
default:

View File

@@ -37,19 +37,19 @@ void handleStarkwareProvideQuantum(uint8_t p1,
addressZero = allzeroes(dataBuffer, 20);
}
if ((p1 != STARK_QUANTUM_ETH) && !addressZero) {
for (i = 0; i < MAX_TOKEN; i++) {
currentToken = &tmpCtx.transactionContext.tokens[i];
for (i = 0; i < MAX_ITEMS; i++) {
currentToken = &tmpCtx.transactionContext.extraInfo[i].token;
if (tmpCtx.transactionContext.tokenSet[i] &&
(memcmp(currentToken->address, dataBuffer, 20) == 0)) {
break;
}
}
if (i == MAX_TOKEN) {
if (i == MAX_ITEMS) {
PRINTF("Associated token not found\n");
THROW(0x6A80);
}
} else {
i = MAX_TOKEN;
i = MAX_ITEMS;
}
memmove(dataContext.tokenContext.quantum, dataBuffer + 20, 32);
if (p1 != STARK_QUANTUM_LEGACY) {

View File

@@ -85,6 +85,7 @@ void compound_plugin_call(int message, void *parameters) {
// enforce that ETH amount should be 0, except in ceth.mint case
if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) {
if (context->selectorIndex != CETH_MINT) {
PRINTF("Eth amount is not zero and token minted is not CETH!\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
@@ -148,12 +149,12 @@ void compound_plugin_call(int message, void *parameters) {
msg->result = ETH_PLUGIN_RESULT_OK;
} break;
case ETH_PLUGIN_PROVIDE_TOKEN: {
ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
case ETH_PLUGIN_PROVIDE_INFO: {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
PRINTF("compound plugin provide token: %d\n", (msg->token1 != NULL));
if (msg->token1 != NULL) {
strlcpy(context->ticker_1, msg->token1->ticker, MAX_TICKER_LEN);
PRINTF("compound plugin provide token: %d\n", (msg->item1 != NULL));
if (msg->item1 != NULL) {
strlcpy(context->ticker_1, msg->item1->token.ticker, MAX_TICKER_LEN);
switch (context->selectorIndex) {
case COMPOUND_REDEEM_UNDERLYING:
case COMPOUND_MINT:
@@ -166,7 +167,7 @@ void compound_plugin_call(int message, void *parameters) {
// Only case where we use the compound contract decimals
case COMPOUND_REDEEM:
context->decimals = msg->token1->decimals;
context->decimals = msg->item1->token.decimals;
msg->result = ETH_PLUGIN_RESULT_OK;
break;

View File

@@ -0,0 +1,136 @@
#include "erc1155_plugin.h"
#include "eth_plugin_internal.h"
static const uint8_t ERC1155_APPROVE_FOR_ALL_SELECTOR[SELECTOR_SIZE] = {0xa2, 0x2c, 0xb4, 0x65};
static const uint8_t ERC1155_SAFE_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0xf2, 0x42, 0x43, 0x2a};
static const uint8_t ERC1155_SAFE_BATCH_TRANSFER[SELECTOR_SIZE] = {0xf2, 0x42, 0x43, 0x2a};
const uint8_t *const ERC1155_SELECTORS[NUM_ERC1155_SELECTORS] = {
ERC1155_APPROVE_FOR_ALL_SELECTOR,
ERC1155_SAFE_TRANSFER_SELECTOR,
ERC1155_SAFE_BATCH_TRANSFER,
};
static void handle_init_contract(void *parameters) {
ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
uint8_t i;
for (i = 0; i < NUM_ERC1155_SELECTORS; i++) {
if (memcmp((uint8_t *) PIC(ERC1155_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) {
context->selectorIndex = i;
break;
}
}
// No selector found.
if (i == NUM_ERC1155_SELECTORS) {
PRINTF("Unknown erc1155 selector %.*H\n", SELECTOR_SIZE, msg->selector);
msg->result = ETH_PLUGIN_RESULT_FALLBACK;
return;
}
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case SAFE_TRANSFER:
case SAFE_BATCH_TRANSFER:
context->next_param = FROM;
break;
case SET_APPROVAL_FOR_ALL:
context->next_param = OPERATOR;
break;
default:
PRINTF("Unsupported selector index: %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void handle_finalize(void *parameters) {
ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
if (context->selectorIndex != SAFE_BATCH_TRANSFER) {
msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
} else {
msg->tokenLookup1 = NULL;
}
msg->tokenLookup2 = NULL;
switch (context->selectorIndex) {
case SAFE_TRANSFER:
msg->numScreens = 4;
break;
case SET_APPROVAL_FOR_ALL:
case SAFE_BATCH_TRANSFER:
msg->numScreens = 3;
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}
// Check if some ETH is attached to this tx
if (!allzeroes((void *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value))) {
// Those functions are not payable so return an error.
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}
msg->uiType = ETH_UI_TYPE_GENERIC;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_provide_info(void *parameters) {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_query_contract_id(void *parameters) {
ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
strlcpy(msg->name, "NFT", msg->nameLength);
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
strlcpy(msg->version, "Allowance", msg->versionLength);
break;
case SAFE_TRANSFER:
case SAFE_BATCH_TRANSFER:
strlcpy(msg->version, "Transfer", msg->versionLength);
break;
default:
PRINTF("Unsupported selector %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void erc1155_plugin_call(int message, void *parameters) {
switch (message) {
case ETH_PLUGIN_INIT_CONTRACT: {
handle_init_contract(parameters);
} break;
case ETH_PLUGIN_PROVIDE_PARAMETER: {
handle_provide_parameter_1155(parameters);
} break;
case ETH_PLUGIN_FINALIZE: {
handle_finalize(parameters);
} break;
case ETH_PLUGIN_PROVIDE_INFO: {
handle_provide_info(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_ID: {
handle_query_contract_id(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_UI: {
handle_query_contract_ui_1155(parameters);
} break;
default:
PRINTF("Unhandled message %d\n", message);
break;
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <string.h>
#include "eth_plugin_handler.h"
#include "shared_context.h"
#include "ethUtils.h"
#include "utils.h"
// Internal plugin for EIP 1155: https://eips.ethereum.org/EIPS/eip-1155
#define NUM_ERC1155_SELECTORS 3
typedef enum {
SET_APPROVAL_FOR_ALL,
SAFE_TRANSFER,
SAFE_BATCH_TRANSFER,
} erc1155_selector_t;
typedef enum {
FROM,
TO,
TOKEN_IDS_OFFSET,
TOKEN_IDS_LENGTH,
TOKEN_ID,
VALUE_OFFSET,
VALUE_LENGTH,
VALUE,
OPERATOR,
APPROVED,
NONE,
} erc1155_selector_field;
typedef struct erc1155_context_t {
uint8_t address[ADDRESS_LENGTH];
uint8_t tokenId[INT256_LENGTH];
uint8_t value[INT256_LENGTH];
uint32_t valueOffset;
uint32_t tokenIdsOffset;
uint32_t targetOffset;
bool approved;
erc1155_selector_field next_param;
uint8_t selectorIndex;
} erc1155_context_t;
// TODO: Find out why there is a duplicate if we remove 1155 suffix
void handle_provide_parameter_1155(void *parameters);
void handle_query_contract_ui_1155(void *parameters);

View File

@@ -0,0 +1,106 @@
#include "erc1155_plugin.h"
#include "eth_plugin_internal.h"
static void handle_safe_transfer(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
switch (context->next_param) {
case FROM:
context->next_param = TO;
break;
case TO:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->next_param = VALUE;
break;
case VALUE:
copy_parameter(context->value, msg->parameter, sizeof(context->value));
context->next_param = NONE;
break;
default:
// Some extra data might be present so don't error.
break;
}
}
static void handle_batch_transfer(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
switch (context->next_param) {
case FROM:
context->next_param = TO;
break;
case TO:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_IDS_OFFSET:
context->tokenIdsOffset = U4BE(msg->parameter, PARAMETER_LENGTH - 4);
context->next_param = VALUE_OFFSET;
break;
case VALUE_OFFSET:
context->targetOffset = context->tokenIdsOffset;
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->targetOffset = context->valueOffset;
context->next_param = VALUE;
break;
case VALUE:
copy_parameter(context->value, msg->parameter, sizeof(context->value));
context->targetOffset = 0;
context->next_param = NONE;
default:
// Some extra data might be present so don't error.
break;
}
}
static void handle_approval_for_all(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
switch (context->next_param) {
case OPERATOR:
context->next_param = APPROVED;
copy_address(context->address, msg->parameter, sizeof(context->address));
break;
case APPROVED:
context->approved = msg->parameter[PARAMETER_LENGTH - 1];
context->next_param = NONE;
break;
default:
PRINTF("Param %d not supported\n", context->next_param);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_provide_parameter_1155(void *parameters) {
ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
PRINTF("erc1155 plugin provide parameter %d %.*H\n",
msg->parameterOffset,
PARAMETER_LENGTH,
msg->parameter);
msg->result = ETH_PLUGIN_RESULT_SUCCESSFUL;
if (context->targetOffset > SELECTOR_SIZE &&
context->targetOffset != msg->parameterOffset - SELECTOR_SIZE) {
return;
}
switch (context->selectorIndex) {
case SAFE_TRANSFER:
handle_safe_transfer(msg, context);
break;
case SAFE_BATCH_TRANSFER:
handle_batch_transfer(msg, context);
break;
case SET_APPROVAL_FOR_ALL:
handle_approval_for_all(msg, context);
break;
default:
PRINTF("Selector index %d not supported\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}

View File

@@ -0,0 +1,135 @@
#include "erc1155_plugin.h"
static void set_approval_for_all_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
switch (msg->screenIndex) {
case 0:
if (context->approved) {
strlcpy(msg->title, "Allow", msg->titleLength);
} else {
strlcpy(msg->title, "Revoke", msg->titleLength);
}
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "To Manage ALL", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_transfer_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
switch (msg->screenIndex) {
case 0:
// What will be displayed on the screen is:
// | Send |
// | `X` `COLLECTION_NAME` |
// where `X` is `value`
strlcpy(msg->title, "Send", msg->titleLength);
uint256_to_decimal(context->value, sizeof(context->value), msg->msg, msg->msgLength);
strlcat(msg->msg, " ", msg->msgLength);
if (msg->item1) {
strlcat(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcat(msg->msg, "Items", msg->msgLength);
}
break;
case 1:
strlcpy(msg->title, "To", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "NFT ID", msg->titleLength);
uint256_to_decimal(context->tokenId,
sizeof(context->tokenId),
msg->msg,
msg->msgLength);
break;
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_batch_transfer_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "Send NFTs From", msg->titleLength);
uint256_to_decimal(context->value, sizeof(context->value), msg->msg, msg->msgLength);
strlcat(msg->msg, " Different Collections", msg->msgLength);
break;
case 1:
strlcpy(msg->title, "To", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_query_contract_ui_1155(void *parameters) {
ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
set_approval_for_all_ui(msg, context);
break;
case SAFE_TRANSFER:
set_transfer_ui(msg, context);
break;
case SAFE_BATCH_TRANSFER:
set_batch_transfer_ui(msg, context);
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
PRINTF("Unsupported selector index %d\n", context->selectorIndex);
break;
}
}

View File

@@ -159,16 +159,16 @@ void erc20_plugin_call(int message, void *parameters) {
}
} break;
case ETH_PLUGIN_PROVIDE_TOKEN: {
ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
case ETH_PLUGIN_PROVIDE_INFO: {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
erc20_parameters_t *context = (erc20_parameters_t *) msg->pluginContext;
PRINTF("erc20 plugin provide token 1: %d - 2: %d\n",
(msg->token1 != NULL),
(msg->token2 != NULL));
if (msg->token1 != NULL) {
(msg->item1 != NULL),
(msg->item2 != NULL));
if (msg->item1 != NULL) {
context->target = TARGET_ADDRESS;
strlcpy(context->ticker, msg->token1->ticker, MAX_TICKER_LEN);
context->decimals = msg->token1->decimals;
strlcpy(context->ticker, msg->item1->token.ticker, MAX_TICKER_LEN);
context->decimals = msg->item1->token.decimals;
if (context->selectorIndex == ERC20_APPROVE) {
if (check_contract(context)) {
context->target = TARGET_CONTRACT;

View File

@@ -1,151 +1,146 @@
#include <string.h>
#include "erc721_plugin.h"
#include "eth_plugin_internal.h"
#include "eth_plugin_handler.h"
#include "shared_context.h"
#include "ethUtils.h"
#include "utils.h"
typedef struct erc721_parameters_t {
uint8_t selectorIndex;
uint8_t address[ADDRESS_LENGTH];
uint8_t tokenId[INT256_LENGTH];
// tokenDefinition_t *tokenSelf;
// tokenDefinition_t *tokenAddress;
} erc721_parameters_t;
static const uint8_t ERC721_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x13, 0x37, 0x42, 0x42};
static const uint8_t ERC721_APPROVE_FOR_ALL_SELECTOR[SELECTOR_SIZE] = {0xa2, 0x2c, 0xb4, 0x65};
static const uint8_t ERC721_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0x23, 0xb8, 0x72, 0xdd};
static const uint8_t ERC721_SAFE_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0x42, 0x84, 0x2e, 0x0e};
static const uint8_t ERC721_SAFE_TRANSFER_DATA_SELECTOR[SELECTOR_SIZE] = {0xf2, 0x42, 0x43, 0x2a};
bool erc721_plugin_available_check() {
#ifdef HAVE_STARKWARE
if (quantumSet) {
switch (dataContext.tokenContext.quantumType) {
case STARK_QUANTUM_ERC721:
case STARK_QUANTUM_MINTABLE_ERC721:
return true;
default:
return false;
const uint8_t *const ERC721_SELECTORS[NUM_ERC721_SELECTORS] = {
ERC721_APPROVE_SELECTOR,
ERC721_APPROVE_FOR_ALL_SELECTOR,
ERC721_TRANSFER_SELECTOR,
ERC721_SAFE_TRANSFER_SELECTOR,
ERC721_SAFE_TRANSFER_DATA_SELECTOR,
};
static void handle_init_contract(void *parameters) {
ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
uint8_t i;
for (i = 0; i < NUM_ERC721_SELECTORS; i++) {
if (memcmp((uint8_t *) PIC(ERC721_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) {
context->selectorIndex = i;
break;
}
}
return false;
#endif
// No selector found.
if (i == NUM_ERC721_SELECTORS) {
PRINTF("Unknown erc721 selector %.*H\n", SELECTOR_SIZE, msg->selector);
msg->result = ETH_PLUGIN_RESULT_FALLBACK;
return;
}
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
case APPROVE:
context->next_param = OPERATOR;
break;
case SAFE_TRANSFER:
case SAFE_TRANSFER_DATA:
case TRANSFER:
context->next_param = FROM;
break;
default:
PRINTF("Unsupported selector index: %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void handle_finalize(void *parameters) {
ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
msg->tokenLookup2 = NULL;
switch (context->selectorIndex) {
case TRANSFER:
case SAFE_TRANSFER:
case SAFE_TRANSFER_DATA:
case SET_APPROVAL_FOR_ALL:
msg->numScreens = 3;
break;
case APPROVE:
msg->numScreens = 4;
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}
// Check if some ETH is attached to this tx
if (!allzeroes((void *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value))) {
// Set Approval for All is not payable
if (context->selectorIndex == SET_APPROVAL_FOR_ALL) {
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
} else {
// Add an additional screen
msg->numScreens++;
}
}
msg->uiType = ETH_UI_TYPE_GENERIC;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_provide_info(void *parameters) {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_query_contract_id(void *parameters) {
ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
strlcpy(msg->name, "NFT", msg->nameLength);
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
case APPROVE:
strlcpy(msg->version, "Allowance", msg->versionLength);
break;
case SAFE_TRANSFER:
case SAFE_TRANSFER_DATA:
case TRANSFER:
strlcpy(msg->version, "Transfer", msg->versionLength);
break;
default:
PRINTF("Unsupported selector %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void erc721_plugin_call(int message, void *parameters) {
switch (message) {
case ETH_PLUGIN_INIT_CONTRACT: {
ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
// enforce that ETH amount should be 0
if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) {
PRINTF("Err: Transaction amount is not 0 for erc721 approval\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
} else {
size_t i;
for (i = 0; i < NUM_ERC721_SELECTORS; i++) {
if (memcmp((uint8_t *) PIC(ERC721_SELECTORS[i]),
msg->selector,
SELECTOR_SIZE) == 0) {
context->selectorIndex = i;
break;
}
}
if (i == NUM_ERC721_SELECTORS) {
PRINTF("Unknown erc721 selector %.*H\n", SELECTOR_SIZE, msg->selector);
break;
}
if (msg->dataSize != 4 + 32 + 32) {
PRINTF("Invalid erc721 approval data size %d\n", msg->dataSize);
break;
}
PRINTF("erc721 plugin init\n");
msg->result = ETH_PLUGIN_RESULT_OK;
}
handle_init_contract(parameters);
} break;
case ETH_PLUGIN_PROVIDE_PARAMETER: {
ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
PRINTF("erc721 plugin provide parameter %d %.*H\n",
msg->parameterOffset,
32,
msg->parameter);
switch (msg->parameterOffset) {
case 4:
memmove(context->address, msg->parameter + 32 - 20, 20);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
case 4 + 32:
memmove(context->tokenId, msg->parameter, 32);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
default:
PRINTF("Unhandled parameter offset\n");
break;
}
handle_provide_parameter(parameters);
} break;
case ETH_PLUGIN_FINALIZE: {
ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
PRINTF("erc721 plugin finalize\n");
msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
msg->tokenLookup2 = context->address;
msg->numScreens = 3;
msg->uiType = ETH_UI_TYPE_GENERIC;
msg->result = ETH_PLUGIN_RESULT_OK;
handle_finalize(parameters);
} break;
case ETH_PLUGIN_PROVIDE_TOKEN: {
ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
PRINTF("erc721 plugin provide token dest: %d - address: %d\n",
(msg->token1 != NULL),
(msg->token2 != NULL));
// context->tokenSelf = msg->token1;
// context->tokenAddress = msg->token2;
msg->result = ETH_PLUGIN_RESULT_OK;
case ETH_PLUGIN_PROVIDE_INFO: {
handle_provide_info(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_ID: {
ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
strlcpy(msg->name, "Allowance", msg->nameLength);
strlcpy(msg->version, "", msg->versionLength);
msg->result = ETH_PLUGIN_RESULT_OK;
handle_query_contract_id(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_UI: {
ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "Contract Name", msg->titleLength);
getEthDisplayableAddress(tmpContent.txContent.destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
case 1:
strlcpy(msg->title, "NFT Contract", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
case 2:
strlcpy(msg->title, "TokenID", msg->titleLength);
snprintf(msg->msg, 70, "0x%.*H", 32, context->tokenId);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
default:
break;
}
handle_query_contract_ui(parameters);
} break;
default:
PRINTF("Unhandled message %d\n", message);
break;
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <string.h>
#include "eth_plugin_handler.h"
#include "shared_context.h"
#include "ethUtils.h"
#include "utils.h"
// Internal plugin for EIP 721: https://eips.ethereum.org/EIPS/eip-721
#define NUM_ERC721_SELECTORS 5
typedef enum {
APPROVE,
SET_APPROVAL_FOR_ALL,
TRANSFER,
SAFE_TRANSFER,
SAFE_TRANSFER_DATA,
} erc721_selector_t;
typedef enum {
FROM,
TO,
DATA,
TOKEN_ID,
OPERATOR,
APPROVED,
NONE,
} erc721_selector_field;
typedef struct erc721_context_t {
uint8_t address[ADDRESS_LENGTH];
uint8_t tokenId[INT256_LENGTH];
bool approved;
erc721_selector_field next_param;
uint8_t selectorIndex;
} erc721_context_t;
void handle_provide_parameter(void *parameters);
void handle_query_contract_ui(void *parameters);

View File

@@ -0,0 +1,93 @@
#include "erc721_plugin.h"
#include "eth_plugin_internal.h"
static void handle_approve(ethPluginProvideParameter_t *msg, erc721_context_t *context) {
switch (context->next_param) {
case OPERATOR:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->next_param = NONE;
break;
default:
PRINTF("Unhandled parameter offset\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
// `strict` will set msg->result to ERROR if parsing continues after `TOKEN_ID` has been parsed.
static void handle_transfer(ethPluginProvideParameter_t *msg,
erc721_context_t *context,
bool strict) {
switch (context->next_param) {
case FROM:
context->next_param = TO;
break;
case TO:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->next_param = NONE;
break;
default:
if (strict) {
PRINTF("Param %d not supported\n", context->next_param);
msg->result = ETH_PLUGIN_RESULT_ERROR;
}
break;
}
}
static void handle_approval_for_all(ethPluginProvideParameter_t *msg, erc721_context_t *context) {
switch (context->next_param) {
case OPERATOR:
context->next_param = APPROVED;
copy_address(context->address, msg->parameter, sizeof(context->address));
break;
case APPROVED:
context->next_param = NONE;
context->approved = msg->parameter[PARAMETER_LENGTH - 1];
break;
default:
PRINTF("Param %d not supported\n", context->next_param);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_provide_parameter(void *parameters) {
ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
PRINTF("erc721 plugin provide parameter %d %.*H\n",
msg->parameterOffset,
PARAMETER_LENGTH,
msg->parameter);
msg->result = ETH_PLUGIN_RESULT_SUCCESSFUL;
switch (context->selectorIndex) {
case APPROVE:
handle_approve(msg, context);
break;
case SAFE_TRANSFER:
case TRANSFER:
handle_transfer(msg, context, true);
break;
case SAFE_TRANSFER_DATA:
// Set `strict` to `false` because additional data might be present.
handle_transfer(msg, context, false);
break;
case SET_APPROVAL_FOR_ALL:
handle_approval_for_all(msg, context);
break;
default:
PRINTF("Selector index %d not supported\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}

View File

@@ -0,0 +1,166 @@
#include "erc721_plugin.h"
static void set_approval_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "Allow", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "To Spend Your", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "NFT ID", msg->titleLength);
uint256_to_decimal(context->tokenId,
sizeof(context->tokenId),
msg->msg,
msg->msgLength);
break;
case 4:
strlcpy(msg->title, "And send", msg->titleLength);
amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value),
WEI_TO_ETHER,
msg->network_ticker,
msg->msg,
msg->msgLength);
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_approval_for_all_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
switch (msg->screenIndex) {
case 0:
if (context->approved) {
strlcpy(msg->title, "Allow", msg->titleLength);
} else {
strlcpy(msg->title, "Revoke", msg->titleLength);
}
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "To Manage ALL", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "And send", msg->titleLength);
amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value),
WEI_TO_ETHER,
msg->network_ticker,
msg->msg,
msg->msgLength);
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_transfer_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "To", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "Collection Name", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not Found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "NFT ID", msg->titleLength);
uint256_to_decimal(context->tokenId,
sizeof(context->tokenId),
msg->msg,
msg->msgLength);
break;
case 4:
strlcpy(msg->title, "And send", msg->titleLength);
amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value),
WEI_TO_ETHER,
msg->network_ticker,
msg->msg,
msg->msgLength);
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_query_contract_ui(void *parameters) {
ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case APPROVE:
set_approval_ui(msg, context);
break;
case SET_APPROVAL_FOR_ALL:
set_approval_for_all_ui(msg, context);
break;
case SAFE_TRANSFER_DATA:
case SAFE_TRANSFER:
case TRANSFER:
set_transfer_ui(msg, context);
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
PRINTF("Unsupported selector index %d\n", context->selectorIndex);
break;
}
}

View File

@@ -188,8 +188,9 @@ bool starkware_verify_asset_id(uint8_t *tmp32, uint8_t *tokenId, bool assetTypeO
if (quantumSet) {
cx_sha3_t sha3;
tokenDefinition_t *currentToken = NULL;
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
currentToken = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
currentToken =
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
}
cx_keccak_init(&sha3, 256);
compute_token_id(&sha3,
@@ -214,9 +215,9 @@ bool starkware_verify_asset_id(uint8_t *tmp32, uint8_t *tokenId, bool assetTypeO
bool starkware_verify_token(uint8_t *token) {
if (quantumSet) {
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
tokenDefinition_t *currentToken =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
if (memcmp(token + 32 - 20, currentToken->address, 20) != 0) {
PRINTF("Token not matching got %.*H\n", 20, token + 32 - 20);
PRINTF("Current token %.*H\n", 20, currentToken->address);
@@ -235,7 +236,7 @@ bool starkware_verify_token(uint8_t *token) {
bool starkware_verify_quantum(uint8_t *quantum) {
if (quantumSet) {
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
if (memcmp(quantum, dataContext.tokenContext.quantum, 32) != 0) {
PRINTF("Quantum not matching got %.*H\n", 32, quantum);
PRINTF("Current quantum %.*H\n", 32, dataContext.tokenContext.quantum);
@@ -301,7 +302,7 @@ void starkware_print_amount(uint8_t *amountData,
char *ticker = chainConfig->coinName;
if ((amountData == NULL) ||
(forEscape && (dataContext.tokenContext.quantumIndex == MAX_TOKEN))) {
(forEscape && (dataContext.tokenContext.quantumIndex == MAX_ITEMS))) {
decimals = WEI_TO_ETHER;
if (!forEscape) {
convertUint256BE(tmpContent.txContent.value.value,
@@ -312,7 +313,7 @@ void starkware_print_amount(uint8_t *amountData,
}
} else {
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
decimals = token->decimals;
ticker = token->ticker;
readu256BE(amountData, &amountPre);
@@ -334,9 +335,9 @@ void starkware_print_amount(uint8_t *amountData,
void starkware_print_ticker(char *destination, size_t destinationLength) {
char *ticker = chainConfig->coinName;
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
ticker = token->ticker;
}
strlcpy(destination, ticker, destinationLength);
@@ -345,9 +346,9 @@ void starkware_print_ticker(char *destination, size_t destinationLength) {
// TODO : rewrite as independant code
void starkware_print_asset_contract(char *destination, size_t destinationLength) {
// token has been validated to be present previously
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
getEthDisplayableAddress(token->address,
destination,
destinationLength,

View File

@@ -1,11 +1,11 @@
#!/bin/bash
# FILL THESE WITH YOUR OWN SDKs PATHS
# NANOS_SDK=
# NANOX_SDK=
NANOS_SDK=$TWO
NANOX_SDK=$X
# list of apps required by tests that we want to build here
appnames=("ethereum" "ethereum_classic")
appnames=("ethereum")
# create elfs folder if it doesn't exist
mkdir -p elfs
@@ -18,17 +18,17 @@ for app in "${appnames[@]}"
do
echo "**Building $app for Nano S..."
make clean BOLOS_SDK=$NANOS_SDK
make -j DEBUG=1 ALLOW_DATA=1 BOLOS_SDK=$NANOS_SDK CHAIN=$app
make -j ALLOW_DATA=1 NFT_TESTING_KEY=1 DEBUG=1 BOLOS_SDK=$NANOS_SDK CHAIN=$app
cp bin/app.elf "tests/elfs/${app}_nanos.elf"
done
echo "*Building elfs for Nano X..."
for app in "${appnames[@]}"
do
echo "**Building $app for Nano X..."
make clean BOLOS_SDK=$NANOX_SDK
make -j DEBUG=1 ALLOW_DATA=1 BOLOS_SDK=$NANOX_SDK CHAIN=$app
cp bin/app.elf "tests/elfs/${app}_nanox.elf"
done
# echo "*Building elfs for Nano X..."
# for app in "${appnames[@]}"
# do
# echo "**Building $app for Nano X..."
# make clean BOLOS_SDK=$NANOX_SDK
# make -j DEBUG=1 BOLOS_SDK=$NANOX_SDK CHAIN=$app
# cp bin/app.elf "tests/elfs/${app}_nanox.elf"
# done
echo "done"

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

View File

@@ -0,0 +1,27 @@
import "core-js/stable";
import "regenerator-runtime/runtime";
import { waitForAppScreen, zemu, txFromEtherscan } from './test.fixture';
import { TransportStatusError } from "@ledgerhq/errors";
// -------------------
// TODO: Actually write the tests
test.skip('[Nano S] Transfer 1155', zemu("nanos", async (sim, eth) => {
const rawTx = ""
const serializedTx = txFromEtherscan(rawTx);
// with ETH need to test
// const serializedTx = txFromEtherscan("0x02f901350182022f8459682f0085246ad7eb3182de2994424db67b40b15ed85475c3f29dedf601b6ee75b283424242b8c4f242432a000000000000000000000000dcdb88f3754b2841093d9348a2d02df8cf06314c000000000000000000000000df9fb2eff1f2871caeeb94bf262ffba84efddddc0000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000c001a0c4283f86dc852e43e9fd1077b448c63fec76bdeb44dfac977730725e41fa3676a0543b2d2f99f65fb20cd548964eee94b1c1865919f4574c7089d8b95678b667c2");
const tx = eth.signTransaction(
"44'/60'/1'/0/0",
serializedTx,
);
await waitForAppScreen(sim);
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer_ethereum', [12, 0]);
await expect(tx).resolves.toEqual({
});
}));

130
tests/src/erc721.test.js Normal file
View File

@@ -0,0 +1,130 @@
import "core-js/stable";
import "regenerator-runtime/runtime";
import { waitForAppScreen, zemu, txFromEtherscan } from './test.fixture';
test('[Nano S] Transfer erc721', zemu("nanos", async (sim, eth) => {
// https://etherscan.io/tx/0x73cec4fc07de3a24ba42e8756e13b7ddfa9bd449126c37640881195e8ea9e679
// Modified to put a bigger token id
const rawTx = "0x02f8d101058459682f0085233da9943e8301865b94bd3531da5cf5857e7cfaa92426877b022e612cf880b86423b872dd0000000000000000000000004cc568b73c0dcf8e90db26d7fd3a6cfadca108a3000000000000000000000000d4c9b20950c3eca38fc1f33f54bdf9694e488799ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a094c8632fe7277aa8c54cea9d81a15911cfa4970a2bf7356d14d04cc5afbcdab7a013a77b8c79e5d9b2b35edb3c44db3bb41b92f5c463ff126bf19d213b2b9ba8b5"
const serializedTx = txFromEtherscan(rawTx);
const tx = eth.signTransaction(
"44'/60'/1'/0/0",
serializedTx,
);
await waitForAppScreen(sim);
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer', [12, 0]);
await expect(tx).resolves.toEqual({
"r": "59f6a9769cff66eed8be8716c44d39808d1e43f3aa0bb97538e124dba4bc4565",
"s": "662990a841c663a165ba9a83e5cc95c03a999b851e0bd6d296aa70a0f7c96c1a",
"v": "01",
});
}));
test('[Nano S] Transfer erc721 with attached ETH', zemu("nanos", async (sim, eth) => {
const rawTx = "0x02f8d601058459682f0085233da9943e8301865b94bd3531da5cf5857e7cfaa92426877b022e612cf8854242424242b86423b872dd0000000000000000000000004cc568b73c0dcf8e90db26d7fd3a6cfadca108a3000000000000000000000000d4c9b20950c3eca38fc1f33f54bdf9694e4887990000000000000000000000000000000000000000000000000000000000000f21c080a094c8632fe7277aa8c54cea9d81a15911cfa4970a2bf7356d14d04cc5afbcdab7a013a77b8c79e5d9b2b35edb3c44db3bb41b92f5c463ff126bf19d213b2b9ba8b5"
const serializedTx = txFromEtherscan(rawTx);
const tx = eth.signTransaction(
"44'/60'/1'/0/0",
serializedTx,
);
await waitForAppScreen(sim);
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer_with_eth', [13, 0]);
await expect(tx).resolves.toEqual({
"r": "9c42e10b49f3ee315ab2d5f7ad96f1068c75578734b66504716cc279ead27d47",
"s": "45dde78470ad75ffdb27a799b87e4934e2e10e98dbc6f88bc4a9bc19c4de86bc",
"v": "00",
});
}));
test('[Nano S] set approval for all erc721', zemu("nanos", async (sim, eth) => {
// https://etherscan.io/tx/0x86b936db53c19fddf26b8d145f165e1c7fdff3c0f8b14b7758a38f0400cfd93f
const rawTx = "0x02f8b0010c8459682f00852cfbb00ee682b54294d4e4078ca3495de5b1d4db434bebc5a98619778280b844a22cb4650000000000000000000000002efcb1e8d4472d35356b9747bea8a051eac2e3f50000000000000000000000000000000000000000000000000000000000000001c001a0c5b8c024c15ca1452ce8a13eacfcdc25f1c6f581bb3ce570e82f08f1b792b3aca03be4dba0302ae190618a72eb1202ce3af3e17afd7d8a94345a48cae5cad15541";
const serializedTx = txFromEtherscan(rawTx);
const tx = eth.signTransaction(
"44'/60'/1'/0/0",
serializedTx,
);
await waitForAppScreen(sim);
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_approval_for_all', [12, 0]);
await expect(tx).resolves.toEqual({
"r": "8b6a70a1fe76d8e9b1250531a17eb1e367936732d4dfb9befc81a5031b271dc8",
"s": "7658d7151bba0d8504cea2013bead64cb8407dc6be1fca829bb9594b56f679af",
"v": "00",
});
}));
// NOT DONE
test.skip('[Nano S] approval erc721', zemu("nanos", async (sim, eth) => {
// INCORRECT, need to find / create an approval tx
const rawTx = "";
const serializedTx = txFromEtherscan(rawTx);
const tx = eth.signTransaction(
"44'/60'/1'/0/0",
serializedTx,
);
await waitForAppScreen(sim);
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_approval', [12, 0]);
await expect(tx).resolves.toEqual({
});
}));
test('[Nano S] safe transfer erc721', zemu("nanos", async (sim, eth) => {
// https://etherscan.io/tx/0x1ee6ce9be1c9fe6f030ff124ba8c88a410223c022816547e4b3fedd3a4d2dc1e
const rawTx = "0xf8cc82028585077359400083061a8094d4e4078ca3495de5b1d4db434bebc5a98619778280b86442842e0e000000000000000000000000c352b534e8b987e036a93539fd6897f53488e56a0000000000000000000000000a9287d9339c175cd3ea0ad4228f734a9f75ee6200000000000000000000000000000000000000000000000000000000000000621ca08250f4b2c8f28c5e4ef621dba4682990d1faf930c8cb6d032c6e7278e8951d92a03c1e1f6d63ed339041f69f24c6c0968ba26f244f779cb4fa7a468f3ba3d3e06e";
const serializedTx = txFromEtherscan(rawTx);
const tx = eth.signTransaction(
"44'/60'/1'/0/0",
serializedTx,
);
await waitForAppScreen(sim);
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_safe_transfer', [10, 0]);
await expect(tx).resolves.toEqual({
"r": "b936684d5d0e99e09701021fb73ae9403f2ec79414d822d42c5bd1c0a2118f1a",
"s": "23e517c6cac998f392d179be2fe7c3225f0e0a165b1af85548da5d6acaa73c4f",
"v": "25",
});
}));
// NOT DONE
test.skip('[Nano S] safe transfer with data erc721', zemu("nanos", async (sim, eth) => {
// need to find or create a safe transfer with data on etherscan?
const rawTx = "";
const serializedTx = txFromEtherscan(rawTx);
const tx = eth.signTransaction(
"44'/60'/1'/0/0",
serializedTx,
);
await waitForAppScreen(sim);
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_safe_transfer_with_data', [12, 0]);
await expect(tx).resolves.toEqual({
});
}));

View File

@@ -1,5 +1,6 @@
import Zemu from '@zondax/zemu';
import Eth from '@ledgerhq/hw-app-eth';
import {RLP} from "ethers/lib/utils";
const transactionUploadDelay = 60000;
@@ -36,6 +37,35 @@ const NANOX_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanox.elf");
const TIMEOUT = 1000000;
// Generates a serializedTransaction from a rawHexTransaction copy pasted from etherscan.
function txFromEtherscan(rawTx) {
// Remove 0x prefix
rawTx = rawTx.slice(2);
let txType = rawTx.slice(0, 2);
if (txType == "02" || txType == "01") {
// Remove "02" prefix
rawTx = rawTx.slice(2);
} else {
txType = "";
}
let decoded = RLP.decode("0x" + rawTx);
if (txType != "") {
decoded = decoded.slice(0, decoded.length - 3); // remove v, r, s
} else {
decoded[decoded.length - 1] = "0x"; // empty
decoded[decoded.length - 2] = "0x"; // empty
decoded[decoded.length - 3] = "0x01"; // chainID 1
}
// Encode back the data, drop the '0x' prefix
let encoded = RLP.encode(decoded).slice(2);
// Don't forget to prepend the txtype
return txType + encoded;
}
function zemu(device, func) {
return async () => {
jest.setTimeout(TIMEOUT);
@@ -71,5 +101,6 @@ module.exports = {
NANOX_CLONE_ELF_PATH,
sim_options_nanos,
sim_options_nanox,
TIMEOUT
TIMEOUT,
txFromEtherscan,
}