Merge pull request #110 from LedgerHQ/compound

Add plugins and Compound support
This commit is contained in:
Jean P
2020-11-16 17:44:12 +01:00
committed by GitHub
25 changed files with 2479 additions and 1071 deletions

View File

@@ -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)

292
doc/ethapp_plugins.asc Normal file
View File

@@ -0,0 +1,292 @@
Ethereum application Plugins : Technical Specifications
=======================================================
Ledger Firmware Team <hello@ledger.fr>
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, ...)

View File

@@ -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)

213
src/base64.c Normal file
View File

@@ -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 <http://www.apache.org/>.
*
*/
/* Base64 encoder/decoder. Originally Apache file ap_base64.c
*/
#include <string.h>
#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;
}

105
src/base64.h Normal file
View File

@@ -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 <http://www.apache.org/>.
*
*/
#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_

240
src/eth_plugin_handler.c Normal file
View File

@@ -0,0 +1,240 @@
#include <string.h>
#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; i<NUM_INTERNAL_PLUGINS; i++) {
const uint8_t **selectors = PIC(INTERNAL_ETH_PLUGINS[i].selectors);
uint8_t j;
for (j=0; ((j<INTERNAL_ETH_PLUGINS[i].num_selectors) && (contractAddress != NULL)); j++) {
if (memcmp(init->selector, PIC(selectors[j]), SELECTOR_SIZE) == 0) {
strcpy(dataContext.tokenContext.pluginName, INTERNAL_ETH_PLUGINS[i].alias);
dataContext.tokenContext.pluginAvailable = 1;
contractAddress = NULL;
break;
}
}
}
// 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;
}
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; i<NUM_INTERNAL_PLUGINS; i++) {
if (strcmp(alias, INTERNAL_ETH_PLUGINS[i].alias) == 0) {
((PluginCall)PIC(INTERNAL_ETH_PLUGINS[i].impl))(method, parameter);
break;
}
}
if (i == NUM_INTERNAL_PLUGINS) {
uint32_t params[3];
params[0] = (uint32_t)alias;
params[1] = method;
params[2] = (uint32_t)parameter;
BEGIN_TRY {
TRY {
os_lib_call(params);
}
CATCH_OTHER(e) {
PRINTF("Plugin call exception for %s\n", alias);
}
FINALLY {
}
}
END_TRY;
}
// Check the call result
switch(method) {
case ETH_PLUGIN_INIT_CONTRACT:
switch (((ethPluginInitContract_t*)parameter)->result) {
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;
}

20
src/eth_plugin_handler.h Normal file
View File

@@ -0,0 +1,20 @@
#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);
int compound_plugin_call(uint8_t *contractAddress, int method, void *parameter);
void plugin_ui_start(void);
#endif

171
src/eth_plugin_interface.h Normal file
View File

@@ -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

73
src/eth_plugin_internal.c Normal file
View File

@@ -0,0 +1,73 @@
#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 };
static const uint8_t const ERC20_APPROVE_SELECTOR[SELECTOR_SIZE] = { 0x09, 0x5e, 0xa7, 0xb3 };
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 };
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,
2,
"-erc20",
erc20_plugin_call
},
{
COMPOUND_SELECTORS,
4,
"-cmpd",
compound_plugin_call
},
#ifdef HAVE_STARKWARE
{
STARKWARE_SELECTORS,
10,
"-strk",
starkware_plugin_call
},
#endif
};

36
src/eth_plugin_internal.h Normal file
View File

@@ -0,0 +1,36 @@
#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;
#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 3
#define NUM_STARKWARE_SELECTORS 10
extern const uint8_t* const STARKWARE_SELECTORS[NUM_STARKWARE_SELECTORS];
#else
#define NUM_INTERNAL_PLUGINS 2
#endif
extern internalEthPlugin_t const INTERNAL_ETH_PLUGINS[NUM_INTERNAL_PLUGINS];
#endif

175
src/eth_plugin_ui.c Normal file
View File

@@ -0,0 +1,175 @@
#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();
}
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;
#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;
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",
});
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;
dataContext.tokenContext.pluginUiCurrentItem = 0;
ux_flow_init(0, ux_plugin_approval_flow, NULL);
}

View File

@@ -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);

View File

@@ -48,11 +48,8 @@ dataContext_t dataContext;
strings_t strings;
cx_sha3_t global_sha3;
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 +67,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;
@@ -629,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);

View File

@@ -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
@@ -161,11 +163,8 @@ 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;
extern contract_call_t contractProvisioned;
#ifdef HAVE_STARKWARE
extern bool quantumSet;
#endif

View File

@@ -34,7 +34,7 @@ void handleSign(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength
dataLength -= 4;
}
dataPresent = false;
contractProvisioned = CONTRACT_NONE;
dataContext.tokenContext.pluginAvailable = 0;
initTx(&txContext, &global_sha3, &tmpContent.txContent, customProcessor, NULL);
}
else

View File

@@ -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,114 @@ 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);
if (context->currentFieldLength == 4) {
return CUSTOM_NOT_HANDLED;
}
}
}
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 {
if (!N_storage.contractDetails && !dataContext.tokenContext.pluginAvailable) {
return CUSTOM_NOT_HANDLED;
}
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 +167,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 +198,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 +208,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 +344,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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,232 @@
#include <string.h>
#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; i<NUM_COMPOUND_SELECTORS; i++) {
if (memcmp((uint8_t *)PIC(COMPOUND_SELECTORS[i]), msg->selector, 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;
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);
}
PRINTF("compound plugin inititialized\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");
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
else {
PRINTF("CETH contract expects no parameters\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
}
}
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);
}
}

View File

@@ -0,0 +1,208 @@
#include <string.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 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;
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; i<NUM_ERC20_SELECTORS; i++) {
if (memcmp((uint8_t *)PIC(ERC20_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) {
context->selectorIndex = i;
break;
}
}
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");
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");
msg->result = ETH_PLUGIN_RESULT_ERROR;
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");
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;
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, "Type");
strcpy(msg->version, "Approve");
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;
default:
PRINTF("Unhandled message %d\n", message);
}
}

View File

@@ -0,0 +1,450 @@
#include <string.h>
#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; i<NUM_STARKWARE_SELECTORS; i++) {
if (memcmp(PIC(STARKWARE_SELECTORS[i]), msg->selector, 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:
switch(context->selectorIndex) {
case STARKWARE_VERIFY_ESCAPE:
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_get_source_address(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);
}
}