From 38da718b00d8178cfe907d315fbb16f589ea27dd Mon Sep 17 00:00:00 2001 From: BTChip github Date: Tue, 22 Sep 2020 09:22:49 +0200 Subject: [PATCH 01/13] Plugins support WIP --- Makefile | 4 +- examples/signTx.py | 9 +- src/base64.c | 213 +++++++ src/base64.h | 105 ++++ src/eth_plugin_handler.c | 250 ++++++++ src/eth_plugin_handler.h | 19 + src/eth_plugin_interface.h | 171 +++++ src/eth_plugin_internal.c | 52 ++ src/eth_plugin_internal.h | 30 + src/eth_plugin_ui.c | 173 +++++ src/main.c | 2 - src/shared_context.h | 29 +- src_features/signTx/cmd_signTx.c | 1 - src_features/signTx/logic_signTx.c | 592 +++++++----------- .../ui_flow_stark_deposit.c | 125 ---- .../ui_flow_stark_escape.c | 135 ---- .../ui_flow_stark_register.c | 123 ---- .../ui_flow_stark_verify_escape.c | 76 --- .../ui_flow_stark_verifyVaultId.c | 115 ---- .../ui_flow_stark_withdrawal.c | 109 ---- src_plugins/erc20/erc20_plugin.c | 63 ++ src_plugins/starkware/starkware_plugin.c | 444 +++++++++++++ 22 files changed, 1777 insertions(+), 1063 deletions(-) create mode 100644 src/base64.c create mode 100644 src/base64.h create mode 100644 src/eth_plugin_handler.c create mode 100644 src/eth_plugin_handler.h create mode 100644 src/eth_plugin_interface.h create mode 100644 src/eth_plugin_internal.c create mode 100644 src/eth_plugin_internal.h create mode 100644 src/eth_plugin_ui.c delete mode 100644 src_features/stark_contract_deposit/ui_flow_stark_deposit.c delete mode 100644 src_features/stark_contract_escape/ui_flow_stark_escape.c delete mode 100644 src_features/stark_contract_register/ui_flow_stark_register.c delete mode 100644 src_features/stark_contract_verifyEscape/ui_flow_stark_verify_escape.c delete mode 100644 src_features/stark_contract_verifyVaultId/ui_flow_stark_verifyVaultId.c delete mode 100644 src_features/stark_contract_withdrawal/ui_flow_stark_withdrawal.c create mode 100644 src_plugins/erc20/erc20_plugin.c create mode 100644 src_plugins/starkware/starkware_plugin.c diff --git a/Makefile b/Makefile index 7b70775..31568e0 100755 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ APP_LOAD_PARAMS += --path "45'" #APP_LOAD_PARAMS += --path "1517992542'/1101353413'" APPVERSION_M=1 -APPVERSION_N=4 +APPVERSION_N=5 APPVERSION_P=0 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) APP_LOAD_FLAGS= --appFlags 0x240 --dep Ethereum:$(APPVERSION) @@ -292,7 +292,7 @@ LDLIBS += -lm -lgcc -lc include $(BOLOS_SDK)/Makefile.glyphs ### variables processed by the common makefile.rules of the SDK to grab source files and include dirs -APP_SOURCE_PATH += src_common src src_features +APP_SOURCE_PATH += src_common src src_features src_plugins SDK_SOURCE_PATH += lib_stusb lib_stusb_impl lib_u2f SDK_SOURCE_PATH += lib_ux ifeq ($(TARGET_NAME),TARGET_NANOX) diff --git a/examples/signTx.py b/examples/signTx.py index 4366b71..2f4d3d2 100755 --- a/examples/signTx.py +++ b/examples/signTx.py @@ -59,6 +59,7 @@ parser.add_argument('--amount', help="Amount to send in ether", required=True) parser.add_argument('--to', help="Destination address", type=str, required=True) parser.add_argument('--path', help="BIP 32 path to sign with") parser.add_argument('--data', help="Data to add, hex encoded") +parser.add_argument('--descriptor', help="Optional descriptor") args = parser.parse_args() if args.path == None: @@ -85,13 +86,19 @@ tx = Transaction( encodedTx = encode(tx, Transaction) +dongle = getDongle(True) + +if args.descriptor != None: + descriptor = binascii.unhexlify(args.descriptor) + apdu = struct.pack(">BBBBB", 0xE0, 0x0A, 0x00, 0x00, len(descriptor)) + descriptor + dongle.exchange(bytes(apdu)) + donglePath = parse_bip32_path(args.path) apdu = bytearray.fromhex("e0040000") apdu.append(len(donglePath) + 1 + len(encodedTx)) apdu.append(len(donglePath) // 4) apdu += donglePath + encodedTx -dongle = getDongle(True) result = dongle.exchange(bytes(apdu)) # Needs to recover (main.c:1121) diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..78bf86a --- /dev/null +++ b/src/base64.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* ==================================================================== + * Copyright (c) 1995-1999 The Apache Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + */ + +/* Base64 encoder/decoder. Originally Apache file ap_base64.c + */ + +#include + +#include "base64.h" + +#if 0 + +/* aaaack but it's fast and const should make it shared text page. */ +static const unsigned char pr2six[256] = +{ + /* ASCII table */ + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}; + +int Base64decode_len(const char *bufcoded) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while (pr2six[*(bufin++)] <= 63); + + nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; + nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + return nbytesdecoded + 1; +} + +int Base64decode(char *bufplain, const char *bufcoded) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register unsigned char *bufout; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while (pr2six[*(bufin++)] <= 63); + nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; + nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + bufout = (unsigned char *) bufplain; + bufin = (const unsigned char *) bufcoded; + + while (nprbytes > 4) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + bufin += 4; + nprbytes -= 4; + } + + /* Note: (nprbytes == 1) would be an error, so just ingore that case */ + if (nprbytes > 1) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + } + if (nprbytes > 2) { + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + } + if (nprbytes > 3) { + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + } + + *(bufout++) = '\0'; + nbytesdecoded -= (4 - nprbytes) & 3; + return nbytesdecoded; +} + +#endif + +static const char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int Base64encode_len(int len) +{ + return ((len + 2) / 3 * 4) + 1; +} + +int Base64encode(char *encoded, const char *string, int len) +{ + int i; + char *p; + + p = encoded; + for (i = 0; i < len - 2; i += 3) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2) | + ((int) (string[i + 2] & 0xC0) >> 6)]; + *p++ = basis_64[string[i + 2] & 0x3F]; + } + if (i < len) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + if (i == (len - 1)) { + *p++ = basis_64[((string[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + *p++ = '\0'; + return p - encoded; +} diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..c69b93b --- /dev/null +++ b/src/base64.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* ==================================================================== + * Copyright (c) 1995-1999 The Apache Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + */ + + + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int Base64encode_len(int len); +int Base64encode(char * coded_dst, const char *plain_src,int len_plain_src); + +#if 0 + +int Base64decode_len(const char * coded_src); +int Base64decode(char * plain_dst, const char *coded_src); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif //_BASE64_H_ diff --git a/src/eth_plugin_handler.c b/src/eth_plugin_handler.c new file mode 100644 index 0000000..4b9da53 --- /dev/null +++ b/src/eth_plugin_handler.c @@ -0,0 +1,250 @@ +#include +#include "eth_plugin_handler.h" +#include "eth_plugin_internal.h" +#include "shared_context.h" +#include "base64.h" + +void eth_plugin_prepare_init(ethPluginInitContract_t *init, uint8_t *selector, uint32_t dataSize) { + memset((uint8_t*)init, 0, sizeof(ethPluginInitContract_t)); + init->selector = selector; + init->dataSize = dataSize; +} + +void eth_plugin_prepare_provide_parameter(ethPluginProvideParameter_t *provideParameter, uint8_t *parameter, uint32_t parameterOffset) { + memset((uint8_t*)provideParameter, 0, sizeof(ethPluginProvideParameter_t)); + provideParameter->parameter = parameter; + provideParameter->parameterOffset = parameterOffset; +} + +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, tokenDefinition_t *token1, tokenDefinition_t *token2) { + memset((uint8_t*)provideToken, 0, sizeof(ethPluginProvideToken_t)); + provideToken->token1 = token1; + provideToken->token2 = token2; +} + +void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID, char *name, uint32_t nameLength, char *version, uint32_t versionLength) { + memset((uint8_t*)queryContractID, 0, sizeof(ethQueryContractID_t)); + queryContractID->name = name; + queryContractID->nameLength = nameLength; + queryContractID->version = version; + queryContractID->versionLength = versionLength; +} + +void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI, uint8_t screenIndex, char *title, uint32_t titleLength, char *msg, uint32_t msgLength) { + memset((uint8_t*)queryContractUI, 0, sizeof(ethQueryContractUI_t)); + queryContractUI->screenIndex = screenIndex; + queryContractUI->title = title; + queryContractUI->titleLength = titleLength; + queryContractUI->msg = msg; + queryContractUI->msgLength = msgLength; +} + +int eth_plugin_perform_init(uint8_t *contractAddress, ethPluginInitContract_t *init) { + uint8_t i; + dataContext.tokenContext.pluginAvailable = 0; + // Handle hardcoded plugin list + PRINTF("Selector %.*H\n", 4, init->selector); + for (i=0; iselector, PIC(selectors[j]), SELECTOR_SIZE) == 0) { + strcpy(dataContext.tokenContext.pluginName, INTERNAL_ETH_PLUGINS[i].alias); + dataContext.tokenContext.pluginAvailable = 1; + contractAddress = NULL; + break; + } + } + } + +#ifndef TARGET_BLUE + + // Do not handle a plugin if running in swap mode + if (called_from_swap && (contractAddress != NULL)) { + PRINTF("eth_plug_init aborted in swap mode\n"); + return 0; + } + for (;;) { + PRINTF("eth_plugin_init\n"); + if (contractAddress != NULL) { + PRINTF("Trying address %.*H\n", 20, contractAddress); + } + else { + PRINTF("Trying alias %s\n", dataContext.tokenContext.pluginName); + } + int status = eth_plugin_call(contractAddress, ETH_PLUGIN_INIT_CONTRACT, (void*)init); + if (!status) { + return 0; + } + if (status == ETH_PLUGIN_RESULT_OK) { + break; + } + if (status == ETH_PLUGIN_RESULT_OK_ALIAS) { + contractAddress = NULL; + } + } + PRINTF("eth_plugin_init ok %s\n", dataContext.tokenContext.pluginName); + dataContext.tokenContext.pluginAvailable = 1; + return 1; + +#else + + // Disable plugins on Ledger Blue for the time being + + return 0; +#endif + +} + +int eth_plugin_call(uint8_t *contractAddress, int method, void *parameter) { + ethPluginSharedRW_t pluginRW; + ethPluginSharedRO_t pluginRO; + char tmp[PLUGIN_ID_LENGTH]; + char *alias; + uint8_t i; + + pluginRW.sha3 = &global_sha3; + pluginRO.txContent = &tmpContent.txContent; + + if (contractAddress == NULL) { + if (!dataContext.tokenContext.pluginAvailable) { + PRINTF("Cached plugin call but no plugin available\n"); + return 0; + } + alias = dataContext.tokenContext.pluginName; + } + else { + Base64encode(tmp, (char*)contractAddress, 20); + alias = tmp; + } + + // Prepare the call + + switch(method) { + case ETH_PLUGIN_INIT_CONTRACT: + ((ethPluginInitContract_t*)parameter)->pluginSharedRW = &pluginRW; + ((ethPluginInitContract_t*)parameter)->pluginSharedRO = &pluginRO; + ((ethPluginInitContract_t*)parameter)->pluginContext = (uint8_t*)&dataContext.tokenContext.pluginContext; + ((ethPluginInitContract_t*)parameter)->pluginContextLength = sizeof(dataContext.tokenContext.pluginContext); + ((ethPluginInitContract_t*)parameter)->alias = dataContext.tokenContext.pluginName; + break; + case ETH_PLUGIN_PROVIDE_PARAMETER: + ((ethPluginProvideParameter_t*)parameter)->pluginSharedRW = &pluginRW; + ((ethPluginProvideParameter_t*)parameter)->pluginSharedRO = &pluginRO; + ((ethPluginProvideParameter_t*)parameter)->pluginContext = (uint8_t*)&dataContext.tokenContext.pluginContext; + break; + case ETH_PLUGIN_FINALIZE: + ((ethPluginFinalize_t*)parameter)->pluginSharedRW = &pluginRW; + ((ethPluginFinalize_t*)parameter)->pluginSharedRO = &pluginRO; + ((ethPluginFinalize_t*)parameter)->pluginContext = (uint8_t*)&dataContext.tokenContext.pluginContext; + break; + case ETH_PLUGIN_PROVIDE_TOKEN: + ((ethPluginProvideToken_t*)parameter)->pluginSharedRW = &pluginRW; + ((ethPluginProvideToken_t*)parameter)->pluginSharedRO = &pluginRO; + ((ethPluginProvideToken_t*)parameter)->pluginContext = (uint8_t*)&dataContext.tokenContext.pluginContext; + break; + case ETH_PLUGIN_QUERY_CONTRACT_ID: + ((ethQueryContractID_t*)parameter)->pluginSharedRW = &pluginRW; + ((ethQueryContractID_t*)parameter)->pluginSharedRO = &pluginRO; + ((ethQueryContractID_t*)parameter)->pluginContext = (uint8_t*)&dataContext.tokenContext.pluginContext; + break; + case ETH_PLUGIN_QUERY_CONTRACT_UI: + ((ethQueryContractUI_t*)parameter)->pluginSharedRW = &pluginRW; + ((ethQueryContractUI_t*)parameter)->pluginSharedRO = &pluginRO; + ((ethQueryContractUI_t*)parameter)->pluginContext = (uint8_t*)&dataContext.tokenContext.pluginContext; + break; + default: + PRINTF("Unknown plugin method %d\n", method); + return 0; + } + + // Perform the call + + for (i=0; iresult) { + case ETH_PLUGIN_RESULT_OK: + if (contractAddress != NULL) { + strcpy(dataContext.tokenContext.pluginName, alias); + } + break; + case ETH_PLUGIN_RESULT_OK_ALIAS: + break; + default: + return 0; + } + break; + case ETH_PLUGIN_PROVIDE_PARAMETER: + switch (((ethPluginProvideParameter_t*)parameter)->result) { + case ETH_PLUGIN_RESULT_OK: + case ETH_PLUGIN_RESULT_FALLBACK: + break; + default: + return 0; + } + break; + case ETH_PLUGIN_FINALIZE: + switch (((ethPluginFinalize_t*)parameter)->result) { + case ETH_PLUGIN_RESULT_OK: + case ETH_PLUGIN_RESULT_FALLBACK: + break; + default: + return 0; + } + break; + case ETH_PLUGIN_PROVIDE_TOKEN: + switch (((ethPluginProvideToken_t*)parameter)->result) { + case ETH_PLUGIN_RESULT_OK: + case ETH_PLUGIN_RESULT_FALLBACK: + break; + default: + return 0; + } + break; + case ETH_PLUGIN_QUERY_CONTRACT_ID: + if (((ethQueryContractID_t*)parameter)->result != ETH_PLUGIN_RESULT_OK) { + return 0; + } + break; + case ETH_PLUGIN_QUERY_CONTRACT_UI: + if (((ethQueryContractUI_t*)parameter)->result != ETH_PLUGIN_RESULT_OK) { + return 0; + } + break; + default: + return 0; + } + + return 1; +} diff --git a/src/eth_plugin_handler.h b/src/eth_plugin_handler.h new file mode 100644 index 0000000..e8a1a25 --- /dev/null +++ b/src/eth_plugin_handler.h @@ -0,0 +1,19 @@ +#ifndef __ETH_PLUGIN_HANDLER_H__ + +#include "eth_plugin_interface.h" + +void eth_plugin_prepare_init(ethPluginInitContract_t *init, uint8_t *selector, uint32_t dataSize); +void eth_plugin_prepare_provide_parameter(ethPluginProvideParameter_t *provideParameter, uint8_t *parameter, uint32_t parameterOffset); +void eth_plugin_prepare_finalize(ethPluginFinalize_t *finalize); +void eth_plugin_prepare_provide_token(ethPluginProvideToken_t *provideToken, tokenDefinition_t *token1, tokenDefinition_t *token2); +void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID, char *name, uint32_t nameLength, char *version, uint32_t versionLength); +void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI, uint8_t screenIndex, char *title, uint32_t titleLength, char *msg, uint32_t msgLength); + +int eth_plugin_perform_init(uint8_t *contractAddress, ethPluginInitContract_t *init); +// NULL for cached address, or base contract address +int eth_plugin_call(uint8_t *contractAddress, int method, void *parameter); + +void plugin_ui_start(void); + +#endif + diff --git a/src/eth_plugin_interface.h b/src/eth_plugin_interface.h new file mode 100644 index 0000000..ae51cbf --- /dev/null +++ b/src/eth_plugin_interface.h @@ -0,0 +1,171 @@ +#ifndef __ETH_PLUGIN_INTERFACE_H__ + +#define __ETH_PLUGIN_INTERFACE_H__ + +#include "os.h" +#include "cx.h" +#include "ethUstream.h" +#include "tokens.h" + +#define PLUGIN_ID_LENGTH 30 + +typedef enum { + + ETH_PLUGIN_INIT_CONTRACT = 0x0101, + ETH_PLUGIN_PROVIDE_PARAMETER = 0x0102, + ETH_PLUGIN_FINALIZE = 0x0103, + ETH_PLUGIN_PROVIDE_TOKEN = 0x0104, + ETH_PLUGIN_QUERY_CONTRACT_ID = 0x0105, + ETH_PLUGIN_QUERY_CONTRACT_UI = 0x0106 + +} eth_plugin_msg_t; + +typedef enum { + + ETH_PLUGIN_RESULT_ERROR = 0x00, + ETH_PLUGIN_RESULT_OK = 0x01, + ETH_PLUGIN_RESULT_OK_ALIAS = 0x02, + ETH_PLUGIN_RESULT_FALLBACK = 0x03 + +} eth_plugin_result_t; + +typedef enum { + + ETH_UI_TYPE_AMOUNT_ADDRESS = 0x01, + ETH_UI_TYPE_GENERIC = 0x02 + +} eth_ui_type_t; + +typedef void (*PluginCall)(int, void*); + +// Shared objects, read-write + +typedef struct ethPluginSharedRW_t { + + cx_sha3_t *sha3; + +} ethPluginSharedRW_t; + +// Shared objects, read-only + +typedef struct ethPluginSharedRO_t { + + txContent_t *txContent; + +} ethPluginSharedRO_t; + + +// Init Contract + +typedef struct ethPluginInitContract_t { + + // in + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + uint32_t pluginContextLength; + uint8_t *selector; // 4 bytes selector + uint32_t dataSize; + + char *alias; // 29 bytes alias if ETH_PLUGIN_RESULT_OK_ALIAS set + + uint8_t result; + +} ethPluginInitContract_t; + +// Provide parameter + +typedef struct ethPluginProvideParameter_t { + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + uint8_t *parameter; // 32 bytes parameter + uint32_t parameterOffset; + + uint8_t result; + +} ethPluginProvideParameter_t; + +// Finalize + +typedef struct ethPluginFinalize_t { + + ethPluginSharedRW_t *pluginSharedRW; + 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 *amount; // set an uint256 pointer if uiType is UI_AMOUNT_ADDRESS + uint8_t *address; // set to a 20 bytes address pointer if uiType is UI_AMOUNT_ADDRESS + + uint8_t uiType; + uint8_t numScreens; // ignored if uiType is UI_AMOUNT_ADDRESS + uint8_t result; + +} ethPluginFinalize_t; + +// If uiType is UI_AMOUNT_ADDRESS, the amount and address provided by the plugin will be used +// If tokenLookup1 is set, the amount is provided for this token + +// if uiType is UI_TYPE_GENERIC, the ETH application provides tokens if requested then prompts +// for each UI field +// The first field is forced by the ETH app to be the name + version of the plugin handling the request +// The last field is the fee amount + +// Provide token + +typedef struct ethPluginProvideToken_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; + + uint8_t result; + +} ethPluginProvideToken_t; + +// Query Contract name and version + +// This is always called on the non aliased contract + +typedef struct ethQueryContractID_t { + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + + char *name; + uint32_t nameLength; + char *version; + uint32_t versionLength; + + uint8_t result; + +} ethQueryContractID_t; + +// Query Contract UI + +typedef struct ethQueryContractUI_t { + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + uint8_t screenIndex; + char *title; + uint32_t titleLength; + char *msg; + uint32_t msgLength; + + uint8_t result; + +} ethQueryContractUI_t; + +#endif + diff --git a/src/eth_plugin_internal.c b/src/eth_plugin_internal.c new file mode 100644 index 0000000..a7023a9 --- /dev/null +++ b/src/eth_plugin_internal.c @@ -0,0 +1,52 @@ +#include "eth_plugin_internal.h" + +void erc20_plugin_call(int message, void *parameters); +void starkware_plugin_call(int message, void *parameters); + +static const uint8_t const ERC20_SELECTOR[SELECTOR_SIZE] = { 0xa9, 0x05, 0x9c, 0xbb }; + +static const uint8_t* const ERC20_SELECTORS[1] = { ERC20_SELECTOR }; + +#ifdef HAVE_STARKWARE + +static const uint8_t const STARKWARE_REGISTER_ID[SELECTOR_SIZE] = { 0x76, 0x57, 0x18, 0xd7 }; +static const uint8_t const STARKWARE_DEPOSIT_TOKEN_ID[SELECTOR_SIZE] = { 0x00, 0xae, 0xef, 0x8a }; +static const uint8_t const STARKWARE_DEPOSIT_ETH_ID[SELECTOR_SIZE] = { 0xe2, 0xbb, 0xb1, 0x58 }; +static const uint8_t const STARKWARE_DEPOSIT_CANCEL_ID[SELECTOR_SIZE] = { 0xc7, 0xfb, 0x11, 0x7c }; +static const uint8_t const STARKWARE_DEPOSIT_RECLAIM_ID[SELECTOR_SIZE] = { 0x4e, 0xab, 0x38, 0xf4 }; +static const uint8_t const STARKWARE_WITHDRAW_ID[SELECTOR_SIZE] = { 0x2e, 0x1a, 0x7d, 0x4d }; +static const uint8_t const STARKWARE_FULL_WITHDRAWAL_ID[SELECTOR_SIZE] = { 0x27, 0x6d, 0xd1, 0xde }; +static const uint8_t const STARKWARE_FREEZE_ID[SELECTOR_SIZE] = { 0xb9, 0x10, 0x72, 0x09 }; +static const uint8_t const STARKWARE_ESCAPE_ID[SELECTOR_SIZE] = { 0x9e, 0x3a, 0xda, 0xc4 }; +static const uint8_t const STARKWARE_VERIFY_ESCAPE_ID[SELECTOR_SIZE] = { 0x2d, 0xd5, 0x30, 0x06 }; + +const uint8_t* const STARKWARE_SELECTORS[NUM_STARKWARE_SELECTORS] = { + STARKWARE_REGISTER_ID, STARKWARE_DEPOSIT_TOKEN_ID, STARKWARE_DEPOSIT_ETH_ID, + STARKWARE_DEPOSIT_CANCEL_ID, STARKWARE_DEPOSIT_RECLAIM_ID, STARKWARE_WITHDRAW_ID, + STARKWARE_FULL_WITHDRAWAL_ID, STARKWARE_FREEZE_ID, STARKWARE_ESCAPE_ID, + STARKWARE_VERIFY_ESCAPE_ID +}; + +#endif + +// All internal alias names start with 'minus' + +const internalEthPlugin_t const INTERNAL_ETH_PLUGINS[NUM_INTERNAL_PLUGINS] = { + { + ERC20_SELECTORS, + 1, + "-erc20", + erc20_plugin_call + }, + +#ifdef HAVE_STARKWARE + + { + STARKWARE_SELECTORS, + 10, + "-strk", + starkware_plugin_call + }, + +#endif +}; diff --git a/src/eth_plugin_internal.h b/src/eth_plugin_internal.h new file mode 100644 index 0000000..59b76a5 --- /dev/null +++ b/src/eth_plugin_internal.h @@ -0,0 +1,30 @@ +#ifndef __ETH_PLUGIN_INTERNAL_H__ + +#include "eth_plugin_interface.h" + +#define SELECTOR_SIZE 4 + +typedef struct internalEthPlugin_t { + const uint8_t **selectors; + uint8_t num_selectors; + char alias[7]; + PluginCall impl; +} internalEthPlugin_t; + +#ifdef HAVE_STARKWARE + +#define NUM_INTERNAL_PLUGINS 2 + +#define NUM_STARKWARE_SELECTORS 10 + +extern const uint8_t* const STARKWARE_SELECTORS[NUM_STARKWARE_SELECTORS]; + +#else + +#define NUM_INTERNAL_PLUGINS 1 + +#endif + +extern internalEthPlugin_t const INTERNAL_ETH_PLUGINS[NUM_INTERNAL_PLUGINS]; + +#endif diff --git a/src/eth_plugin_ui.c b/src/eth_plugin_ui.c new file mode 100644 index 0000000..d63fb9e --- /dev/null +++ b/src/eth_plugin_ui.c @@ -0,0 +1,173 @@ +#include "shared_context.h" +#ifdef HAVE_UX_FLOW +#include "ui_flow.h" +#endif +#include "ui_callbacks.h" +#include "eth_plugin_handler.h" + +typedef enum { + + PLUGIN_UI_INSIDE = 0, + PLUGIN_UI_OUTSIDE + +} plugin_ui_state_t; + +void computeFees(char *displayBuffer, uint32_t displayBufferSize); + +void plugin_ui_get_id() { + ethQueryContractID_t pluginQueryContractID; + eth_plugin_prepare_query_contract_ID(&pluginQueryContractID, strings.tmp.tmp, sizeof(strings.tmp.tmp), strings.tmp.tmp2, sizeof(strings.tmp.tmp2)); + // Query the original contract for ID if it's not an internal alias + if (!eth_plugin_call( + (dataContext.tokenContext.pluginName[0] == '-' ? NULL : tmpContent.txContent.destination), + ETH_PLUGIN_QUERY_CONTRACT_ID, (void*)&pluginQueryContractID)) { + PRINTF("Plugin query contract ID call failed\n"); + io_seproxyhal_touch_tx_cancel(NULL); + } +} + +void plugin_ui_get_item() { + ethQueryContractUI_t pluginQueryContractUI; + eth_plugin_prepare_query_contract_UI(&pluginQueryContractUI, dataContext.tokenContext.pluginUiCurrentItem, strings.tmp.tmp, sizeof(strings.tmp.tmp), strings.tmp.tmp2, sizeof(strings.tmp.tmp2)); + if (!eth_plugin_call(NULL, ETH_PLUGIN_QUERY_CONTRACT_UI, (void*)&pluginQueryContractUI)) { + PRINTF("Plugin query contract UI call failed\n"); + io_seproxyhal_touch_tx_cancel(NULL); + } +} + +void display_next_plugin_item(bool entering) { + if (entering) { + if (dataContext.tokenContext.pluginUiState == PLUGIN_UI_OUTSIDE) { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_INSIDE; + dataContext.tokenContext.pluginUiCurrentItem = 0; + plugin_ui_get_item(); + ux_flow_next(); + } + else { + if (dataContext.tokenContext.pluginUiCurrentItem > 0) { + dataContext.tokenContext.pluginUiCurrentItem--; + plugin_ui_get_item(); + ux_flow_next(); + } + else { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; + dataContext.tokenContext.pluginUiCurrentItem = 0; + ux_flow_prev(); + } + } + } + else { + if (dataContext.tokenContext.pluginUiState == PLUGIN_UI_OUTSIDE) { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_INSIDE; + plugin_ui_get_item(); + ux_flow_prev(); + // Reset multi page layout to the first page + G_ux.layout_paging.current = 0; + ux_layout_paging_redisplay(G_ux.stack_count-1); + } + else { + if (dataContext.tokenContext.pluginUiCurrentItem < dataContext.tokenContext.pluginUiMaxItems - 1) { + dataContext.tokenContext.pluginUiCurrentItem++; + plugin_ui_get_item(); + ux_flow_prev(); + // Reset multi page layout to the first page + G_ux.layout_paging.current = 0; + ux_layout_paging_redisplay(G_ux.stack_count-1); + } + else { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; + ux_flow_next(); + } + } + } +} + +void plugin_ui_compute_fees() { + computeFees(strings.common.maxFee, sizeof(strings.common.maxFee)); +} + +UX_FLOW_DEF_NOCB( + ux_plugin_approval_intro_step, + pnn, + { + &C_icon_eye, + "Review", + "contract call", + }); + +UX_STEP_NOCB_INIT( + ux_plugin_approval_id_step, + bnnn_paging, + plugin_ui_get_id(), + { + .title = strings.tmp.tmp, + .text = strings.tmp.tmp2 + }); + +UX_STEP_INIT( + ux_plugin_approval_before_step, + NULL, + NULL, + { + display_next_plugin_item(true); + }); + +UX_FLOW_DEF_NOCB( + ux_plugin_approval_display_step, + bnnn_paging, + { + .title = strings.tmp.tmp, + .text = strings.tmp.tmp2 + }); + +UX_STEP_INIT( + ux_plugin_approval_after_step, + NULL, + NULL, + { + display_next_plugin_item(false); + }); + +UX_STEP_NOCB_INIT( + ux_plugin_approval_fees_step, + bnnn_paging, + plugin_ui_compute_fees(), + { + .title = "Max fees", + .text = strings.common.maxFee + }); + +UX_FLOW_DEF_VALID( + ux_plugin_approval_ok_step, + pbb, + io_seproxyhal_touch_tx_ok(NULL), + { + &C_icon_validate_14, + "Accept", + "and send", + }); +UX_FLOW_DEF_VALID( + ux_plugin_approval_cancel_step, + pb, + io_seproxyhal_touch_tx_cancel(NULL), + { + &C_icon_crossmark, + "Reject", + }); + +const ux_flow_step_t * const ux_plugin_approval_flow [] = { + &ux_plugin_approval_intro_step, + &ux_plugin_approval_id_step, + &ux_plugin_approval_before_step, + &ux_plugin_approval_display_step, + &ux_plugin_approval_after_step, + &ux_plugin_approval_fees_step, + &ux_plugin_approval_ok_step, + &ux_plugin_approval_cancel_step +}; + +void plugin_ui_start() { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; + dataContext.tokenContext.pluginUiCurrentItem = 0; + ux_flow_init(0, ux_plugin_approval_flow, NULL); +} diff --git a/src/main.c b/src/main.c index cf3d4df..24b5377 100644 --- a/src/main.c +++ b/src/main.c @@ -52,7 +52,6 @@ uint8_t dataAllowed; uint8_t contractDetails; uint8_t appState; bool dataPresent; -contract_call_t contractProvisioned; bool called_from_swap; #ifdef HAVE_STARKWARE bool quantumSet; @@ -70,7 +69,6 @@ void reset_app_context() { //PRINTF("!!RESET_APP_CONTEXT\n"); appState = APP_STATE_IDLE; os_memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN); - contractProvisioned = CONTRACT_NONE; called_from_swap = false; #ifdef HAVE_STARKWARE quantumSet = false; diff --git a/src/shared_context.h b/src/shared_context.h index d04cb6e..0fc4485 100644 --- a/src/shared_context.h +++ b/src/shared_context.h @@ -14,6 +14,7 @@ #include "uint256.h" #include "tokens.h" #include "chainConfig.h" +#include "eth_plugin_interface.h" #define MAX_BIP32_PATH 10 @@ -30,23 +31,25 @@ typedef struct internalStorage_t { } internalStorage_t; typedef struct tokenContext_t { -#ifdef HAVE_STARKWARE - uint8_t data[4 + 32 + 32 + 32 + 32]; -#else - uint8_t data[4 + 32 + 32]; -#endif - uint32_t dataFieldPos; + char pluginName[PLUGIN_ID_LENGTH]; + uint8_t pluginAvailable; + + uint8_t data[32]; + uint8_t fieldIndex; + uint8_t fieldOffset; + + uint8_t pluginUiMaxItems; + uint8_t pluginUiCurrentItem; + uint8_t pluginUiState; + + uint8_t pluginContext[3 * 32]; + #ifdef HAVE_STARKWARE uint8_t quantum[32]; uint8_t quantumIndex; #endif -} tokenContext_t; -typedef struct rawDataContext_t { - uint8_t data[32]; - uint8_t fieldIndex; - uint8_t fieldOffset; -} rawDataContext_t; +} tokenContext_t; typedef struct publicKeyContext_t { cx_ecfp_public_key_t publicKey; @@ -104,7 +107,6 @@ typedef struct starkContext_t { typedef union { tokenContext_t tokenContext; - rawDataContext_t rawDataContext; #ifdef HAVE_STARKWARE starkContext_t starkContext; #endif @@ -165,7 +167,6 @@ extern uint8_t dataAllowed; extern uint8_t contractDetails; extern bool dataPresent; extern uint8_t appState; -extern contract_call_t contractProvisioned; #ifdef HAVE_STARKWARE extern bool quantumSet; #endif diff --git a/src_features/signTx/cmd_signTx.c b/src_features/signTx/cmd_signTx.c index 506a185..a3d12d0 100644 --- a/src_features/signTx/cmd_signTx.c +++ b/src_features/signTx/cmd_signTx.c @@ -34,7 +34,6 @@ void handleSign(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength dataLength -= 4; } dataPresent = false; - contractProvisioned = CONTRACT_NONE; initTx(&txContext, &global_sha3, &tmpContent.txContent, customProcessor, NULL); } else diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index d72f7da..3c009a5 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -5,39 +5,10 @@ #ifdef HAVE_STARKWARE #include "stark_utils.h" #endif - -#define TOKEN_TRANSFER_DATA_SIZE 4 + 32 + 32 -static const uint8_t const TOKEN_TRANSFER_ID[] = { 0xa9, 0x05, 0x9c, 0xbb }; - -#define ALLOWANCE_DATA_SIZE 4 + 32 + 32 -static const uint8_t const ALLOWANCE_ID[] = { 0x09, 0x5e, 0xa7, 0xb3 }; +#include "eth_plugin_handler.h" #define ERR_SILENT_MODE_CHECK_FAILED 0x6001 -#ifdef HAVE_STARKWARE - -#define STARKWARE_REGISTER_DATA_SIZE 4 + 32 -static const uint8_t const STARKWARE_REGISTER_ID[] = { 0x76, 0x57, 0x18, 0xd7 }; -#define STARKWARE_DEPOSIT_TOKEN_DATA_SIZE 4 + 32 + 32 + 32 -static const uint8_t const STARKWARE_DEPOSIT_TOKEN_ID[] = { 0x00, 0xae, 0xef, 0x8a }; -#define STARKWARE_DEPOSIT_ETH_DATA_SIZE 4 + 32 + 32 -static const uint8_t const STARKWARE_DEPOSIT_ETH_ID[] = { 0xe2, 0xbb, 0xb1, 0x58 }; -#define STARKWARE_DEPOSIT_CANCEL_DATA_SIZE 4 + 32 + 32 -static const uint8_t const STARKWARE_DEPOSIT_CANCEL_ID[] = { 0xc7, 0xfb, 0x11, 0x7c }; -#define STARKWARE_DEPOSIT_RECLAIM_DATA_SIZE 4 + 32 + 32 -static const uint8_t const STARKWARE_DEPOSIT_RECLAIM_ID[] = { 0x4e, 0xab, 0x38, 0xf4 }; -#define STARKWARE_WITHDRAW_DATA_SIZE 4 + 32 -static const uint8_t const STARKWARE_WITHDRAW_ID[] = { 0x2e, 0x1a, 0x7d, 0x4d }; -#define STARKWARE_FULL_WITHDRAWAL_DATA_SIZE 4 + 32 -static const uint8_t const STARKWARE_FULL_WITHDRAWAL_ID[] = { 0x27, 0x6d, 0xd1, 0xde }; -#define STARKWARE_FREEZE_DATA_SIZE 4 + 32 -static const uint8_t const STARKWARE_FREEZE_ID[] = { 0xb9, 0x10, 0x72, 0x09 }; -#define STARKWARE_ESCAPE_DATA_SIZE 4 + 32 + 32 + 32 + 32 -static const uint8_t const STARKWARE_ESCAPE_ID[] = { 0x9e, 0x3a, 0xda, 0xc4 }; -static const uint8_t const STARKWARE_VERIFY_ESCAPE_ID[] = { 0x2d, 0xd5, 0x30, 0x06 }; - -#endif - uint32_t splitBinaryParameterPart(char *result, uint8_t *parameter) { uint32_t i; for (i=0; i<8; i++) { @@ -66,191 +37,108 @@ customStatus_e customProcessor(txContext_t *context) { return CUSTOM_NOT_HANDLED; } if (context->currentFieldPos == 0) { + ethPluginInitContract_t pluginInit; // If handling the beginning of the data field, assume that the function selector is present if (context->commandLength < 4) { PRINTF("Missing function selector\n"); return CUSTOM_FAULT; } - // Initial check to see if the call can be processed - if ((context->currentFieldLength == TOKEN_TRANSFER_DATA_SIZE) && - (os_memcmp(context->workBuffer, TOKEN_TRANSFER_ID, 4) == 0) && - (getKnownToken(tmpContent.txContent.destination) != NULL)) { - contractProvisioned = CONTRACT_ERC20; - } - else - if ((context->currentFieldLength == ALLOWANCE_DATA_SIZE) && - (os_memcmp(context->workBuffer, ALLOWANCE_ID, 4) == 0)) { - contractProvisioned = CONTRACT_ALLOWANCE; - } -#ifdef HAVE_STARKWARE - else - if ((context->currentFieldLength >= STARKWARE_REGISTER_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_REGISTER_ID, 4) == 0)) { - contractProvisioned = CONTRACT_STARKWARE_REGISTER; - } - else - if ((context->currentFieldLength == STARKWARE_DEPOSIT_ETH_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_DEPOSIT_ETH_ID, 4) == 0)) { - contractProvisioned = CONTRACT_STARKWARE_DEPOSIT_ETH; - } - else - if ((context->currentFieldLength == STARKWARE_DEPOSIT_TOKEN_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_DEPOSIT_TOKEN_ID, 4) == 0) && - quantumSet) { - contractProvisioned = CONTRACT_STARKWARE_DEPOSIT_TOKEN; - } - else - if ((context->currentFieldLength == STARKWARE_WITHDRAW_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_WITHDRAW_ID, 4) == 0) && - quantumSet) { - contractProvisioned = CONTRACT_STARKWARE_WITHDRAW; - } - else - if ((context->currentFieldLength == STARKWARE_DEPOSIT_CANCEL_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_DEPOSIT_CANCEL_ID, 4) == 0)) { - contractProvisioned = CONTRACT_STARKWARE_DEPOSIT_CANCEL; - } - else - if ((context->currentFieldLength == STARKWARE_DEPOSIT_RECLAIM_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_DEPOSIT_RECLAIM_ID, 4) == 0)) { - contractProvisioned = CONTRACT_STARKWARE_DEPOSIT_RECLAIM; - } - else - if ((context->currentFieldLength == STARKWARE_FULL_WITHDRAWAL_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_FULL_WITHDRAWAL_ID, 4) == 0)) { - contractProvisioned = CONTRACT_STARKWARE_FULL_WITHDRAWAL; - } - else - if ((context->currentFieldLength == STARKWARE_FREEZE_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_FREEZE_ID, 4) == 0)) { - contractProvisioned = CONTRACT_STARKWARE_FREEZE; - } - else - if ((context->currentFieldLength == STARKWARE_ESCAPE_DATA_SIZE) && - (os_memcmp(context->workBuffer, STARKWARE_ESCAPE_ID, 4) == 0) && - quantumSet) { - contractProvisioned = CONTRACT_STARKWARE_ESCAPE; - } - else - if (os_memcmp(context->workBuffer, STARKWARE_VERIFY_ESCAPE_ID, 4) == 0) { - contractProvisioned = CONTRACT_STARKWARE_VERIFY_ESCAPE; + eth_plugin_prepare_init(&pluginInit, context->workBuffer, context->currentFieldLength); + dataContext.tokenContext.pluginAvailable = eth_plugin_perform_init(tmpContent.txContent.destination, &pluginInit); + PRINTF("pluginAvailable %d\n", dataContext.tokenContext.pluginAvailable); + if (dataContext.tokenContext.pluginAvailable) { + dataContext.tokenContext.fieldIndex = 0; + dataContext.tokenContext.fieldOffset = 0; + copyTxData(context, NULL, 4); } + } + uint32_t blockSize; + uint32_t copySize; + uint32_t fieldPos = context->currentFieldPos; + if (fieldPos == 0) { // not reached if a plugin is available + if (!N_storage.dataAllowed) { + PRINTF("Data field forbidden\n"); + return CUSTOM_FAULT; + } + if (!N_storage.contractDetails) { + return CUSTOM_NOT_HANDLED; + } + dataContext.tokenContext.fieldIndex = 0; + dataContext.tokenContext.fieldOffset = 0; + blockSize = 4; + } + else { + blockSize = 32 - (dataContext.tokenContext.fieldOffset % 32); + } -#endif - } - // Sanity check - // Also handle exception that only need to process the beginning of the data - if ((contractProvisioned != CONTRACT_NONE) && -#ifdef HAVE_STARKWARE - (contractProvisioned != CONTRACT_STARKWARE_VERIFY_ESCAPE) && - (contractProvisioned != CONTRACT_STARKWARE_REGISTER) && -#endif - (context->currentFieldLength > sizeof(dataContext.tokenContext.data))) { - PRINTF("Data field overflow - dropping customization\n"); - contractProvisioned = CONTRACT_NONE; - } - PRINTF("contractProvisioned %d\n", contractProvisioned); - if (contractProvisioned != CONTRACT_NONE) { - if (context->currentFieldPos < context->currentFieldLength) { - uint32_t copySize = MIN(context->commandLength, - context->currentFieldLength - context->currentFieldPos); - // Handle the case where we only need to handle the beginning of the data parameter - if ((context->currentFieldPos + copySize) < sizeof(dataContext.tokenContext.data)) { - copyTxData(context, - dataContext.tokenContext.data + context->currentFieldPos, + // Sanity check + if ((context->currentFieldLength - fieldPos) < blockSize) { + PRINTF("Unconsistent data\n"); + return CUSTOM_FAULT; + } + + copySize = (context->commandLength < blockSize ? context->commandLength : blockSize); + + PRINTF("currentFieldPos %d copySize %d\n", context->currentFieldPos, copySize); + + copyTxData(context, + dataContext.tokenContext.data + dataContext.tokenContext.fieldOffset, copySize); - } - else { - if (context->currentFieldPos < sizeof(dataContext.tokenContext.data)) { - uint32_t copySize2 = sizeof(dataContext.tokenContext.data) - context->currentFieldPos; - copyTxData(context, - dataContext.tokenContext.data + context->currentFieldPos, - copySize2); - copySize -= copySize2; - } - copyTxData(context, NULL, copySize); - } - } - if (context->currentFieldPos == context->currentFieldLength) { - context->currentField++; - context->processingField = false; - } - return CUSTOM_HANDLED; - } - else { - uint32_t blockSize; - uint32_t copySize; - uint32_t fieldPos = context->currentFieldPos; - if (fieldPos == 0) { - if (!N_storage.dataAllowed) { - PRINTF("Data field forbidden\n"); + + if (context->currentFieldPos == context->currentFieldLength) { + context->currentField++; + context->processingField = false; + } + + dataContext.tokenContext.fieldOffset += copySize; + + if (copySize == blockSize) { + // Can process or display + if (dataContext.tokenContext.pluginAvailable) { + ethPluginProvideParameter_t pluginProvideParameter; + eth_plugin_prepare_provide_parameter(&pluginProvideParameter, + dataContext.tokenContext.data, + dataContext.tokenContext.fieldIndex * 32 + 4); + if (!eth_plugin_call(NULL, ETH_PLUGIN_PROVIDE_PARAMETER, (void*)&pluginProvideParameter)) { + PRINTF("Plugin parameter call failed\n"); return CUSTOM_FAULT; } - if (!N_storage.contractDetails) { - return CUSTOM_NOT_HANDLED; - } - dataContext.rawDataContext.fieldIndex = 0; - dataContext.rawDataContext.fieldOffset = 0; - blockSize = 4; - } - else { - if (!N_storage.contractDetails) { - return CUSTOM_NOT_HANDLED; - } - blockSize = 32 - (dataContext.rawDataContext.fieldOffset % 32); - } - - // Sanity check - if ((context->currentFieldLength - fieldPos) < blockSize) { - PRINTF("Unconsistent data\n"); - return CUSTOM_FAULT; - } - - copySize = (context->commandLength < blockSize ? context->commandLength : blockSize); - copyTxData(context, - dataContext.rawDataContext.data + dataContext.rawDataContext.fieldOffset, - copySize); - - if (context->currentFieldPos == context->currentFieldLength) { - context->currentField++; - context->processingField = false; - } - - dataContext.rawDataContext.fieldOffset += copySize; - - if (copySize == blockSize) { - // Can display - if (fieldPos != 0) { - dataContext.rawDataContext.fieldIndex++; - } - dataContext.rawDataContext.fieldOffset = 0; - if (fieldPos == 0) { - array_hexstr(strings.tmp.tmp, dataContext.rawDataContext.data, 4); - ux_flow_init(0, ux_confirm_selector_flow, NULL); - } - else { - uint32_t offset = 0; - uint32_t i; - snprintf(strings.tmp.tmp2, sizeof(strings.tmp.tmp2), "Field %d", dataContext.rawDataContext.fieldIndex); - for (i=0; i<4; i++) { - offset += splitBinaryParameterPart(strings.tmp.tmp + offset, dataContext.rawDataContext.data + 8 * i); - if (i != 3) { - strings.tmp.tmp[offset++] = ':'; - } - } - ux_flow_init(0, ux_confirm_parameter_flow, NULL); - } - } - else { + dataContext.tokenContext.fieldIndex++; + dataContext.tokenContext.fieldOffset = 0; return CUSTOM_HANDLED; - } + } - return CUSTOM_SUSPENDED; - } + if (fieldPos != 0) { + dataContext.tokenContext.fieldIndex++; + } + dataContext.tokenContext.fieldOffset = 0; + if (fieldPos == 0) { + array_hexstr(strings.tmp.tmp, dataContext.tokenContext.data, 4); + ux_flow_init(0, ux_confirm_selector_flow, NULL); + } + else { + uint32_t offset = 0; + uint32_t i; + snprintf(strings.tmp.tmp2, sizeof(strings.tmp.tmp2), "Field %d", dataContext.tokenContext.fieldIndex); + for (i=0; i<4; i++) { + offset += splitBinaryParameterPart(strings.tmp.tmp + offset, dataContext.tokenContext.data + 8 * i); + if (i != 3) { + strings.tmp.tmp[offset++] = ':'; + } + } + ux_flow_init(0, ux_confirm_parameter_flow, NULL); + } + } + else { + return CUSTOM_HANDLED; + } + + return CUSTOM_SUSPENDED; } return CUSTOM_NOT_HANDLED; } + void to_uppercase(char* str, unsigned char size){ for (unsigned char i = 0; i < size && str[i] != 0; i++) { @@ -273,117 +161,23 @@ void compareOrCopy(char* preapproved_string, char* parsed_string, bool silent_mo } } -void finalizeParsing(bool direct) { - uint256_t gasPrice, startGas, uint256; - uint32_t i; - char displayBuffer[50]; - uint8_t decimals = WEI_TO_ETHER; - uint8_t *ticker = (uint8_t *)PIC(chainConfig->coinName); - uint8_t *feeTicker = (uint8_t *)PIC(chainConfig->coinName); - uint8_t tickerOffset = 0; +void reportFinalizeError(bool direct) { + reset_app_context(); + if (direct) { + THROW(0x6A80); + } + else { + io_seproxyhal_send_status(0x6A80); + ui_idle(); + } +} + +void computeFees(char *displayBuffer, uint32_t displayBufferSize) { + uint256_t gasPrice, startGas, uint256; + uint8_t *feeTicker = (uint8_t *)PIC(chainConfig->coinName); + uint8_t tickerOffset = 0; + uint32_t i; - // Verify the chain - if (chainConfig->chainId != 0) { - uint32_t v = getV(&tmpContent.txContent); - if (chainConfig->chainId != v) { - reset_app_context(); - PRINTF("Invalid chainId %d expected %d\n", v, chainConfig->chainId); - if (direct) { - THROW(0x6A80); - } - else { - io_seproxyhal_send_status(0x6A80); - ui_idle(); - return; - } - } - } - // Store the hash - cx_hash((cx_hash_t *)&global_sha3, CX_LAST, tmpCtx.transactionContext.hash, 0, tmpCtx.transactionContext.hash, 32); -#ifdef HAVE_STARKWARE - if ((contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_ETH) || - (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_TOKEN) || - (contractProvisioned == CONTRACT_STARKWARE_WITHDRAW) || - (contractProvisioned == CONTRACT_STARKWARE_ESCAPE)) { - // For a deposit / withdrawal / escape, check if the token ID is known or can't parse - uint8_t tokenIdOffset = (4 + ((contractProvisioned == CONTRACT_STARKWARE_ESCAPE) ? 32 + 32 : 0)); - if (quantumSet) { - tokenDefinition_t *currentToken = NULL; - if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) { - currentToken = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex]; - } - compute_token_id(&global_sha3, - (currentToken != NULL ? currentToken->address : NULL), - dataContext.tokenContext.quantum, G_io_apdu_buffer + 100); - if (os_memcmp(dataContext.tokenContext.data + tokenIdOffset, G_io_apdu_buffer + 100, 32) != 0) { - PRINTF("Token ID not matching - computed %.*H\n", 32, G_io_apdu_buffer + 100); - PRINTF("Current quantum %.*H\n", 32, dataContext.tokenContext.quantum); - PRINTF("Requested %.*H\n", 32, dataContext.tokenContext.data + tokenIdOffset); - contractProvisioned = CONTRACT_NONE; - } - } - else { - PRINTF("Quantum not set\n"); - contractProvisioned = CONTRACT_NONE; - } - } -#endif - // If there is a token to process, check if it is well known - if ((contractProvisioned == CONTRACT_ERC20) || (contractProvisioned == CONTRACT_ALLOWANCE)) { - tokenDefinition_t *currentToken = getKnownToken(tmpContent.txContent.destination); - if (currentToken != NULL) { - dataPresent = false; - decimals = currentToken->decimals; - ticker = currentToken->ticker; - tmpContent.txContent.destinationLength = 20; - os_memmove(tmpContent.txContent.destination, dataContext.tokenContext.data + 4 + 12, 20); - os_memmove(tmpContent.txContent.value.value, dataContext.tokenContext.data + 4 + 32, 32); - tmpContent.txContent.value.length = 32; - } - } - else { - if (dataPresent && contractProvisioned == CONTRACT_NONE && !N_storage.dataAllowed) { - reset_app_context(); - PRINTF("Data field forbidden\n"); - if (direct) { - THROW(0x6A80); - } - else { - io_seproxyhal_send_status(0x6A80); - ui_idle(); - return; - } - } - } - // Add address - if (tmpContent.txContent.destinationLength != 0) { - displayBuffer[0] = '0'; - displayBuffer[1] = 'x'; - getEthAddressStringFromBinary(tmpContent.txContent.destination, (uint8_t*)displayBuffer+2, &global_sha3, chainConfig); - compareOrCopy(strings.common.fullAddress, displayBuffer, called_from_swap); - } - else - { - strcpy(strings.common.fullAddress, "Contract"); - } - if ((contractProvisioned == CONTRACT_NONE) || (contractProvisioned == CONTRACT_ERC20) || - (contractProvisioned == CONTRACT_ALLOWANCE)) { - // Add amount in ethers or tokens - if ((contractProvisioned == CONTRACT_ALLOWANCE) && ismaxint(tmpContent.txContent.value.value, 32)) { - i = 0; - tickerOffset = 0; - while (ticker[tickerOffset]) { - displayBuffer[tickerOffset] = ticker[tickerOffset]; - tickerOffset++; - } - strcpy(displayBuffer + tickerOffset, "Unlimited"); - } - else { - amountToString(tmpContent.txContent.value.value, tmpContent.txContent.value.length, decimals, (char*)ticker, displayBuffer, sizeof(displayBuffer)); - } - compareOrCopy(strings.common.fullAmount, displayBuffer, called_from_swap); - } - // Compute maximum fee PRINTF("Max fee\n"); PRINTF("Gasprice %.*H\n", tmpContent.txContent.gasprice.length, tmpContent.txContent.gasprice.value); PRINTF("Startgas %.*H\n", tmpContent.txContent.startgas.length, tmpContent.txContent.startgas.value); @@ -398,7 +192,7 @@ void finalizeParsing(bool direct) { adjustDecimals((char *)(G_io_apdu_buffer + 100), i, (char *)G_io_apdu_buffer, 100, WEI_TO_ETHER); i = 0; tickerOffset=0; - memset(displayBuffer, 0, sizeof(displayBuffer)); + memset(displayBuffer, 0, displayBufferSize); while (feeTicker[tickerOffset]) { displayBuffer[tickerOffset] = feeTicker[tickerOffset]; tickerOffset++; @@ -408,7 +202,128 @@ void finalizeParsing(bool direct) { i++; } displayBuffer[tickerOffset + i] = '\0'; - compareOrCopy(strings.common.maxFee, displayBuffer, called_from_swap); +} + +void finalizeParsing(bool direct) { + char displayBuffer[50]; + uint8_t decimals = WEI_TO_ETHER; + uint8_t *ticker = (uint8_t *)PIC(chainConfig->coinName); + ethPluginFinalize_t pluginFinalize; + tokenDefinition_t *token1 = NULL, *token2 = NULL; + bool genericUI = true; + + // Verify the chain + if (chainConfig->chainId != 0) { + uint32_t v = getV(&tmpContent.txContent); + if (chainConfig->chainId != v) { + reset_app_context(); + PRINTF("Invalid chainId %d expected %d\n", v, chainConfig->chainId); + reportFinalizeError(direct); + if (!direct) { + return; + } + } + } + // Store the hash + cx_hash((cx_hash_t *)&global_sha3, CX_LAST, tmpCtx.transactionContext.hash, 0, tmpCtx.transactionContext.hash, 32); + + // Finalize the plugin handling + if (dataContext.tokenContext.pluginAvailable) { + genericUI = false; + eth_plugin_prepare_finalize(&pluginFinalize); + if (!eth_plugin_call(NULL, ETH_PLUGIN_FINALIZE, (void*)&pluginFinalize)) { + PRINTF("Plugin finalize call failed\n"); + reportFinalizeError(direct); + if (!direct) { + return; + } + } + // Lookup tokens if requested + if (pluginFinalize.tokenLookup1 != NULL) { + ethPluginProvideToken_t pluginProvideToken; + token1 = getKnownToken(pluginFinalize.tokenLookup1); + if (pluginFinalize.tokenLookup2 != NULL) { + token2 = getKnownToken(pluginFinalize.tokenLookup2); + } + eth_plugin_prepare_provide_token(&pluginProvideToken, token1, token2); + if (!eth_plugin_call(NULL, ETH_PLUGIN_PROVIDE_TOKEN, (void*)&pluginProvideToken)) { + PRINTF("Plugin provide token call failed\n"); + reportFinalizeError(direct); + if (!direct) { + return; + } + } + pluginFinalize.result = pluginProvideToken.result; + } + if (pluginFinalize.result != ETH_PLUGIN_RESULT_FALLBACK) { + // Handle the right interface + switch(pluginFinalize.uiType) { + case ETH_UI_TYPE_GENERIC: + dataPresent = false; + dataContext.tokenContext.pluginUiMaxItems = pluginFinalize.numScreens; + break; + case ETH_UI_TYPE_AMOUNT_ADDRESS: + genericUI = true; + dataPresent = false; + if ((pluginFinalize.amount == NULL) || (pluginFinalize.address == NULL)) { + PRINTF("Incorrect amount/address set by plugin\n"); + reportFinalizeError(direct); + if (!direct) { + return; + } + } + memmove(tmpContent.txContent.value.value, pluginFinalize.amount, 32); + tmpContent.txContent.value.length = 32; + memmove(tmpContent.txContent.destination, pluginFinalize.address, 20); + tmpContent.txContent.destinationLength = 20; + if (token1 != NULL) { + decimals = token1->decimals; + ticker = token1->ticker; + } + break; + default: + PRINTF("ui type %d not supported\n", pluginFinalize.uiType); + reportFinalizeError(direct); + if (!direct) { + return; + } + } + } + else { + genericUI = true; + } + } + + if (dataPresent && !N_storage.dataAllowed) { + PRINTF("Data field forbidden\n"); + reportFinalizeError(direct); + if (!direct) { + return; + } + } + // Prepare destination address to display + if (genericUI) { + if (tmpContent.txContent.destinationLength != 0) { + displayBuffer[0] = '0'; + displayBuffer[1] = 'x'; + getEthAddressStringFromBinary(tmpContent.txContent.destination, (uint8_t*)displayBuffer+2, &global_sha3, chainConfig); + compareOrCopy(strings.common.fullAddress, displayBuffer, called_from_swap); + } + else + { + strcpy(strings.common.fullAddress, "Contract"); + } + } + // Prepare amount to display + if (genericUI) { + amountToString(tmpContent.txContent.value.value, tmpContent.txContent.value.length, decimals, (char*)ticker, displayBuffer, sizeof(displayBuffer)); + compareOrCopy(strings.common.fullAmount, displayBuffer, called_from_swap); + } + // Compute maximum fee + if (genericUI) { + computeFees(displayBuffer, sizeof(displayBuffer)); + compareOrCopy(strings.common.maxFee, displayBuffer, called_from_swap); + } bool no_consent = false; @@ -423,57 +338,14 @@ void finalizeParsing(bool direct) { } else{ -#ifdef HAVE_STARKWARE - - if (contractProvisioned == CONTRACT_STARKWARE_REGISTER) { - ux_flow_init(0, ux_approval_starkware_register_flow, NULL); - return; + if (genericUI) { + ux_flow_init(0, + ((dataPresent && !N_storage.contractDetails) ? ux_approval_tx_data_warning_flow : ux_approval_tx_flow), + NULL); } - else - if (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_TOKEN) { - ux_flow_init(0, ux_approval_starkware_deposit_flow, NULL); - return; + else { + plugin_ui_start(); } - else - if (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_ETH) { - ux_flow_init(0, ux_approval_starkware_deposit_flow, NULL); - return; - } - else - if ((contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_CANCEL) || - (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_RECLAIM) || - (contractProvisioned == CONTRACT_STARKWARE_FULL_WITHDRAWAL) || - (contractProvisioned == CONTRACT_STARKWARE_FREEZE)) { - ux_flow_init(0, ux_approval_starkware_verify_vault_id_flow, NULL); - return; - } - else - if (contractProvisioned == CONTRACT_STARKWARE_WITHDRAW) { - ux_flow_init(0, ux_approval_starkware_withdraw_flow, NULL); - return; - } - else - if (contractProvisioned == CONTRACT_STARKWARE_ESCAPE) { - ux_flow_init(0, ux_approval_starkware_escape_flow, NULL); - return; - } - else - if (contractProvisioned == CONTRACT_STARKWARE_VERIFY_ESCAPE) { - ux_flow_init(0, ux_approval_starkware_verify_escape_flow, NULL); - return; - } - -#endif - - if (contractProvisioned == CONTRACT_ALLOWANCE) { - ux_flow_init(0, ux_approval_allowance_flow, NULL); - return; - } - - ux_flow_init(0, - ((dataPresent && !N_storage.contractDetails) ? ux_approval_tx_data_warning_flow : ux_approval_tx_flow), - NULL); } } - diff --git a/src_features/stark_contract_deposit/ui_flow_stark_deposit.c b/src_features/stark_contract_deposit/ui_flow_stark_deposit.c deleted file mode 100644 index 4ce69cf..0000000 --- a/src_features/stark_contract_deposit/ui_flow_stark_deposit.c +++ /dev/null @@ -1,125 +0,0 @@ -#ifdef HAVE_STARKWARE - -#include "shared_context.h" -#include "ui_callbacks.h" -#include "utils.h" - -void prepare_deposit_3() { - uint8_t address[41]; - getEthAddressStringFromBinary(tmpContent.txContent.destination, address, &global_sha3, chainConfig); - strings.common.fullAddress[0] = '0'; - strings.common.fullAddress[1] = 'x'; - os_memmove((unsigned char *)strings.common.fullAddress+2, address, 40); - strings.common.fullAddress[42] = '\0'; -} - -void prepare_deposit_4() { - snprintf(strings.common.fullAddress, 10, "%d", U4BE(dataContext.tokenContext.data, 4 + 32 + 32 - 4)); -} - -void prepare_deposit_5() { - uint256_t amount, amountPre, quantum; - uint8_t decimals; - char *ticker = (char*)PIC(chainConfig->coinName); - - if (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_ETH) { - decimals = WEI_TO_ETHER; - convertUint256BE(tmpContent.txContent.value.value, tmpContent.txContent.value.length, &amountPre); - } - else { - tokenDefinition_t *token = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex]; - decimals = token->decimals; - ticker = (char*)token->ticker; - readu256BE(dataContext.tokenContext.data + 4 + 32 + 32, &amountPre); - } - readu256BE(dataContext.tokenContext.quantum, &quantum); - mul256(&amountPre, &quantum, &amount); - tostring256(&amount, 10, (char*)(G_io_apdu_buffer + 100), 100); - strcpy(strings.common.fullAmount, ticker); - adjustDecimals((char*)(G_io_apdu_buffer + 100), strlen((char*)(G_io_apdu_buffer + 100)), strings.common.fullAmount + strlen(ticker), 50 - strlen(ticker), decimals); -} - -UX_STEP_NOCB(ux_approval_starkware_deposit_1_step, - pnn, - { - &C_icon_eye, - "Review", - "transaction", - }); - -UX_STEP_NOCB( - ux_approval_starkware_deposit_2_step, - bnnn_paging, - { - .title = "Deposit", - .text = " " - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_deposit_3_step, - bnnn_paging, - prepare_deposit_3(), - { - .title = "Contract Name", - .text = strings.common.fullAddress, - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_deposit_4_step, - bnnn_paging, - prepare_deposit_4(), - { - .title = "Token Account", - .text = strings.common.fullAddress - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_deposit_5_step, - bnnn_paging, - prepare_deposit_5(), - { - .title = "Amount", - .text = strings.common.fullAmount - }); - - -UX_STEP_NOCB( - ux_approval_starkware_deposit_6_step, - bnnn_paging, - { - .title = "Max Fees", - .text = strings.common.maxFee, - }); - -UX_STEP_CB( - ux_approval_starkware_deposit_7_step, - pbb, - io_seproxyhal_touch_tx_ok(NULL), - { - &C_icon_validate_14, - "Accept", - "and send", - }); - -UX_STEP_CB( - ux_approval_starkware_deposit_8_step, - pb, - io_seproxyhal_touch_tx_cancel(NULL), - { - &C_icon_crossmark, - "Reject", - }); - -UX_FLOW(ux_approval_starkware_deposit_flow, - &ux_approval_starkware_deposit_1_step, - &ux_approval_starkware_deposit_2_step, - &ux_approval_starkware_deposit_3_step, - &ux_approval_starkware_deposit_4_step, - &ux_approval_starkware_deposit_5_step, - &ux_approval_starkware_deposit_6_step, - &ux_approval_starkware_deposit_7_step, - &ux_approval_starkware_deposit_8_step -); - -#endif - diff --git a/src_features/stark_contract_escape/ui_flow_stark_escape.c b/src_features/stark_contract_escape/ui_flow_stark_escape.c deleted file mode 100644 index 7811125..0000000 --- a/src_features/stark_contract_escape/ui_flow_stark_escape.c +++ /dev/null @@ -1,135 +0,0 @@ -#ifdef HAVE_STARKWARE - -#include "shared_context.h" -#include "ui_callbacks.h" - -void prepare_escape_3() { - uint8_t address[41]; - getEthAddressStringFromBinary(tmpContent.txContent.destination, address, &global_sha3, chainConfig); - strings.common.fullAddress[0] = '0'; - strings.common.fullAddress[1] = 'x'; - os_memmove((unsigned char *)strings.common.fullAddress+2, address, 40); - strings.common.fullAddress[42] = '\0'; -} - -void prepare_escape_4() { - uint256_t amount, amountPre, quantum; - uint8_t decimals; - char *ticker = (char*)PIC(chainConfig->coinName); - - if (dataContext.tokenContext.quantumIndex == MAX_TOKEN) { - decimals = WEI_TO_ETHER; - } - else { - tokenDefinition_t *token = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex]; - decimals = token->decimals; - ticker = (char*)token->ticker; - } - readu256BE(dataContext.tokenContext.data + 4 + 32 + 32 + 32, &amountPre); - readu256BE(dataContext.tokenContext.quantum, &quantum); - mul256(&amountPre, &quantum, &amount); - tostring256(&amount, 10, (char*)(G_io_apdu_buffer + 100), 100); - strcpy(strings.common.fullAmount, ticker); - adjustDecimals((char*)(G_io_apdu_buffer + 100), strlen((char*)(G_io_apdu_buffer + 100)), strings.common.fullAmount + strlen(ticker), 50 - strlen(ticker), decimals); -} - -void prepare_escape_5() { - snprintf(strings.tmp.tmp, 70, "0x%.*H", 32, dataContext.tokenContext.data + 4 + 32); -} - -void prepare_escape_6() { - snprintf(strings.common.fullAddress, 10, "%d", U4BE(dataContext.tokenContext.data, 4 + 32 - 4)); -} - -UX_STEP_NOCB(ux_approval_starkware_escape_1_step, - pnn, - { - &C_icon_eye, - "Review", - "transaction", - }); - -UX_STEP_NOCB( - ux_approval_starkware_escape_2_step, - bnnn_paging, - { - .title = "Escape", - .text = " " - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_escape_3_step, - bnnn_paging, - prepare_escape_3(), - { - .title = "Contract Name", - .text = strings.common.fullAddress, - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_escape_4_step, - bnnn_paging, - prepare_escape_4(), - { - .title = "Amount", - .text = strings.common.fullAmount - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_escape_5_step, - bnnn_paging, - prepare_escape_5(), - { - .title = "Master Account", - .text = strings.tmp.tmp - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_escape_6_step, - bnnn_paging, - prepare_escape_6(), - { - .title = "Token Account", - .text = strings.common.fullAddress - }); - -UX_STEP_NOCB( - ux_approval_starkware_escape_7_step, - bnnn_paging, - { - .title = "Max Fees", - .text = strings.common.maxFee, - }); - -UX_STEP_CB( - ux_approval_starkware_escape_8_step, - pbb, - io_seproxyhal_touch_tx_ok(NULL), - { - &C_icon_validate_14, - "Accept", - "and send", - }); - -UX_STEP_CB( - ux_approval_starkware_escape_9_step, - pb, - io_seproxyhal_touch_tx_cancel(NULL), - { - &C_icon_crossmark, - "Reject", - }); - -UX_FLOW(ux_approval_starkware_escape_flow, - &ux_approval_starkware_escape_1_step, - &ux_approval_starkware_escape_2_step, - &ux_approval_starkware_escape_3_step, - &ux_approval_starkware_escape_4_step, - &ux_approval_starkware_escape_5_step, - &ux_approval_starkware_escape_6_step, - &ux_approval_starkware_escape_7_step, - &ux_approval_starkware_escape_8_step, - &ux_approval_starkware_escape_9_step -); - -#endif diff --git a/src_features/stark_contract_register/ui_flow_stark_register.c b/src_features/stark_contract_register/ui_flow_stark_register.c deleted file mode 100644 index 8080585..0000000 --- a/src_features/stark_contract_register/ui_flow_stark_register.c +++ /dev/null @@ -1,123 +0,0 @@ -#ifdef HAVE_STARKWARE - -#include "shared_context.h" -#include "ui_callbacks.h" - -void prepare_register_3() { - uint8_t address[41]; - getEthAddressStringFromBinary(tmpContent.txContent.destination, address, &global_sha3, chainConfig); - strings.common.fullAddress[0] = '0'; - strings.common.fullAddress[1] = 'x'; - os_memmove((unsigned char *)strings.common.fullAddress+2, address, 40); - strings.common.fullAddress[42] = '\0'; -} - -void prepare_register_4() { - uint8_t privateKeyData[32]; - uint8_t address[41]; - cx_ecfp_private_key_t privateKey; - cx_ecfp_public_key_t publicKey; - os_perso_derive_node_bip32(CX_CURVE_256K1, tmpCtx.transactionContext.bip32Path, - tmpCtx.transactionContext.pathLength, - privateKeyData, NULL); - cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey); - io_seproxyhal_io_heartbeat(); - cx_ecfp_generate_pair(CX_CURVE_256K1, &publicKey, &privateKey, 1); - os_memset(&privateKey, 0, sizeof(privateKey)); - os_memset(privateKeyData, 0, sizeof(privateKeyData)); - io_seproxyhal_io_heartbeat(); - getEthAddressStringFromKey(&publicKey, address, &global_sha3, chainConfig); - strings.common.fullAddress[0] = '0'; - strings.common.fullAddress[1] = 'x'; - os_memmove((unsigned char *)strings.common.fullAddress+2, address, 40); - strings.common.fullAddress[42] = '\0'; -} - -void prepare_register_5() { - snprintf(strings.tmp.tmp, 70, "0x%.*H", 32, dataContext.tokenContext.data + 4); -} - -UX_STEP_NOCB(ux_approval_starkware_register_1_step, - pnn, - { - &C_icon_eye, - "Review", - "transaction", - }); - -UX_STEP_NOCB( - ux_approval_starkware_register_2_step, - bnnn_paging, - { - .title = "Registration", - .text = " " - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_register_3_step, - bnnn_paging, - prepare_register_3(), - { - .title = "Contract Name", - .text = strings.common.fullAddress, - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_register_4_step, - bnnn_paging, - prepare_register_4(), - { - .title = "From ETH address", - .text = strings.common.fullAddress - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_register_5_step, - bnnn_paging, - prepare_register_5(), - { - .title = "Master account", - .text = strings.tmp.tmp - }); - - -UX_STEP_NOCB( - ux_approval_starkware_register_6_step, - bnnn_paging, - { - .title = "Max Fees", - .text = strings.common.maxFee, - }); - -UX_STEP_CB( - ux_approval_starkware_register_7_step, - pbb, - io_seproxyhal_touch_tx_ok(NULL), - { - &C_icon_validate_14, - "Accept", - "and send", - }); - -UX_STEP_CB( - ux_approval_starkware_register_8_step, - pb, - io_seproxyhal_touch_tx_cancel(NULL), - { - &C_icon_crossmark, - "Reject", - }); - -UX_FLOW(ux_approval_starkware_register_flow, - &ux_approval_starkware_register_1_step, - &ux_approval_starkware_register_2_step, - &ux_approval_starkware_register_3_step, - &ux_approval_starkware_register_4_step, - &ux_approval_starkware_register_5_step, - &ux_approval_starkware_register_6_step, - &ux_approval_starkware_register_7_step, - &ux_approval_starkware_register_8_step -); - -#endif - diff --git a/src_features/stark_contract_verifyEscape/ui_flow_stark_verify_escape.c b/src_features/stark_contract_verifyEscape/ui_flow_stark_verify_escape.c deleted file mode 100644 index 9dd732b..0000000 --- a/src_features/stark_contract_verifyEscape/ui_flow_stark_verify_escape.c +++ /dev/null @@ -1,76 +0,0 @@ -#ifdef HAVE_STARKWARE - -#include "shared_context.h" -#include "ui_callbacks.h" - -void prepare_verify_escape_3() { - uint8_t address[41]; - getEthAddressStringFromBinary(tmpContent.txContent.destination, address, &global_sha3, chainConfig); - strings.common.fullAddress[0] = '0'; - strings.common.fullAddress[1] = 'x'; - os_memmove((unsigned char *)strings.common.fullAddress+2, address, 40); - strings.common.fullAddress[42] = '\0'; -} - -UX_STEP_NOCB(ux_approval_starkware_verify_escape_1_step, - pnn, - { - &C_icon_eye, - "Review", - "transaction", - }); - -UX_STEP_NOCB( - ux_approval_starkware_verify_escape_2_step, - bnnn_paging, - { - .title = "Verify Escape", - .text = " " - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_verify_escape_3_step, - bnnn_paging, - prepare_verify_escape_3(), - { - .title = "Contract Name", - .text = strings.common.fullAddress, - }); - -UX_STEP_NOCB( - ux_approval_starkware_verify_escape_4_step, - bnnn_paging, - { - .title = "Max Fees", - .text = strings.common.maxFee, - }); - -UX_STEP_CB( - ux_approval_starkware_verify_escape_5_step, - pbb, - io_seproxyhal_touch_tx_ok(NULL), - { - &C_icon_validate_14, - "Accept", - "and send", - }); - -UX_STEP_CB( - ux_approval_starkware_verify_escape_6_step, - pb, - io_seproxyhal_touch_tx_cancel(NULL), - { - &C_icon_crossmark, - "Reject", - }); - -UX_FLOW(ux_approval_starkware_verify_escape_flow, - &ux_approval_starkware_verify_escape_1_step, - &ux_approval_starkware_verify_escape_2_step, - &ux_approval_starkware_verify_escape_3_step, - &ux_approval_starkware_verify_escape_4_step, - &ux_approval_starkware_verify_escape_5_step, - &ux_approval_starkware_verify_escape_6_step -); - -#endif diff --git a/src_features/stark_contract_verifyVaultId/ui_flow_stark_verifyVaultId.c b/src_features/stark_contract_verifyVaultId/ui_flow_stark_verifyVaultId.c deleted file mode 100644 index be3d529..0000000 --- a/src_features/stark_contract_verifyVaultId/ui_flow_stark_verifyVaultId.c +++ /dev/null @@ -1,115 +0,0 @@ -#ifdef HAVE_STARKWARE - -#include "shared_context.h" -#include "ui_callbacks.h" - -void prepare_verify_vault_id_2() { - if (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_CANCEL) { - strcpy(strings.common.fullAddress, "Cancel Deposit"); - } - else - if (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_RECLAIM) { - strcpy(strings.common.fullAddress, "Reclaim Deposit"); - } - else - if (contractProvisioned == CONTRACT_STARKWARE_FULL_WITHDRAWAL) { - strcpy(strings.common.fullAddress, "Full Withdrawal"); - } - else - if (contractProvisioned == CONTRACT_STARKWARE_FREEZE) { - strcpy(strings.common.fullAddress, "Freeze"); - } -} - -void prepare_verify_vault_id_3() { - uint8_t address[41]; - getEthAddressStringFromBinary(tmpContent.txContent.destination, address, &global_sha3, chainConfig); - strings.common.fullAddress[0] = '0'; - strings.common.fullAddress[1] = 'x'; - os_memmove((unsigned char *)strings.common.fullAddress+2, address, 40); - strings.common.fullAddress[42] = '\0'; -} - -void prepare_verify_vault_id_4() { - uint8_t offset = 0; - if ((contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_CANCEL) || (contractProvisioned == CONTRACT_STARKWARE_DEPOSIT_RECLAIM)) { - offset = 32; - } - snprintf(strings.common.fullAddress, 10, "%d", U4BE(dataContext.tokenContext.data, 4 + offset + 32 - 4)); -} - -UX_STEP_NOCB(ux_approval_starkware_verify_vault_id_1_step, - pnn, - { - &C_icon_eye, - "Review", - "transaction", - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_verify_vault_id_2_step, - bnnn_paging, - prepare_verify_vault_id_2(), - { - .title = strings.common.fullAddress, - .text = " " - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_verify_vault_id_3_step, - bnnn_paging, - prepare_verify_vault_id_3(), - { - .title = "Contract Name", - .text = strings.common.fullAddress, - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_verify_vault_id_4_step, - bnnn_paging, - prepare_verify_vault_id_4(), - { - .title = "Token Account", - .text = strings.common.fullAddress - }); - - -UX_STEP_NOCB( - ux_approval_starkware_verify_vault_id_5_step, - bnnn_paging, - { - .title = "Max Fees", - .text = strings.common.maxFee, - }); - -UX_STEP_CB( - ux_approval_starkware_verify_vault_id_6_step, - pbb, - io_seproxyhal_touch_tx_ok(NULL), - { - &C_icon_validate_14, - "Accept", - "and send", - }); - -UX_STEP_CB( - ux_approval_starkware_verify_vault_id_7_step, - pb, - io_seproxyhal_touch_tx_cancel(NULL), - { - &C_icon_crossmark, - "Reject", - }); - -UX_FLOW(ux_approval_starkware_verify_vault_id_flow, - &ux_approval_starkware_verify_vault_id_1_step, - &ux_approval_starkware_verify_vault_id_2_step, - &ux_approval_starkware_verify_vault_id_3_step, - &ux_approval_starkware_verify_vault_id_4_step, - &ux_approval_starkware_verify_vault_id_5_step, - &ux_approval_starkware_verify_vault_id_6_step, - &ux_approval_starkware_verify_vault_id_7_step -); - -#endif - diff --git a/src_features/stark_contract_withdrawal/ui_flow_stark_withdrawal.c b/src_features/stark_contract_withdrawal/ui_flow_stark_withdrawal.c deleted file mode 100644 index 676bca2..0000000 --- a/src_features/stark_contract_withdrawal/ui_flow_stark_withdrawal.c +++ /dev/null @@ -1,109 +0,0 @@ -#ifdef HAVE_STARKWARE - -#include "shared_context.h" -#include "ui_callbacks.h" - -void prepare_register_4(); - -void prepare_withdraw_3() { - uint8_t address[41]; - getEthAddressStringFromBinary(tmpContent.txContent.destination, address, &global_sha3, chainConfig); - strings.common.fullAddress[0] = '0'; - strings.common.fullAddress[1] = 'x'; - os_memmove((unsigned char *)strings.common.fullAddress+2, address, 40); - strings.common.fullAddress[42] = '\0'; -} - -void prepare_withdraw_5() { - char *ticker = (char*)PIC(chainConfig->coinName); - - if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) { - tokenDefinition_t *token = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex]; - ticker = (char*)token->ticker; - } - strcpy(strings.common.fullAmount, ticker); -} - -UX_STEP_NOCB(ux_approval_starkware_withdraw_1_step, - pnn, - { - &C_icon_eye, - "Review", - "transaction", - }); - -UX_STEP_NOCB( - ux_approval_starkware_withdraw_2_step, - bnnn_paging, - { - .title = "Withdrawal", - .text = " " - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_withdraw_3_step, - bnnn_paging, - prepare_withdraw_3(), - { - .title = "Contract Name", - .text = strings.common.fullAddress, - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_withdraw_4_step, - bnnn_paging, - prepare_register_4(), - { - .title = "To Eth Address", - .text = strings.common.fullAddress - }); - -UX_STEP_NOCB_INIT( - ux_approval_starkware_withdraw_5_step, - bnnn_paging, - prepare_withdraw_5(), - { - .title = "Token Symbol", - .text = strings.common.fullAmount - }); - - -UX_STEP_NOCB( - ux_approval_starkware_withdraw_6_step, - bnnn_paging, - { - .title = "Max Fees", - .text = strings.common.maxFee, - }); - -UX_STEP_CB( - ux_approval_starkware_withdraw_7_step, - pbb, - io_seproxyhal_touch_tx_ok(NULL), - { - &C_icon_validate_14, - "Accept", - "and send", - }); - -UX_STEP_CB( - ux_approval_starkware_withdraw_8_step, - pb, - io_seproxyhal_touch_tx_cancel(NULL), - { - &C_icon_crossmark, - "Reject", - }); - -UX_FLOW(ux_approval_starkware_withdraw_flow, - &ux_approval_starkware_withdraw_1_step, - &ux_approval_starkware_withdraw_2_step, - &ux_approval_starkware_withdraw_3_step, - &ux_approval_starkware_withdraw_4_step, - &ux_approval_starkware_withdraw_5_step, - &ux_approval_starkware_withdraw_6_step, - &ux_approval_starkware_withdraw_7_step, - &ux_approval_starkware_withdraw_8_step -); - -#endif diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c new file mode 100644 index 0000000..4bb3525 --- /dev/null +++ b/src_plugins/erc20/erc20_plugin.c @@ -0,0 +1,63 @@ +#include +#include "eth_plugin_interface.h" + +typedef struct erc20_parameters_t { + + uint8_t destinationAddress[20]; + uint8_t amount[32]; + +} erc20_parameters_t; + +void erc20_plugin_call(int message, void *parameters) { + + switch(message) { + case ETH_PLUGIN_INIT_CONTRACT: { + ethPluginInitContract_t *msg = (ethPluginInitContract_t*)parameters; + PRINTF("erc20 plugin init\n"); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_PROVIDE_PARAMETER : { + ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t*)parameters; + erc20_parameters_t *context = (erc20_parameters_t*)msg->pluginContext; + PRINTF("erc20 plugin provide parameter %d %.*H\n", msg->parameterOffset, 32, msg->parameter); + switch(msg->parameterOffset) { + case 4: + memmove(context->destinationAddress, msg->parameter + 12, 20); + msg->result = ETH_PLUGIN_RESULT_OK; + break; + case 4 + 32: + memmove(context->amount, msg->parameter, 32); + msg->result = ETH_PLUGIN_RESULT_OK; + break; + default: + PRINTF("Unhandled parameter offset\n"); + break; + } + } + break; + + case ETH_PLUGIN_FINALIZE: { + ethPluginFinalize_t *msg = (ethPluginFinalize_t*)parameters; + erc20_parameters_t *context = (erc20_parameters_t*)msg->pluginContext; + PRINTF("erc20 plugin finalize\n"); + msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; + msg->amount = context->amount; + msg->address = context->destinationAddress; + msg->uiType = ETH_UI_TYPE_AMOUNT_ADDRESS; + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_PROVIDE_TOKEN: { + ethPluginProvideToken_t *msg = (ethPluginProvideToken_t*)parameters; + PRINTF("erc20 plugin provide token %d\n", (msg->token1 != NULL)); + msg->result = (msg->token1 != NULL ? ETH_PLUGIN_RESULT_OK : ETH_PLUGIN_RESULT_FALLBACK); + } + break; + + default: + PRINTF("Unhandled message %d\n", message); + } +} diff --git a/src_plugins/starkware/starkware_plugin.c b/src_plugins/starkware/starkware_plugin.c new file mode 100644 index 0000000..555e62f --- /dev/null +++ b/src_plugins/starkware/starkware_plugin.c @@ -0,0 +1,444 @@ +#include +#include "eth_plugin_interface.h" +#include "shared_context.h" // TODO : rewrite as independant code +#include "eth_plugin_internal.h" // TODO : rewrite as independant code + +typedef enum { + + STARKWARE_REGISTER = 0, + STARKWARE_DEPOSIT_TOKEN, + STARKWARE_DEPOSIT_ETH, + STARKWARE_DEPOSIT_CANCEL, + STARKWARE_DEPOSIT_RECLAIM, + STARKWARE_WITHDRAW, + STARKWARE_FULL_WITHDRAW, + STARKWARE_FREEZE, + STARKWARE_ESCAPE, + STARKWARE_VERIFY_ESCAPE + +} starkwareSelector_t; + +// register : starkkey (32), drop param 2 + // Registration + // Contract Name + // From ETH address + // Master account +// deposit token : verify tokenId (32), vaultId (4), quantized Amount (32) + // Deposit + // Contract Name + // Token Account + // Amount +// deposit : verify tokenId (32), vaultId (4) + // Flow similar to deposit +// deposit cancel, deposit reclaim : tokenId (32) ignored, vaultId(4) +// full withdrawal, freeze : vaultId (4) + // Cancel Deposit | Reclaim Deposit | Full Withdrawal | Freeze + // Contract Name + // Token Account +// withdrawal : verify tokenId (32) + // Withdrawal + // Contract Name + // To Eth Address + // Token Symbol +// escape : starkkey (32), vaultId (4), verify tokenId (32), quantized Amount (32) + // Escape + // Contract Name + // Amount + // Master Account + // Token Account +// verify escape : escapeProof (ignore) + // Verify Escape + // Contract Name + +static const uint8_t STARKWARE_EXPECTED_DATA_SIZE[] = { + 4 + 32, + 4 + 32 + 32 + 32, + 4 + 32 + 32, + 4 + 32 + 32, + 4 + 32 + 32, + 4 + 32, + 4 + 32, + 4 + 32, + 4 + 32 + 32 + 32 + 32, + 0 +}; + +static const uint8_t STARKWARE_NUM_SCREENS[] = { + 4 - 1, + 4 - 1, + 4 - 1, + 3 - 1, + 3 - 1, + 4 - 1, + 3 - 1, + 3 - 1, + 5 - 1, + 2 - 1 +}; + +typedef struct starkware_parameters_t { + + uint8_t vaultId[4]; + uint8_t selectorIndex; + uint8_t starkKey[32]; + uint8_t amount[32]; + uint8_t validToken; + +} starkware_parameters_t; + +// TODO : rewrite as independant code +bool starkware_verify_token_id(uint8_t *tmp32, uint8_t *tokenId) { + if (quantumSet) { + tokenDefinition_t *currentToken = NULL; + if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) { + currentToken = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex]; + } + compute_token_id(&global_sha3, + (currentToken != NULL ? currentToken->address : NULL), + dataContext.tokenContext.quantum, tmp32); + if (memcmp(tokenId, tmp32, 32) != 0) { + PRINTF("Token ID not matching - computed %.*H\n", 32, tmp32); + PRINTF("Current quantum %.*H\n", 32, dataContext.tokenContext.quantum); + PRINTF("Requested %.*H\n", 32, tokenId); + return false; + } + } + else { + PRINTF("Quantum not set\n"); + return false; + } + return true; +} + +void starkware_print_vault_id(uint32_t vaultId, char *destination) { + snprintf(destination, 10, "%d", vaultId); +} + +void starkware_print_stark_key(uint8_t *starkKey, char *destination) { + snprintf(destination, 70, "0x%.*H", 32, starkKey); +} + +// TODO : rewrite as independant code +void starkware_print_eth_address(uint8_t *address, char *destination) { + destination[0] = '0'; + destination[1] = 'x'; + getEthAddressStringFromBinary(address, destination + 2, &global_sha3, chainConfig); + destination[42] = '\0'; +} + +// TODO : rewrite as independant code +void starkware_print_amount(uint8_t *amountData, char *destination, bool forEscape) { + uint256_t amount, amountPre, quantum; + uint8_t decimals; + char *ticker = (char*)PIC(chainConfig->coinName); + + if ((amountData == NULL) || (forEscape && (dataContext.tokenContext.quantumIndex == MAX_TOKEN))) { + decimals = WEI_TO_ETHER; + if (!forEscape) { + convertUint256BE(tmpContent.txContent.value.value, tmpContent.txContent.value.length, &amountPre); + } + else { + readu256BE(amountData, &amountPre); + } + } + else { + tokenDefinition_t *token = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex]; + decimals = token->decimals; + ticker = (char*)token->ticker; + readu256BE(amountData, &amountPre); + } + readu256BE(dataContext.tokenContext.quantum, &quantum); + mul256(&amountPre, &quantum, &amount); + tostring256(&amount, 10, (char*)(G_io_apdu_buffer + 100), 100); + strcpy(destination, ticker); + adjustDecimals((char*)(G_io_apdu_buffer + 100), strlen((char*)(G_io_apdu_buffer + 100)), destination + strlen(ticker), 50 - strlen(ticker), decimals); +} + +// TODO : rewrite as independant code +void starkware_print_ticker(char *destination) { + char *ticker = (char*)PIC(chainConfig->coinName); + + if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) { + tokenDefinition_t *token = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex]; + ticker = (char*)token->ticker; + } + strcpy(destination, ticker); +} + +// TODO : rewrite as independant code +void starkware_get_source_address(char *destination) { + uint8_t privateKeyData[32]; + cx_ecfp_private_key_t privateKey; + cx_ecfp_public_key_t publicKey; + os_perso_derive_node_bip32(CX_CURVE_256K1, tmpCtx.transactionContext.bip32Path, + tmpCtx.transactionContext.pathLength, + privateKeyData, NULL); + cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey); + io_seproxyhal_io_heartbeat(); + cx_ecfp_generate_pair(CX_CURVE_256K1, &publicKey, &privateKey, 1); + os_memset(&privateKey, 0, sizeof(privateKey)); + os_memset(privateKeyData, 0, sizeof(privateKeyData)); + io_seproxyhal_io_heartbeat(); + destination[0] = '0'; + destination[1] = 'x'; + getEthAddressStringFromKey(&publicKey, destination + 2, &global_sha3, chainConfig); + destination[42] = '\0'; +} + +void starkware_plugin_call(int message, void *parameters) { + + switch(message) { + case ETH_PLUGIN_INIT_CONTRACT: { + uint8_t i; + ethPluginInitContract_t *msg = (ethPluginInitContract_t*)parameters; + starkware_parameters_t *context = (starkware_parameters_t*)msg->pluginContext; + PRINTF("starkware plugin init\n"); + for (i=0; iselector, SELECTOR_SIZE) == 0) { + context->selectorIndex = i; + break; + } + } + if (i == NUM_STARKWARE_SELECTORS) { + PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector); + break; + } + if (STARKWARE_EXPECTED_DATA_SIZE[context->selectorIndex] != 0) { + if (msg->dataSize != STARKWARE_EXPECTED_DATA_SIZE[context->selectorIndex]) { + PRINTF("Unexpected data size for command %d expected %d got %d\n", context->selectorIndex, + STARKWARE_EXPECTED_DATA_SIZE[context->selectorIndex], msg->dataSize); + break; + } + } + context->validToken = true; + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_PROVIDE_PARAMETER : { + ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t*)parameters; + starkware_parameters_t *context = (starkware_parameters_t*)msg->pluginContext; + PRINTF("starkware plugin provide parameter %d %.*H\n", msg->parameterOffset, 32, msg->parameter); + // Ignore for verify escape + if (context->selectorIndex == STARKWARE_VERIFY_ESCAPE) { + msg->result = ETH_PLUGIN_RESULT_OK; + break; + } + switch(msg->parameterOffset) { + case 4: + switch(context->selectorIndex) { + case STARKWARE_REGISTER: + case STARKWARE_ESCAPE: + memmove(context->starkKey, msg->parameter, 32); + break; + case STARKWARE_DEPOSIT_CANCEL: + case STARKWARE_DEPOSIT_RECLAIM: + break; + case STARKWARE_FULL_WITHDRAW: + case STARKWARE_FREEZE: + memmove(context->vaultId, msg->parameter + 32 - 4, 4); + break; + case STARKWARE_DEPOSIT_ETH: + case STARKWARE_DEPOSIT_TOKEN: + case STARKWARE_WITHDRAW: + context->validToken = starkware_verify_token_id(context->amount, msg->parameter); + break; + default: + break; + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + case 4 + 32: + switch(context->selectorIndex) { + case STARKWARE_DEPOSIT_CANCEL: + case STARKWARE_DEPOSIT_RECLAIM: + case STARKWARE_DEPOSIT_ETH: + case STARKWARE_DEPOSIT_TOKEN: + case STARKWARE_ESCAPE: + memmove(context->vaultId, msg->parameter + 32 - 4, 4); + break; + default: + break; + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + case 4 + 32 + 32: + switch(context->selectorIndex) { + case STARKWARE_DEPOSIT_TOKEN: + memmove(context->amount, msg->parameter, 32); + break; + case STARKWARE_ESCAPE: + context->validToken = starkware_verify_token_id(context->amount, msg->parameter); + break; + default: + break; + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + case 4 + 32 + 32 + 32: + switch(context->selectorIndex) { + case STARKWARE_ESCAPE: + memmove(context->amount, msg->parameter, 32); + break; + default: + break; + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + default: + PRINTF("Unhandled parameter offset\n"); + break; + } + } + break; + + case ETH_PLUGIN_FINALIZE: { + ethPluginFinalize_t *msg = (ethPluginFinalize_t*)parameters; + starkware_parameters_t *context = (starkware_parameters_t*)msg->pluginContext; + PRINTF("starkware plugin finalize\n"); + if (!context->validToken) { + msg->result = ETH_PLUGIN_RESULT_FALLBACK; + } + else { + msg->uiType = ETH_UI_TYPE_GENERIC; + msg->numScreens = STARKWARE_NUM_SCREENS[context->selectorIndex]; + msg->result = ETH_PLUGIN_RESULT_OK; + } + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_ID: { + ethQueryContractID_t *msg = (ethQueryContractID_t*)parameters; + starkware_parameters_t *context = (starkware_parameters_t*)msg->pluginContext; + PRINTF("starkware query contract id\n"); + switch(context->selectorIndex) { + case STARKWARE_REGISTER: + strcpy(msg->name, "Register"); + break; + case STARKWARE_DEPOSIT_TOKEN: + case STARKWARE_DEPOSIT_ETH: + strcpy(msg->name, "Deposit"); + break; + case STARKWARE_DEPOSIT_CANCEL: + strcpy(msg->name, "Cancel Deposit"); + break; + case STARKWARE_DEPOSIT_RECLAIM: + strcpy(msg->name, "Reclaim Deposit"); + break; + case STARKWARE_WITHDRAW: + strcpy(msg->name, "Withdrawal"); + break; + case STARKWARE_FULL_WITHDRAW: + strcpy(msg->name, "Full Withdrawal"); + break; + case STARKWARE_FREEZE: + strcpy(msg->name, "Freeze"); + break; + case STARKWARE_ESCAPE: + strcpy(msg->name, "Escape"); + break; + case STARKWARE_VERIFY_ESCAPE: + strcpy(msg->name, "Verify Escape"); + break; + default: + break; + } + strcpy(msg->version, "Starkware"); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_UI: { + ethQueryContractUI_t *msg = (ethQueryContractUI_t*)parameters; + starkware_parameters_t *context = (starkware_parameters_t*)msg->pluginContext; + switch(msg->screenIndex) { + case 0: + strcpy(msg->title, "Contract Name"); + starkware_print_eth_address(tmpContent.txContent.destination, msg->msg); + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + case 1: + switch(context->selectorIndex) { + case STARKWARE_REGISTER: + strcpy(msg->title, "From ETH Address"); + starkware_get_source_address(msg->msg); + break; + case STARKWARE_DEPOSIT_TOKEN: + case STARKWARE_DEPOSIT_ETH: + case STARKWARE_DEPOSIT_CANCEL: + case STARKWARE_DEPOSIT_RECLAIM: + case STARKWARE_FULL_WITHDRAW: + case STARKWARE_FREEZE: + strcpy(msg->title, "Token Account"); + starkware_print_vault_id(U4BE(context->vaultId, 0), msg->msg); + break; + case STARKWARE_WITHDRAW: + strcpy(msg->title, "To ETH Address"); + starkware_print_eth_address(tmpContent.txContent.destination, msg->msg); + break; + case STARKWARE_ESCAPE: + strcpy(msg->title, "Amount"); + starkware_print_amount(context->amount, msg->msg, true); + break; + default: + PRINTF("Unexpected screen %d for %d\n", msg->screenIndex, context->selectorIndex); + break; + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + case 2: + switch(context->selectorIndex) { + case STARKWARE_REGISTER: + case STARKWARE_ESCAPE: + strcpy(msg->title, "Master Account"); + PRINTF("Master account %s\n", msg->msg); + starkware_print_stark_key(context->starkKey, msg->msg); + break; + case STARKWARE_DEPOSIT_TOKEN: + case STARKWARE_DEPOSIT_ETH: + strcpy(msg->title, "Amount"); + starkware_print_amount( + (context->selectorIndex == STARKWARE_DEPOSIT_ETH ? NULL : context->amount), + msg->msg, false); + break; + case STARKWARE_WITHDRAW: + strcpy(msg->title, "Token Symbol"); + starkware_print_ticker(msg->msg); + break; + default: + PRINTF("Unexpected screen %d for %d\n", msg->screenIndex, context->selectorIndex); + break; + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + case 3: + switch(context->selectorIndex) { + case STARKWARE_ESCAPE: + strcpy(msg->title, "Token Account"); + starkware_print_vault_id(U4BE(context->vaultId, 0), msg->msg); + break; + default: + PRINTF("Unexpected screen %d for %d\n", msg->screenIndex, context->selectorIndex); + break; + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + default: + PRINTF("Unexpected screen %d for %d\n", msg->screenIndex, context->selectorIndex); + break; + } + } + break; + + default: + PRINTF("Unhandled message %d\n", message); + } +} From d0311933c32dc5c1bda7beae314b30ecc661775c Mon Sep 17 00:00:00 2001 From: BTChip github Date: Thu, 24 Sep 2020 13:37:38 +0200 Subject: [PATCH 02/13] Add plugins documentation --- doc/ethapp_plugins.asc | 292 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 doc/ethapp_plugins.asc diff --git a/doc/ethapp_plugins.asc b/doc/ethapp_plugins.asc new file mode 100644 index 0000000..64cc1ae --- /dev/null +++ b/doc/ethapp_plugins.asc @@ -0,0 +1,292 @@ +Ethereum application Plugins : Technical Specifications +======================================================= +Ledger Firmware Team +Specification version 1.0 - 24th of September 2020 + +## 1.0 + - Initial release + +## About + +This specification describes the plugin interface used to display a specific UI on device for Ethereum smart contracts. + +## Flow overview + +When signing an Ethereum transaction containing data, the Ethereum application looks for a plugin using .either a selector list or the contract address. + +If a plugin is found, each network serialized data field (32 bytes) is passed to the plugin along with the field offset. The plugin can decide to stop the signature process if a data field isn't expected + +After all fields have been received, the plugin can report to the Ethereum application whether the full data is accepted, and the user interface model that'll be used to display the data + +### Amount/Address user interface + +In this model, the generic (without data) transaction display is used, with the amount and destination address replaced by data provided by the plugin + +### Generic user interface + +In this model, the plugin first reports a number of screens (2 lines of text, the second line being scrollable) to be displayed + +The Ethereum application will request each screen to be displayed to the plugin and let the user browse through them. + +The first screen being displayed is always a description of the plugin being used (name and version reported by the plugin), and the last screens include the transaction fees in ETH and a confirmation prompt + +### Code flow + +The plugin interfacing logic is described in _src/eth_plugin_interface.h_ + +The plugin common dispatcher is found in _src/eth_plugin_handler.c_ + +The plugin generic UI dispatcher is found in _src/eth_plugin_ui.c_ + +Sample internal plugins are provided in _src_plugins/_ + +## Creating a plugin + +### Creating an internal plugin + +Internal plugins are triggered on specific selectors. You can modify _src/eth_plugin_internal.c_ to add your mapping. + +Other specific mappings can be also added by modifying the common dispatcher + +### Creating an external plugin + +An external plugin is a library application named after the base64 encoding of the 20 bytes smart contract address + +## Detailed flow messages + +### Generic fields + +The following generic fields are present in all messages : + + * pluginSharedRW : scratch objects and utilities available to the plugin (can be read and written) + + * pluginSharedRO : transaction data available to the plugin (can only be read) + + * pluginContext : arbitrary data blob holding the plugin context, to be set and used by the plugin + + * result : return code set by the plugin following the message processing + +### ETH_PLUGIN_INIT_CONTRACT + +[source,C] +---- + +typedef struct ethPluginInitContract_t { + + // in + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + uint32_t pluginContextLength; + uint8_t *selector; // 4 bytes selector + uint32_t dataSize; + + char *alias; // 29 bytes alias if ETH_PLUGIN_RESULT_OK_ALIAS set + + uint8_t result; + +} ethPluginInitContract_t; + +---- + +This message is sent when the selector of the data has been parsed. The following specific fields are filled when the plugin is called : + + * pluginContextLength : length of the data field available to store the plugin context + * selector : 4 bytes selector of the data field + * dataSize : size in bytes of the data field + +The following return codes are expected, any other will abort the signing process : + + * ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized + * ETH_PLUGIN_RESULT_OK_ALIAS : if a base64 encoded alias of another plugin to call is copied to the _alias_ field. In this case, the dispatcher will follow the alias chain, and the original plugin will only be called to retrieve its name when using a generic user interface + * ETH_PLUGIN_RESULT_FALLBACK : if the signing logic should fallback to the generic one + +### ETH_PLUGIN_PROVIDE_PARAMETER + +[source,C] +---- + +typedef struct ethPluginProvideParameter_t { + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + uint8_t *parameter; // 32 bytes parameter + uint32_t parameterOffset; + + uint8_t result; + +} ethPluginProvideParameter_t; + +---- + +This message is sent when a new 32 bytes component of the data field is available. The following specific fields are filled when the plugin is called : + + * parameter : pointer to the 32 bytes parameter being parsed + * parameterOffset : offset to this parameter from the beginning of the data field (starts at 4, following the selector) + +The following return codes are expected, any other will abort the signing process : + + * 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_FINALIZE + +[source,C] +---- + +typedef struct ethPluginFinalize_t { + + ethPluginSharedRW_t *pluginSharedRW; + 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 *amount; // set an uint256 pointer if uiType is UI_AMOUNT_ADDRESS + uint8_t *address; // set to a 20 bytes address pointer if uiType is UI_AMOUNT_ADDRESS + + uint8_t uiType; + uint8_t numScreens; // ignored if uiType is UI_AMOUNT_ADDRESS + uint8_t result; + +} 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 + * 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 : + + * amount : set to a pointer to a 256 bits number + * address : set to a pointer to a 20 bytes address + +The following specific fields are filled by the plugin when returning a generic UI : + + * numScreens : number of screens handled by the plugin + +The following return codes are expected, any other will abort the signing process : + + * 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 + +[source,C] +---- + +typedef struct ethPluginProvideToken_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; + + uint8_t result; + +} ethPluginProvideToken_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 : + + * 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 + +The following return codes are expected, any other will abort the signing process : + + * 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_QUERY_CONTRACT_ID + +[source,C] +---- + +typedef struct ethQueryContractID_t { + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + + char *name; + uint32_t nameLength; + char *version; + uint32_t versionLength; + + uint8_t result; + +} 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 : + + * name : pointer to the name of the plugin, to be filled by the plugin + * nameLength : maximum name length + * version : pointer to the version of the plugin, to be filled by the plugin + * versionLength : maximum version length + +The following return codes are expected, any other will abort the signing process : + + * ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized + +### ETH_PLUGIN_QUERY_CONTRACT_UI + +[source,C] +---- + +typedef struct ethQueryContractUI_t { + + ethPluginSharedRW_t *pluginSharedRW; + ethPluginSharedRO_t *pluginSharedRO; + uint8_t *pluginContext; + uint8_t screenIndex; + char *title; + uint32_t titleLength; + char *msg; + uint32_t msgLength; + + uint8_t result; + +} 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 : + + * 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 + * msg : pointer to the second line of the screen, to be filled by the plugin + * msgLength : maximum msg length + +The following return codes are expected, any other will abort the signing process : + + * ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized + +## Caveats + +When setting a pointer from the plugin space, make sure to use an address that will be accessible from the Ethereum application (typically in the plugin RAM context, *not* on the plugin stack) + +Do not use data types that need to be aligned (such as uint32_t) in the plugin context + +## TODOs + +Provide a sample callback mechanism for common plugin actions (amount to string, 256 bits number multiplication ...) to avoid duplicating code in the plugin space + +Provide external plugins samples + +Fully support Starkware as an independant application (APDU logic added) + +Support extra flags for the generic UI (fast confirmation on first screen, ...) + +Support extra plugin provisioning (signed list of associated smart contract addresses, ...) From ef3c5fa27b14d3dfb6868c9a14035933d1590b27 Mon Sep 17 00:00:00 2001 From: BTChip github Date: Fri, 25 Sep 2020 23:00:57 +0200 Subject: [PATCH 03/13] Fix parsing for a contract call with no data --- src_features/signTx/logic_signTx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index 3c009a5..1814e03 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -50,6 +50,10 @@ customStatus_e customProcessor(txContext_t *context) { dataContext.tokenContext.fieldIndex = 0; dataContext.tokenContext.fieldOffset = 0; copyTxData(context, NULL, 4); + if (context->currentFieldLength == 4) { + dataContext.tokenContext.fieldIndex++; + return CUSTOM_HANDLED; + } } } uint32_t blockSize; From 6dfe93c4dc8f25b3a69dcc6aebd4358a23e65316 Mon Sep 17 00:00:00 2001 From: TamtamHero <10632523+TamtamHero@users.noreply.github.com> Date: Tue, 29 Sep 2020 14:56:47 +0200 Subject: [PATCH 04/13] Fix compilation issue on Nano X + last flow step issue --- src/eth_plugin_ui.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/eth_plugin_ui.c b/src/eth_plugin_ui.c index d63fb9e..d38642f 100644 --- a/src/eth_plugin_ui.c +++ b/src/eth_plugin_ui.c @@ -61,9 +61,6 @@ void display_next_plugin_item(bool entering) { dataContext.tokenContext.pluginUiState = PLUGIN_UI_INSIDE; plugin_ui_get_item(); ux_flow_prev(); - // Reset multi page layout to the first page - G_ux.layout_paging.current = 0; - ux_layout_paging_redisplay(G_ux.stack_count-1); } else { if (dataContext.tokenContext.pluginUiCurrentItem < dataContext.tokenContext.pluginUiMaxItems - 1) { @@ -71,8 +68,12 @@ void display_next_plugin_item(bool entering) { plugin_ui_get_item(); ux_flow_prev(); // Reset multi page layout to the first page - G_ux.layout_paging.current = 0; - ux_layout_paging_redisplay(G_ux.stack_count-1); + G_ux.layout_paging.current = 0; + #ifdef TARGET_NANOS + ux_layout_paging_redisplay(G_ux.stack_count-1); + #else + ux_layout_bnnn_paging_redisplay(0); + #endif } else { dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; @@ -155,16 +156,17 @@ UX_FLOW_DEF_VALID( "Reject", }); -const ux_flow_step_t * const ux_plugin_approval_flow [] = { - &ux_plugin_approval_intro_step, - &ux_plugin_approval_id_step, - &ux_plugin_approval_before_step, - &ux_plugin_approval_display_step, - &ux_plugin_approval_after_step, - &ux_plugin_approval_fees_step, - &ux_plugin_approval_ok_step, - &ux_plugin_approval_cancel_step -}; +UX_FLOW( + ux_plugin_approval_flow, + &ux_plugin_approval_intro_step, + &ux_plugin_approval_id_step, + &ux_plugin_approval_before_step, + &ux_plugin_approval_display_step, + &ux_plugin_approval_after_step, + &ux_plugin_approval_fees_step, + &ux_plugin_approval_ok_step, + &ux_plugin_approval_cancel_step +); void plugin_ui_start() { dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; From 7e1bdce8354ba2c1c5d186028dfb78c54111bc4a Mon Sep 17 00:00:00 2001 From: TamtamHero <10632523+TamtamHero@users.noreply.github.com> Date: Wed, 30 Sep 2020 12:33:39 +0200 Subject: [PATCH 05/13] Fix basic transacting --- src_features/signTx/cmd_signTx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src_features/signTx/cmd_signTx.c b/src_features/signTx/cmd_signTx.c index a3d12d0..ac35fcd 100644 --- a/src_features/signTx/cmd_signTx.c +++ b/src_features/signTx/cmd_signTx.c @@ -34,6 +34,7 @@ void handleSign(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength dataLength -= 4; } dataPresent = false; + dataContext.tokenContext.pluginAvailable = 0; initTx(&txContext, &global_sha3, &tmpContent.txContent, customProcessor, NULL); } else From 3e4fb33c689b43571212961a57629def0a23f1c9 Mon Sep 17 00:00:00 2001 From: BTChip github Date: Tue, 27 Oct 2020 11:18:31 +0100 Subject: [PATCH 06/13] Fix parsing --- src_features/signTx/logic_signTx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index 1814e03..4cc9faf 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -72,6 +72,9 @@ customStatus_e customProcessor(txContext_t *context) { blockSize = 4; } else { + if (!N_storage.contractDetails) { + return CUSTOM_NOT_HANDLED; + } blockSize = 32 - (dataContext.tokenContext.fieldOffset % 32); } From 193ff80cfc72a9dc32ab9f3e0bf17824f825beb1 Mon Sep 17 00:00:00 2001 From: BTChip github Date: Fri, 30 Oct 2020 14:31:24 +0100 Subject: [PATCH 07/13] Fix parsing --- src_features/signTx/logic_signTx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index 4cc9faf..90bfcc3 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -72,7 +72,7 @@ customStatus_e customProcessor(txContext_t *context) { blockSize = 4; } else { - if (!N_storage.contractDetails) { + if (!N_storage.contractDetails && !dataContext.tokenContext.pluginAvailable) { return CUSTOM_NOT_HANDLED; } blockSize = 32 - (dataContext.tokenContext.fieldOffset % 32); From 6a754b5189da269ddc18d616ccab5dad32200779 Mon Sep 17 00:00:00 2001 From: TamtamHero <10632523+TamtamHero@users.noreply.github.com> Date: Thu, 24 Sep 2020 17:02:57 +0200 Subject: [PATCH 08/13] Add ERC20 allowance (approve) support in internal ERC20 plugin --- src/eth_plugin_internal.c | 9 +- src/eth_plugin_internal.h | 4 + src_plugins/erc20/erc20_plugin.c | 169 ++++++++++++++++++++++++++++--- 3 files changed, 166 insertions(+), 16 deletions(-) diff --git a/src/eth_plugin_internal.c b/src/eth_plugin_internal.c index a7023a9..1bc89b8 100644 --- a/src/eth_plugin_internal.c +++ b/src/eth_plugin_internal.c @@ -3,9 +3,12 @@ void erc20_plugin_call(int message, void *parameters); void starkware_plugin_call(int message, void *parameters); -static const uint8_t const ERC20_SELECTOR[SELECTOR_SIZE] = { 0xa9, 0x05, 0x9c, 0xbb }; +static const uint8_t const ERC20_TRANSFER_SELECTOR[SELECTOR_SIZE] = { 0xa9, 0x05, 0x9c, 0xbb }; +static const uint8_t const ERC20_APPROVE_SELECTOR[SELECTOR_SIZE] = { 0x09, 0x5e, 0xa7, 0xb3 }; -static const uint8_t* const ERC20_SELECTORS[1] = { ERC20_SELECTOR }; +const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS] = { + ERC20_TRANSFER_SELECTOR, ERC20_APPROVE_SELECTOR +}; #ifdef HAVE_STARKWARE @@ -34,7 +37,7 @@ const uint8_t* const STARKWARE_SELECTORS[NUM_STARKWARE_SELECTORS] = { const internalEthPlugin_t const INTERNAL_ETH_PLUGINS[NUM_INTERNAL_PLUGINS] = { { ERC20_SELECTORS, - 1, + 2, "-erc20", erc20_plugin_call }, diff --git a/src/eth_plugin_internal.h b/src/eth_plugin_internal.h index 59b76a5..17e9da3 100644 --- a/src/eth_plugin_internal.h +++ b/src/eth_plugin_internal.h @@ -11,6 +11,10 @@ typedef struct internalEthPlugin_t { PluginCall impl; } internalEthPlugin_t; +#define NUM_ERC20_SELECTORS 2 + +extern const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS]; + #ifdef HAVE_STARKWARE #define NUM_INTERNAL_PLUGINS 2 diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c index 4bb3525..fab3df0 100644 --- a/src_plugins/erc20/erc20_plugin.c +++ b/src_plugins/erc20/erc20_plugin.c @@ -1,20 +1,86 @@ #include -#include "eth_plugin_interface.h" +#include "eth_plugin_internal.h" +#include "eth_plugin_handler.h" +#include "shared_context.h" +#include "ethUtils.h" +#include "utils.h" + +typedef enum { + ERC20_TRANSFER = 0, + ERC20_APPROVE +} erc20Selector_t; + +typedef enum { + TARGET_ADDRESS = 0, + TARGET_CONTRACT, + TARGET_COMPOUND +} targetType_t; typedef struct erc20_parameters_t { - - uint8_t destinationAddress[20]; + uint8_t selectorIndex; + uint8_t destinationAddress[21]; uint8_t amount[32]; - + uint8_t ticker_1[MAX_TICKER_LEN]; + uint8_t ticker_2[MAX_TICKER_LEN]; + uint8_t decimals; + uint8_t target; } erc20_parameters_t; +typedef struct ticker_binding_t { + char ticker1[MAX_TICKER_LEN]; + char ticker2[MAX_TICKER_LEN]; +} ticker_binding_t; + +#define NUM_COMPOUND_BINDINGS 9 +const ticker_binding_t const COMPOUND_BINDINGS[NUM_COMPOUND_BINDINGS] = { + {"DAI", "CDAI"}, + {"WETH", "CETH"}, + {"USDC", "CUSDC"}, + {"ZRX", "CZRX"}, + {"USDT", "CUSDT"}, + {"WBTC", "CBTC"}, + {"BAT", "CBAT"}, + {"REPv2", "CREP"}, + {"SAI", "CSAI"}, +}; + +bool check_token_binding(char* ticker1, char* ticker2, const ticker_binding_t* bindings, size_t num_bindings){ + for(size_t i = 0; i < num_bindings; i++){ + ticker_binding_t* binding = (ticker_binding_t *)PIC(&bindings[i]); + if (strncmp(binding->ticker1, ticker1, strnlen(binding->ticker1, MAX_TICKER_LEN)) == 0 && + strncmp(binding->ticker2, ticker2, strnlen(binding->ticker2, MAX_TICKER_LEN)) == 0){ + return true; + } + } + return false; +} + void erc20_plugin_call(int message, void *parameters) { switch(message) { case ETH_PLUGIN_INIT_CONTRACT: { ethPluginInitContract_t *msg = (ethPluginInitContract_t*)parameters; - PRINTF("erc20 plugin init\n"); - msg->result = ETH_PLUGIN_RESULT_OK; + erc20_parameters_t *context = (erc20_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\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; + } + else { + size_t i; + for (i=0; iselector, SELECTOR_SIZE) == 0) { + context->selectorIndex = i; + break; + } + } + if (i == NUM_ERC20_SELECTORS) { + PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector); + break; + } + PRINTF("erc20 plugin init\n"); + msg->result = ETH_PLUGIN_RESULT_OK; + } } break; @@ -42,18 +108,95 @@ void erc20_plugin_call(int message, void *parameters) { ethPluginFinalize_t *msg = (ethPluginFinalize_t*)parameters; erc20_parameters_t *context = (erc20_parameters_t*)msg->pluginContext; PRINTF("erc20 plugin finalize\n"); - msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; - msg->amount = context->amount; - msg->address = context->destinationAddress; - msg->uiType = ETH_UI_TYPE_AMOUNT_ADDRESS; - msg->result = ETH_PLUGIN_RESULT_OK; + if (context->selectorIndex == ERC20_TRANSFER){ + msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; + msg->amount = context->amount; + msg->address = context->destinationAddress; + msg->uiType = ETH_UI_TYPE_AMOUNT_ADDRESS; + msg->result = ETH_PLUGIN_RESULT_OK; + } + else if (context->selectorIndex == ERC20_APPROVE){ + msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; + msg->tokenLookup2 = context->destinationAddress; + msg->numScreens = 2; + msg->uiType = ETH_UI_TYPE_GENERIC; + msg->result = ETH_PLUGIN_RESULT_OK; + } } break; case ETH_PLUGIN_PROVIDE_TOKEN: { ethPluginProvideToken_t *msg = (ethPluginProvideToken_t*)parameters; - PRINTF("erc20 plugin provide token %d\n", (msg->token1 != NULL)); - msg->result = (msg->token1 != NULL ? ETH_PLUGIN_RESULT_OK : ETH_PLUGIN_RESULT_FALLBACK); + 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) { + context->target = TARGET_ADDRESS; + strcpy((char *)context->ticker_1, (char *)msg->token1->ticker); + context->decimals = msg->token1->decimals; + if (context->selectorIndex == ERC20_APPROVE){ + if(msg->token2 != NULL){ + context->target = TARGET_CONTRACT; + strcpy((char *)context->ticker_2, (char *)msg->token2->ticker); + // test if we're doing a Compound allowance + if (check_token_binding((char *)msg->token1->ticker, (char *)msg->token2->ticker, COMPOUND_BINDINGS, NUM_COMPOUND_BINDINGS)){ + context->target = TARGET_COMPOUND; + } + } + } + msg->result = ETH_PLUGIN_RESULT_OK; + } + else { + msg->result = ETH_PLUGIN_RESULT_FALLBACK; + } + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_ID: { + ethQueryContractID_t *msg = (ethQueryContractID_t*)parameters; + strcpy(msg->name, "Approve"); + strcpy(msg->version, ""); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_UI: { + ethQueryContractUI_t *msg = (ethQueryContractUI_t*)parameters; + erc20_parameters_t *context = (erc20_parameters_t*)msg->pluginContext; + switch(msg->screenIndex) { + case 0: + strcpy(msg->title, "Amount"); + if(ismaxint(context->amount, sizeof(context->amount))){ + strcpy(msg->msg, "Unlimited "); + strcat(msg->msg, (char *)context->ticker_1); + } + else{ + amountToString(context->amount, sizeof(context->amount), context->decimals, (char *)context->ticker_1, msg->msg, 100); + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + case 1: + if(context->target >= TARGET_CONTRACT){ + strcpy(msg->title, "Contract"); + if (context->target == TARGET_COMPOUND){ + strcpy(msg->msg, "Compound "); + strcat(msg->msg, (char *)context->ticker_2 + 1); // remove the 'c' char at beginning of compound ticker + } + else { + strcpy(msg->msg, (char *)context->ticker_2); + } + } + else{ + strcpy(msg->title, "Address"); + msg->msg[0] = '0'; + msg->msg[1] = 'x'; + getEthAddressStringFromBinary(context->destinationAddress, (uint8_t *)msg->msg + 2, msg->pluginSharedRW->sha3, chainConfig); + } + + msg->result = ETH_PLUGIN_RESULT_OK; + break; + default: + break; + } } break; From 4f2cef2e20371dbb31f343b7ecb6e4c75a7fccd9 Mon Sep 17 00:00:00 2001 From: TamtamHero <10632523+TamtamHero@users.noreply.github.com> Date: Mon, 5 Oct 2020 18:22:42 +0200 Subject: [PATCH 09/13] Add Compound support as an internal plugin --- src/eth_plugin_handler.h | 1 + src/eth_plugin_internal.c | 18 ++ src/eth_plugin_internal.h | 10 +- src/eth_plugin_ui.c | 2 +- src_features/signTx/logic_signTx.c | 5 +- src_plugins/compound/compound_plugin.c | 224 +++++++++++++++++++++++++ src_plugins/erc20/erc20_plugin.c | 4 +- 7 files changed, 254 insertions(+), 10 deletions(-) create mode 100644 src_plugins/compound/compound_plugin.c diff --git a/src/eth_plugin_handler.h b/src/eth_plugin_handler.h index e8a1a25..338017b 100644 --- a/src/eth_plugin_handler.h +++ b/src/eth_plugin_handler.h @@ -12,6 +12,7 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI, int eth_plugin_perform_init(uint8_t *contractAddress, ethPluginInitContract_t *init); // NULL for cached address, or base contract address int eth_plugin_call(uint8_t *contractAddress, int method, void *parameter); +int compound_plugin_call(uint8_t *contractAddress, int method, void *parameter); void plugin_ui_start(void); diff --git a/src/eth_plugin_internal.c b/src/eth_plugin_internal.c index 1bc89b8..ca1fe37 100644 --- a/src/eth_plugin_internal.c +++ b/src/eth_plugin_internal.c @@ -1,6 +1,7 @@ #include "eth_plugin_internal.h" void erc20_plugin_call(int message, void *parameters); +void compound_plugin_call(int message, void *parameters); void starkware_plugin_call(int message, void *parameters); static const uint8_t const ERC20_TRANSFER_SELECTOR[SELECTOR_SIZE] = { 0xa9, 0x05, 0x9c, 0xbb }; @@ -10,6 +11,16 @@ const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS] = { ERC20_TRANSFER_SELECTOR, ERC20_APPROVE_SELECTOR }; +static const uint8_t const COMPOUND_REDEEM_UNDERLYING_SELECTOR[SELECTOR_SIZE] = { 0x85, 0x2a, 0x12, 0xe3 }; +static const uint8_t const COMPOUND_REDEEM_SELECTOR[SELECTOR_SIZE] = { 0xdb, 0x00, 0x6a, 0x75 }; +static const uint8_t const COMPOUND_MINT_SELECTOR[SELECTOR_SIZE] = { 0xa0, 0x71, 0x2d, 0x68 }; +static const uint8_t const CETH_MINT_SELECTOR[SELECTOR_SIZE] = { 0x12, 0x49, 0xc5, 0x8b }; + +const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS] = { + COMPOUND_REDEEM_UNDERLYING_SELECTOR, COMPOUND_REDEEM_SELECTOR, + COMPOUND_MINT_SELECTOR, CETH_MINT_SELECTOR +}; + #ifdef HAVE_STARKWARE static const uint8_t const STARKWARE_REGISTER_ID[SELECTOR_SIZE] = { 0x76, 0x57, 0x18, 0xd7 }; @@ -42,6 +53,13 @@ const internalEthPlugin_t const INTERNAL_ETH_PLUGINS[NUM_INTERNAL_PLUGINS] = { erc20_plugin_call }, + { + COMPOUND_SELECTORS, + 4, + "-cmpd", + compound_plugin_call + }, + #ifdef HAVE_STARKWARE { diff --git a/src/eth_plugin_internal.h b/src/eth_plugin_internal.h index 17e9da3..38540ab 100644 --- a/src/eth_plugin_internal.h +++ b/src/eth_plugin_internal.h @@ -12,20 +12,22 @@ typedef struct internalEthPlugin_t { } internalEthPlugin_t; #define NUM_ERC20_SELECTORS 2 - extern const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS]; +#define NUM_COMPOUND_SELECTORS 4 +extern const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS]; + + #ifdef HAVE_STARKWARE -#define NUM_INTERNAL_PLUGINS 2 +#define NUM_INTERNAL_PLUGINS 3 #define NUM_STARKWARE_SELECTORS 10 - extern const uint8_t* const STARKWARE_SELECTORS[NUM_STARKWARE_SELECTORS]; #else -#define NUM_INTERNAL_PLUGINS 1 +#define NUM_INTERNAL_PLUGINS 2 #endif diff --git a/src/eth_plugin_ui.c b/src/eth_plugin_ui.c index d38642f..281de79 100644 --- a/src/eth_plugin_ui.c +++ b/src/eth_plugin_ui.c @@ -134,7 +134,7 @@ UX_STEP_NOCB_INIT( bnnn_paging, plugin_ui_compute_fees(), { - .title = "Max fees", + .title = "Max Fees", .text = strings.common.maxFee }); diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index 90bfcc3..b4c7383 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -51,9 +51,8 @@ customStatus_e customProcessor(txContext_t *context) { dataContext.tokenContext.fieldOffset = 0; copyTxData(context, NULL, 4); if (context->currentFieldLength == 4) { - dataContext.tokenContext.fieldIndex++; - return CUSTOM_HANDLED; - } + return CUSTOM_NOT_HANDLED; + } } } uint32_t blockSize; diff --git a/src_plugins/compound/compound_plugin.c b/src_plugins/compound/compound_plugin.c new file mode 100644 index 0000000..815d71c --- /dev/null +++ b/src_plugins/compound/compound_plugin.c @@ -0,0 +1,224 @@ +#include +#include "eth_plugin_interface.h" +#include "shared_context.h" // TODO : rewrite as independant code +#include "eth_plugin_internal.h" // TODO : rewrite as independant code +#include "utils.h" + +typedef enum { + COMPOUND_REDEEM_UNDERLYING = 0, + COMPOUND_REDEEM, + COMPOUND_MINT, + CETH_MINT +} compoundSelector_t; + +static const uint8_t COMPOUND_EXPECTED_DATA_SIZE[] = { + 4 + 32, + 4 + 32, + 4 + 32, + 4, +}; + +// redeemUnderlying : redeemAmount (32) +// redeem underlying token +// redeem : redeemTokens (32) +// redeem Ctoken +// mint : mintAmount (32) +// lend some token +// mint : +// lend some Ether + +typedef struct compound_parameters_t { + uint8_t selectorIndex; + uint8_t amount[32]; + uint8_t ticker_1[MAX_TICKER_LEN]; + uint8_t decimals; +} compound_parameters_t; + +typedef struct underlying_asset_decimals_t { + char c_ticker[MAX_TICKER_LEN]; + uint8_t decimals; +} underlying_asset_decimals_t; + + + +/* Sadly, we don't have the information about the underlying asset's decimals, which can differ from the cToken decimals. +Therefore, we hardcode a binding table. If Compound adds a lot of token in the future, we will have to move to a CAL +based architecture instead, as this one doesn't scale well.*/ +#define NUM_COMPOUND_BINDINGS 9 +const underlying_asset_decimals_t const UNDERLYING_ASSET_DECIMALS[NUM_COMPOUND_BINDINGS] = { + {"cDAI", 18}, + {"CETH", 18}, + {"CUSDC", 6}, + {"CZRX", 18}, + {"CUSDT", 6}, + {"CBTC", 8}, + {"CBAT", 18}, + {"CREP", 18}, + {"cSAI", 18}, +}; + +bool get_underlying_asset_decimals(char* compound_ticker, uint8_t* out_decimals){ + for(size_t i = 0; i < NUM_COMPOUND_BINDINGS; i++){ + underlying_asset_decimals_t* binding = (underlying_asset_decimals_t *)PIC(&UNDERLYING_ASSET_DECIMALS[i]); + if (strncmp(binding->c_ticker, compound_ticker, strnlen(binding->c_ticker, MAX_TICKER_LEN)) == 0){ + *out_decimals = binding->decimals; + return true; + } + } + return false; +} + +void compound_plugin_call(int message, void *parameters) { + + switch(message) { + case ETH_PLUGIN_INIT_CONTRACT: { + ethPluginInitContract_t *msg = (ethPluginInitContract_t*)parameters; + compound_parameters_t *context = (compound_parameters_t*)msg->pluginContext; + size_t i; + for (i=0; iselector, SELECTOR_SIZE) == 0) { + context->selectorIndex = i; + break; + } + } + // 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){ + msg->result = ETH_PLUGIN_RESULT_ERROR; + } + } + if(context->selectorIndex == CETH_MINT){ + // ETH amount 0x1234 is stored 0x12340000...000 instead of 0x00....001234, so we strip the following zeroes when copying + memset(context->amount, 0, sizeof(context->amount)); + memmove(context->amount + sizeof(context->amount) - msg->pluginSharedRO->txContent->value.length, msg->pluginSharedRO->txContent->value.value, 32); + } + if (i == NUM_COMPOUND_SELECTORS) { + PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector); + break; + } + if (msg->dataSize != COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex]) { + PRINTF("Unexpected data size for command %d expected %d got %d\n", context->selectorIndex, + COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex], msg->dataSize); + break; + } + PRINTF("compound plugin init\n"); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_PROVIDE_PARAMETER : { + ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t*)parameters; + compound_parameters_t *context = (compound_parameters_t*)msg->pluginContext; + PRINTF("compound plugin provide parameter %d %.*H\n", msg->parameterOffset, 32, msg->parameter); + if (context->selectorIndex != CETH_MINT){ + switch(msg->parameterOffset) { + case 4: + memmove(context->amount, msg->parameter, 32); + msg->result = ETH_PLUGIN_RESULT_OK; + break; + default: + PRINTF("Unhandled parameter offset\n"); + break; + } + } + } + break; + + case ETH_PLUGIN_FINALIZE: { + ethPluginFinalize_t *msg = (ethPluginFinalize_t*)parameters; + PRINTF("compound plugin finalize\n"); + msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; + msg->numScreens = 2; + msg->uiType = ETH_UI_TYPE_GENERIC; + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_PROVIDE_TOKEN: { + ethPluginProvideToken_t *msg = (ethPluginProvideToken_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) { + strcpy((char *)context->ticker_1, (char *)msg->token1->ticker); + switch (context->selectorIndex) + { + case COMPOUND_REDEEM_UNDERLYING: + case COMPOUND_MINT: + case CETH_MINT: + msg->result = get_underlying_asset_decimals(context->ticker_1, &context->decimals) ? ETH_PLUGIN_RESULT_OK : ETH_PLUGIN_RESULT_FALLBACK; + break; + + // Only case where we use the compound contract decimals + case COMPOUND_REDEEM: + context->decimals = msg->token1->decimals; + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + default: + msg->result = ETH_PLUGIN_RESULT_FALLBACK; + break; + } + } + else { + msg->result = ETH_PLUGIN_RESULT_FALLBACK; + } + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_ID: { + ethQueryContractID_t *msg = (ethQueryContractID_t*)parameters; + compound_parameters_t *context = (compound_parameters_t*)msg->pluginContext; + strcpy(msg->name, "Type"); + switch (context->selectorIndex) + { + case COMPOUND_REDEEM_UNDERLYING: + case COMPOUND_REDEEM: + strcpy(msg->version, "Redeem"); + break; + + case COMPOUND_MINT: + case CETH_MINT: + strcpy(msg->version, "Lend"); + break; + + default: + break; + } + strcat(msg->version, " Assets"); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case ETH_PLUGIN_QUERY_CONTRACT_UI: { + ethQueryContractUI_t *msg = (ethQueryContractUI_t*)parameters; + compound_parameters_t *context = (compound_parameters_t*)msg->pluginContext; + switch(msg->screenIndex) { + case 0: { + strcpy(msg->title, "Amount"); + char * ticker_ptr = (char *)context->ticker_1; + /* skip "c" in front of cToken unless we use "redeem", as + redeem is the only operation dealing with a cToken amount */ + if(context->selectorIndex != COMPOUND_REDEEM){ + ticker_ptr++; + } + amountToString(context->amount, sizeof(context->amount), context->decimals, ticker_ptr, msg->msg, 100); + msg->result = ETH_PLUGIN_RESULT_OK; + } + break; + + case 1: + strcpy(msg->title, "Contract"); + strcpy(msg->msg, "Compound "); + strcat(msg->msg, (char *)context->ticker_1 + 1); // remove the 'c' char at beginning of compound ticker + msg->result = ETH_PLUGIN_RESULT_OK; + break; + default: + break; + } + } + break; + + default: + PRINTF("Unhandled message %d\n", message); + } +} diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c index fab3df0..552f7dc 100644 --- a/src_plugins/erc20/erc20_plugin.c +++ b/src_plugins/erc20/erc20_plugin.c @@ -153,8 +153,8 @@ void erc20_plugin_call(int message, void *parameters) { case ETH_PLUGIN_QUERY_CONTRACT_ID: { ethQueryContractID_t *msg = (ethQueryContractID_t*)parameters; - strcpy(msg->name, "Approve"); - strcpy(msg->version, ""); + strcpy(msg->name, "Type"); + strcpy(msg->version, "Approve"); msg->result = ETH_PLUGIN_RESULT_OK; } break; From ba2592ed42a3ff25e92d3a297c66f9b67b4edd08 Mon Sep 17 00:00:00 2001 From: BTChip github Date: Fri, 23 Oct 2020 21:45:20 +0200 Subject: [PATCH 10/13] Fix Starkware withdrawal address --- src_plugins/starkware/starkware_plugin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_plugins/starkware/starkware_plugin.c b/src_plugins/starkware/starkware_plugin.c index 555e62f..14f9f2b 100644 --- a/src_plugins/starkware/starkware_plugin.c +++ b/src_plugins/starkware/starkware_plugin.c @@ -379,7 +379,7 @@ void starkware_plugin_call(int message, void *parameters) { break; case STARKWARE_WITHDRAW: strcpy(msg->title, "To ETH Address"); - starkware_print_eth_address(tmpContent.txContent.destination, msg->msg); + starkware_get_source_address(msg->msg); break; case STARKWARE_ESCAPE: strcpy(msg->title, "Amount"); From dd5fc505facf86a4aead15e78a9d375d7b09699b Mon Sep 17 00:00:00 2001 From: TamtamHero <10632523+TamtamHero@users.noreply.github.com> Date: Fri, 30 Oct 2020 14:50:18 +0100 Subject: [PATCH 11/13] Remove unused code --- src/eth_plugin_handler.c | 10 ---------- src/handle_swap_sign_transaction.c | 2 -- src/main.c | 4 ---- src/shared_context.h | 2 -- 4 files changed, 18 deletions(-) diff --git a/src/eth_plugin_handler.c b/src/eth_plugin_handler.c index 4b9da53..fd4d206 100644 --- a/src/eth_plugin_handler.c +++ b/src/eth_plugin_handler.c @@ -61,8 +61,6 @@ int eth_plugin_perform_init(uint8_t *contractAddress, ethPluginInitContract_t *i } } -#ifndef TARGET_BLUE - // Do not handle a plugin if running in swap mode if (called_from_swap && (contractAddress != NULL)) { PRINTF("eth_plug_init aborted in swap mode\n"); @@ -90,14 +88,6 @@ int eth_plugin_perform_init(uint8_t *contractAddress, ethPluginInitContract_t *i PRINTF("eth_plugin_init ok %s\n", dataContext.tokenContext.pluginName); dataContext.tokenContext.pluginAvailable = 1; return 1; - -#else - - // Disable plugins on Ledger Blue for the time being - - return 0; -#endif - } int eth_plugin_call(uint8_t *contractAddress, int method, void *parameter) { diff --git a/src/handle_swap_sign_transaction.c b/src/handle_swap_sign_transaction.c index ccfa280..3db3b93 100644 --- a/src/handle_swap_sign_transaction.c +++ b/src/handle_swap_sign_transaction.c @@ -47,8 +47,6 @@ void handle_swap_sign_transaction(create_transaction_parameters_t* sign_transact storage.initialized = 0x01; nvm_write((void*)&N_storage, (void*)&storage, sizeof(internalStorage_t)); } - dataAllowed = N_storage.dataAllowed; - contractDetails = N_storage.contractDetails; UX_INIT(); USB_power(0); diff --git a/src/main.c b/src/main.c index 24b5377..ec57c7d 100644 --- a/src/main.c +++ b/src/main.c @@ -48,8 +48,6 @@ dataContext_t dataContext; strings_t strings; cx_sha3_t global_sha3; -uint8_t dataAllowed; -uint8_t contractDetails; uint8_t appState; bool dataPresent; bool called_from_swap; @@ -627,8 +625,6 @@ void coin_main_with_config(chain_config_t *config) { storage.initialized = 0x01; nvm_write((void*)&N_storage, (void*)&storage, sizeof(internalStorage_t)); } - dataAllowed = N_storage.dataAllowed; - contractDetails = N_storage.contractDetails; USB_power(0); USB_power(1); diff --git a/src/shared_context.h b/src/shared_context.h index 0fc4485..054ef0f 100644 --- a/src/shared_context.h +++ b/src/shared_context.h @@ -163,8 +163,6 @@ extern cx_sha3_t global_sha3; extern const internalStorage_t N_storage_real; extern bool called_from_swap; -extern uint8_t dataAllowed; -extern uint8_t contractDetails; extern bool dataPresent; extern uint8_t appState; #ifdef HAVE_STARKWARE From e7428586d1ed5b4ef7f5da6ecabf3fd60dbfe1b1 Mon Sep 17 00:00:00 2001 From: BTChip github Date: Fri, 13 Nov 2020 20:56:44 +0100 Subject: [PATCH 12/13] Fix Starkware Verify Escape parsing --- src_plugins/starkware/starkware_plugin.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src_plugins/starkware/starkware_plugin.c b/src_plugins/starkware/starkware_plugin.c index 14f9f2b..154038c 100644 --- a/src_plugins/starkware/starkware_plugin.c +++ b/src_plugins/starkware/starkware_plugin.c @@ -290,8 +290,14 @@ void starkware_plugin_call(int message, void *parameters) { break; default: - PRINTF("Unhandled parameter offset\n"); - break; + switch(context->selectorIndex) { + case STARKWARE_VERIFY_ESCAPE: + msg->result = ETH_PLUGIN_RESULT_OK; + break; + default: + PRINTF("Unhandled parameter offset\n"); + break; + } } } break; From d432e574319689eade45db03f3060fbcd0cf644d Mon Sep 17 00:00:00 2001 From: TamtamHero <10632523+TamtamHero@users.noreply.github.com> Date: Sat, 14 Nov 2020 18:14:34 +0100 Subject: [PATCH 13/13] Make plugins result status explicits on every case --- src_plugins/compound/compound_plugin.c | 28 +++++++++++++++++--------- src_plugins/erc20/erc20_plugin.c | 2 ++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src_plugins/compound/compound_plugin.c b/src_plugins/compound/compound_plugin.c index 815d71c..4985bc6 100644 --- a/src_plugins/compound/compound_plugin.c +++ b/src_plugins/compound/compound_plugin.c @@ -85,23 +85,26 @@ void compound_plugin_call(int message, void *parameters) { if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)){ if(context->selectorIndex != CETH_MINT){ msg->result = ETH_PLUGIN_RESULT_ERROR; + break; } } + if (i == NUM_COMPOUND_SELECTORS) { + PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector); + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } + if (msg->dataSize != COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex]) { + PRINTF("Unexpected data size for command %d expected %d got %d\n", context->selectorIndex, + COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex], msg->dataSize); + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } if(context->selectorIndex == CETH_MINT){ // ETH amount 0x1234 is stored 0x12340000...000 instead of 0x00....001234, so we strip the following zeroes when copying memset(context->amount, 0, sizeof(context->amount)); memmove(context->amount + sizeof(context->amount) - msg->pluginSharedRO->txContent->value.length, msg->pluginSharedRO->txContent->value.value, 32); } - if (i == NUM_COMPOUND_SELECTORS) { - PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector); - break; - } - if (msg->dataSize != COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex]) { - PRINTF("Unexpected data size for command %d expected %d got %d\n", context->selectorIndex, - COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex], msg->dataSize); - break; - } - PRINTF("compound plugin init\n"); + PRINTF("compound plugin inititialized\n"); msg->result = ETH_PLUGIN_RESULT_OK; } break; @@ -118,9 +121,14 @@ void compound_plugin_call(int message, void *parameters) { break; default: PRINTF("Unhandled parameter offset\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; break; } } + else { + PRINTF("CETH contract expects no parameters\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; + } } break; diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c index 552f7dc..c3b6f1c 100644 --- a/src_plugins/erc20/erc20_plugin.c +++ b/src_plugins/erc20/erc20_plugin.c @@ -76,6 +76,7 @@ void erc20_plugin_call(int message, void *parameters) { } if (i == NUM_ERC20_SELECTORS) { PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector); + msg->result = ETH_PLUGIN_RESULT_ERROR; break; } PRINTF("erc20 plugin init\n"); @@ -99,6 +100,7 @@ void erc20_plugin_call(int message, void *parameters) { break; default: PRINTF("Unhandled parameter offset\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; break; } }