Merge pull request #62 from LedgerHQ/externalize-erc20

Externalize erc20
This commit is contained in:
Nicolas Bacca
2019-03-17 10:52:36 +01:00
committed by GitHub
4 changed files with 151 additions and 18 deletions

View File

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

View File

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

View File

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

View File

@@ -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_ */