diff --git a/doc/ethapp.asc b/doc/ethapp.asc index 8f6d622..c22034f 100644 --- a/doc/ethapp.asc +++ b/doc/ethapp.asc @@ -1,7 +1,7 @@ Ethereum application : Common Technical Specifications ======================================================= Ledger Firmware Team -Application version 1.2 - 19th of August 2017 +Application version 1.1.10 - 4th of February 2019 ## 1.0 - Initial release @@ -13,6 +13,9 @@ Application version 1.2 - 19th of August 2017 ## 1.2 - Add SIGN ETH PERSONAL MESSAGE +## 1.1.10 + - Add PROVIDE ERC 20 TOKEN INFORMATION + ## About This application describes the APDU messages interface to communicate with the Ethereum application. @@ -188,6 +191,47 @@ The input data is the message to sign, streamed to the device in 255 bytes maxim |============================================================================================================================== +### PROVIDE ERC 20 TOKEN INFORMATION + +#### Description + +This commands provides a trusted description of an ERC 20 token to associate a contract address with a ticker and number of decimals. + +It shall be run immediately before performing a transaction involving a contract calling this contract address to display the proper token information to the user if necessary, as marked in GET APP CONFIGURATION flags. + +The signature is computed on + +ticker || address || number of decimals (uint4be) || chainId (uint4be) + +signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f0976b22d3388a9a5e446ac62d25cf725b62a2555b2dd464a4da0ab2f4d506820543af1d242470b1b1a969a27578f353 + +#### Coding + +'Command' + +[width="80%"] +|============================================================================================================================== +| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le* +| E0 | 0A | 00 | 00 | variable | 00 +|============================================================================================================================== + +'Input data' + +[width="80%"] +|============================================================================================================================== +| *Description* | *Length* +| Length of ERC 20 ticker | 1 +| ERC 20 ticker | variable +| ERC 20 contract address | 20 +| Number of decimals (big endian encoded) | 4 +| Chain ID (big endian encoded) | 4 +| Token information signature | variable +|============================================================================================================================== + +'Output data' + +None + ### GET APP CONFIGURATION #### Description @@ -215,6 +259,8 @@ None | *Description* | *Length* | Flags 0x01 : arbitrary data signature enabled by user + + 0x02 : ERC 20 Token information needs to be provided externally | 01 | Application major version | 01 | Application minor version | 01 diff --git a/src/main.c b/src/main.c index 33776e5..0ed1892 100644 --- a/src/main.c +++ b/src/main.c @@ -51,11 +51,15 @@ void finalizeParsing(bool); #define MAX_BIP32_PATH 10 +#define APP_FLAG_DATA_ALLOWED 0x01 +#define APP_FLAG_EXTERNAL_TOKEN_NEEDED 0x02 + #define CLA 0xE0 #define INS_GET_PUBLIC_KEY 0x02 #define INS_SIGN 0x04 #define INS_GET_APP_CONFIGURATION 0x06 #define INS_SIGN_PERSONAL_MESSAGE 0x08 +#define INS_PROVIDE_ERC20_TOKEN_INFORMATION 0x0A #define P1_CONFIRM 0x01 #define P1_NON_CONFIRM 0x00 #define P2_NO_CHAINCODE 0x00 @@ -73,6 +77,22 @@ void finalizeParsing(bool); #define WEI_TO_ETHER 18 static const uint8_t const TOKEN_TRANSFER_ID[] = { 0xa9, 0x05, 0x9c, 0xbb }; + +static const uint8_t const TOKEN_SIGNATURE_PUBLIC_KEY[] = { +// production key 2019-01-11 03:07PM (erc20signer) + 0x04, + + 0x5e,0x6c,0x10,0x20,0xc1,0x4d,0xc4,0x64, + 0x42,0xfe,0x89,0xf9,0x7c,0x0b,0x68,0xcd, + 0xb1,0x59,0x76,0xdc,0x24,0xf2,0x4c,0x31, + 0x6e,0x7b,0x30,0xfe,0x4e,0x8c,0xc7,0x6b, + + 0x14,0x89,0x15,0x0c,0x21,0x51,0x4e,0xbf, + 0x44,0x0f,0xf5,0xde,0xa5,0x39,0x3d,0x83, + 0xde,0x53,0x58,0xcd,0x09,0x8f,0xce,0x8f, + 0xd0,0xf8,0x1d,0xaa,0x94,0x97,0x91,0x83 +}; + typedef struct tokenContext_t { uint8_t data[4 + 32 + 32]; uint32_t dataFieldPos; @@ -95,6 +115,7 @@ typedef struct transactionContext_t { uint8_t pathLength; uint32_t bip32Path[MAX_BIP32_PATH]; uint8_t hash[32]; + tokenDefinition_t currentToken; } transactionContext_t; typedef struct messageSigningContext_t { @@ -128,6 +149,7 @@ volatile uint8_t contractDetails; volatile char addressSummary[32]; volatile bool dataPresent; volatile bool tokenProvisioned; +volatile bool currentTokenSet; bagl_element_t tmp_element; @@ -1394,6 +1416,7 @@ uint32_t splitBinaryParameterPart(char *result, uint8_t *parameter) { tokenDefinition_t* getKnownToken() { tokenDefinition_t *currentToken = NULL; +#ifdef HAVE_TOKENS_LIST uint32_t numTokens = 0; uint32_t i; switch(chainConfig->kind) { @@ -1537,10 +1560,16 @@ tokenDefinition_t* getKnownToken() { return currentToken; } } +#endif + + if ((currentTokenSet || tokenProvisioned) && (os_memcmp(tmpCtx.transactionContext.currentToken.address, tmpContent.txContent.destination, 20) == 0)) { + currentTokenSet = false; + return &tmpCtx.transactionContext.currentToken; + } + return NULL; } - customStatus_e customProcessor(txContext_t *context) { if ((context->currentField == TX_RLP_DATA) && (context->currentFieldLength != 0)) { @@ -1672,7 +1701,7 @@ void handleGetPublicKey(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t da uint32_t bip32Path[MAX_BIP32_PATH]; uint32_t i; uint8_t bip32PathLength = *(dataBuffer++); - cx_ecfp_private_key_t privateKey; + cx_ecfp_private_key_t privateKey; if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) { @@ -1686,9 +1715,7 @@ void handleGetPublicKey(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t da THROW(0x6B00); } for (i = 0; i < bip32PathLength; i++) { - bip32Path[i] = (dataBuffer[0] << 24) | - (dataBuffer[1] << 16) | - (dataBuffer[2] << 8) | (dataBuffer[3]); + bip32Path[i] = U4BE(dataBuffer, 0); dataBuffer += 4; } tmpCtx.publicKeyContext.getChaincode = (p2 == P2_CHAINCODE); @@ -1853,6 +1880,53 @@ void finalizeParsing(bool direct) { #endif // #if TARGET_ID } +void handleProvideErc20TokenInformation(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength, volatile unsigned int *flags, volatile unsigned int *tx) { + UNUSED(p1); + UNUSED(p2); + UNUSED(flags); + uint32_t offset = 0; + uint8_t tickerLength; + uint32_t chainId; + uint8_t hash[32]; + cx_ecfp_public_key_t tokenKey; + if (dataLength < 1) { + THROW(0x6A80); + } + tickerLength = workBuffer[offset++]; + dataLength--; + if (tickerLength >= sizeof(tmpCtx.transactionContext.currentToken.ticker)) { + THROW(0x6A80); + } + if (dataLength < tickerLength + 20 + 4 + 4) { + THROW(0x6A80); + } + cx_hash_sha256(workBuffer + offset, tickerLength + 20 + 4 + 4, hash); + os_memmove(tmpCtx.transactionContext.currentToken.ticker, workBuffer + offset, tickerLength); + tmpCtx.transactionContext.currentToken.ticker[tickerLength] = '\0'; + offset += tickerLength; + dataLength -= tickerLength; + os_memmove(tmpCtx.transactionContext.currentToken.address, workBuffer + offset, 20); + offset += 20; + dataLength -= 20; + tmpCtx.transactionContext.currentToken.decimals = U4BE(workBuffer, offset); + offset += 4; + dataLength -= 4; + chainId = U4BE(workBuffer, offset); + if ((chainConfig->chainId != 0) && (chainConfig->chainId != chainId)) { + PRINTF("ChainId token mismatch\n"); + THROW(0x6A80); + } + offset += 4; + dataLength -= 4; + cx_ecfp_init_public_key(CX_CURVE_256K1, TOKEN_SIGNATURE_PUBLIC_KEY, sizeof(TOKEN_SIGNATURE_PUBLIC_KEY), &tokenKey); + if (!cx_ecdsa_verify(&tokenKey, CX_LAST, CX_SHA256, hash, 32, workBuffer + offset, dataLength)) { + PRINTF("Invalid token signature\n"); + THROW(0x6A80); + } + currentTokenSet = true; + THROW(0x9000); +} + void handleSign(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength, volatile unsigned int *flags, volatile unsigned int *tx) { UNUSED(tx); parserStatus_e txResult; @@ -1867,14 +1941,12 @@ void handleSign(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength workBuffer++; dataLength--; for (i = 0; i < tmpCtx.transactionContext.pathLength; i++) { - tmpCtx.transactionContext.bip32Path[i] = - (workBuffer[0] << 24) | (workBuffer[1] << 16) | - (workBuffer[2] << 8) | (workBuffer[3]); + tmpCtx.transactionContext.bip32Path[i] = U4BE(workBuffer, 0); workBuffer += 4; dataLength -= 4; } dataPresent = false; - tokenProvisioned = false; + tokenProvisioned = false; initTx(&txContext, &sha3, &tmpContent.txContent, customProcessor, NULL); } else @@ -1916,7 +1988,10 @@ void handleGetAppConfiguration(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint UNUSED(workBuffer); UNUSED(dataLength); UNUSED(flags); - G_io_apdu_buffer[0] = (N_storage.dataAllowed ? 0x01 : 0x00); + G_io_apdu_buffer[0] = (N_storage.dataAllowed ? APP_FLAG_DATA_ALLOWED : 0x00); +#ifndef HAVE_TOKENS_LIST + G_io_apdu_buffer[0] |= APP_FLAG_EXTERNAL_TOKEN_NEEDED; +#endif G_io_apdu_buffer[1] = LEDGER_MAJOR_VERSION; G_io_apdu_buffer[2] = LEDGER_MINOR_VERSION; G_io_apdu_buffer[3] = LEDGER_PATCH_VERSION; @@ -1942,15 +2017,11 @@ void handleSignPersonalMessage(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint workBuffer++; dataLength--; for (i = 0; i < tmpCtx.messageSigningContext.pathLength; i++) { - tmpCtx.messageSigningContext.bip32Path[i] = - (workBuffer[0] << 24) | (workBuffer[1] << 16) | - (workBuffer[2] << 8) | (workBuffer[3]); + tmpCtx.messageSigningContext.bip32Path[i] = U4BE(workBuffer, 0); workBuffer += 4; dataLength -= 4; } - tmpCtx.messageSigningContext.remainingLength = - (workBuffer[0] << 24) | (workBuffer[1] << 16) | - (workBuffer[2] << 8) | (workBuffer[3]); + tmpCtx.messageSigningContext.remainingLength = U4BE(workBuffer, 0); workBuffer += 4; dataLength -= 4; // Initialize message header + length @@ -2016,10 +2087,16 @@ void handleApdu(volatile unsigned int *flags, volatile unsigned int *tx) { switch (G_io_apdu_buffer[OFFSET_INS]) { case INS_GET_PUBLIC_KEY: + currentTokenSet = false; handleGetPublicKey(G_io_apdu_buffer[OFFSET_P1], G_io_apdu_buffer[OFFSET_P2], G_io_apdu_buffer + OFFSET_CDATA, G_io_apdu_buffer[OFFSET_LC], flags, tx); break; - case INS_SIGN: + case INS_PROVIDE_ERC20_TOKEN_INFORMATION: + currentTokenSet = false; + handleProvideErc20TokenInformation(G_io_apdu_buffer[OFFSET_P1], G_io_apdu_buffer[OFFSET_P2], G_io_apdu_buffer + OFFSET_CDATA, G_io_apdu_buffer[OFFSET_LC], flags, tx); + break; + + case INS_SIGN: handleSign(G_io_apdu_buffer[OFFSET_P1], G_io_apdu_buffer[OFFSET_P2], G_io_apdu_buffer + OFFSET_CDATA, G_io_apdu_buffer[OFFSET_LC], flags, tx); break; @@ -2028,6 +2105,7 @@ void handleApdu(volatile unsigned int *flags, volatile unsigned int *tx) { break; case INS_SIGN_PERSONAL_MESSAGE: + currentTokenSet = false; handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1], G_io_apdu_buffer[OFFSET_P2], G_io_apdu_buffer + OFFSET_CDATA, G_io_apdu_buffer[OFFSET_LC], flags, tx); break; diff --git a/src/tokens.c b/src/tokens.c index 48c592c..8578c9f 100644 --- a/src/tokens.c +++ b/src/tokens.c @@ -15,6 +15,8 @@ * limitations under the License. ********************************************************************************/ +#ifdef HAVE_TOKENS_LIST + #include "tokens.h" const tokenDefinition_t const TOKENS_AKROMA[NUM_TOKENS_AKROMA] = {}; @@ -1177,3 +1179,6 @@ const tokenDefinition_t const TOKENS_REOSC[NUM_TOKENS_REOSC] = {}; const tokenDefinition_t const TOKENS_HPB[NUM_TOKENS_HPB] = {}; const tokenDefinition_t const TOKENS_TOMOCHAIN[NUM_TOKENS_TOMOCHAIN] = {}; + +#endif + diff --git a/src/tokens.h b/src/tokens.h index cf3bb5f..bd83fbf 100644 --- a/src/tokens.h +++ b/src/tokens.h @@ -26,6 +26,8 @@ typedef struct tokenDefinition_t { uint8_t decimals; } tokenDefinition_t; +#ifdef HAVE_TOKENS_LIST + #define NUM_TOKENS_AKROMA 0 #define NUM_TOKENS_ELLAISM 1 #define NUM_TOKENS_ETHEREUM 1102 @@ -72,4 +74,6 @@ extern tokenDefinition_t const TOKENS_REOSC[NUM_TOKENS_REOSC]; extern tokenDefinition_t const TOKENS_HPB[NUM_TOKENS_HPB]; extern tokenDefinition_t const TOKENS_TOMOCHAIN[NUM_TOKENS_TOMOCHAIN]; +#endif + #endif /* _TOKENS_H_ */