/******************************************************************************* * Ledger Ethereum App * (c) 2016-2019 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ /** * @brief Utilities for an Ethereum Hardware Wallet logic * @file ethUtils.h * @author Ledger Firmware Team * @version 1.0 * @date 8th of March 2016 */ #include #include #include #include "shared_context.h" #include "utils.h" #include "ethUtils.h" #include "chainConfig.h" #include "ethUstream.h" #include "network.h" bool rlpCanDecode(uint8_t *buffer, uint32_t bufferLength, bool *valid) { if (*buffer <= 0x7f) { } else if (*buffer <= 0xb7) { } else if (*buffer <= 0xbf) { if (bufferLength < (1 + (*buffer - 0xb7))) { return false; } if (*buffer > 0xbb) { *valid = false; // arbitrary 32 bits length limitation return true; } } else if (*buffer <= 0xf7) { } else { if (bufferLength < (1 + (*buffer - 0xf7))) { return false; } if (*buffer > 0xfb) { *valid = false; // arbitrary 32 bits length limitation return true; } } *valid = true; return true; } bool rlpDecodeLength(uint8_t *buffer, uint32_t *fieldLength, uint32_t *offset, bool *list) { if (*buffer <= 0x7f) { *offset = 0; *fieldLength = 1; *list = false; } else if (*buffer <= 0xb7) { *offset = 1; *fieldLength = *buffer - 0x80; *list = false; } else if (*buffer <= 0xbf) { *offset = 1 + (*buffer - 0xb7); *list = false; switch (*buffer) { case 0xb8: *fieldLength = *(buffer + 1); break; case 0xb9: *fieldLength = (*(buffer + 1) << 8) + *(buffer + 2); break; case 0xba: *fieldLength = (*(buffer + 1) << 16) + (*(buffer + 2) << 8) + *(buffer + 3); break; case 0xbb: *fieldLength = (*(buffer + 1) << 24) + (*(buffer + 2) << 16) + (*(buffer + 3) << 8) + *(buffer + 4); break; default: return false; // arbitrary 32 bits length limitation } } else if (*buffer <= 0xf7) { *offset = 1; *fieldLength = *buffer - 0xc0; *list = true; } else { *offset = 1 + (*buffer - 0xf7); *list = true; switch (*buffer) { case 0xf8: *fieldLength = *(buffer + 1); break; case 0xf9: *fieldLength = (*(buffer + 1) << 8) + *(buffer + 2); break; case 0xfa: *fieldLength = (*(buffer + 1) << 16) + (*(buffer + 2) << 8) + *(buffer + 3); break; case 0xfb: *fieldLength = (*(buffer + 1) << 24) + (*(buffer + 2) << 16) + (*(buffer + 3) << 8) + *(buffer + 4); break; default: return false; // arbitrary 32 bits length limitation } } return true; } bool getEthAddressFromKey(cx_ecfp_public_key_t *publicKey, uint8_t *out, cx_sha3_t *sha3Context) { uint8_t hashAddress[INT256_LENGTH]; if (cx_keccak_init_no_throw(sha3Context, 256) != CX_OK) { return false; } if (cx_hash_no_throw((cx_hash_t *) sha3Context, CX_LAST, publicKey->W + 1, 64, hashAddress, 32) != CX_OK) { return false; } memmove(out, hashAddress + 12, 20); return true; } bool getEthAddressStringFromKey(cx_ecfp_public_key_t *publicKey, char *out, cx_sha3_t *sha3Context, uint64_t chainId) { uint8_t hashAddress[INT256_LENGTH]; if (cx_keccak_init_no_throw(sha3Context, 256) != CX_OK) { return false; } if (cx_hash_no_throw((cx_hash_t *) sha3Context, CX_LAST, publicKey->W + 1, 64, hashAddress, 32) != CX_OK) { return false; } if (!getEthAddressStringFromBinary(hashAddress + 12, out, sha3Context, chainId)) { return false; } return true; } bool u64_to_string(uint64_t src, char *dst, uint8_t dst_size) { // Copy the numbers in ASCII format. uint8_t i = 0; do { // Checking `i + 1` to make sure we have enough space for '\0'. if (i + 1 >= dst_size) { return false; } dst[i] = src % 10 + '0'; src /= 10; i++; } while (src); // Null terminate string dst[i] = '\0'; // Revert the string i--; uint8_t j = 0; while (j < i) { char tmp = dst[i]; dst[i] = dst[j]; dst[j] = tmp; i--; j++; } return true; } bool getEthAddressStringFromBinary(uint8_t *address, char *out, cx_sha3_t *sha3Context, uint64_t chainId) { // save some precious stack space union locals_union { uint8_t hashChecksum[INT256_LENGTH]; uint8_t tmp[51]; } locals_union; uint8_t i; bool eip1191 = false; uint32_t offset = 0; switch (chainId) { case 30: case 31: eip1191 = true; break; } if (eip1191) { if (!u64_to_string(chainId, (char *) locals_union.tmp, sizeof(locals_union.tmp))) { return false; } offset = strnlen((char *) locals_union.tmp, sizeof(locals_union.tmp)); strlcat((char *) locals_union.tmp + offset, "0x", sizeof(locals_union.tmp) - offset); offset = strnlen((char *) locals_union.tmp, sizeof(locals_union.tmp)); } for (i = 0; i < 20; i++) { uint8_t digit = address[i]; locals_union.tmp[offset + 2 * i] = HEXDIGITS[(digit >> 4) & 0x0f]; locals_union.tmp[offset + 2 * i + 1] = HEXDIGITS[digit & 0x0f]; } if (cx_keccak_init_no_throw(sha3Context, 256) != CX_OK) { return false; } if (cx_hash_no_throw((cx_hash_t *) sha3Context, CX_LAST, locals_union.tmp, offset + 40, locals_union.hashChecksum, 32) != CX_OK) { return false; } for (i = 0; i < 40; i++) { uint8_t digit = address[i / 2]; if ((i % 2) == 0) { digit = (digit >> 4) & 0x0f; } else { digit = digit & 0x0f; } if (digit < 10) { out[i] = HEXDIGITS[digit]; } else { int v = (locals_union.hashChecksum[i / 2] >> (4 * (1 - i % 2))) & 0x0f; if (v >= 8) { out[i] = HEXDIGITS[digit] - 'a' + 'A'; } else { out[i] = HEXDIGITS[digit]; } } } out[40] = '\0'; return true; } /* Fills the `out` buffer with the lowercase string representation of the pubkey passed in as binary format by `in`. (eg: uint8_t*:0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB -> char*:"0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB\0" ) `sha3` context doesn't have have to be initialized prior to call.*/ bool getEthDisplayableAddress(uint8_t *in, char *out, size_t out_len, cx_sha3_t *sha3, uint64_t chainId) { if (out_len < 43) { strlcpy(out, "ERROR", out_len); return false; } out[0] = '0'; out[1] = 'x'; if (!getEthAddressStringFromBinary(in, out + 2, sha3, chainId)) { strlcpy(out, "ERROR", out_len); return false; } return true; } bool adjustDecimals(const char *src, size_t srcLength, char *target, size_t targetLength, uint8_t decimals) { uint32_t startOffset; uint32_t lastZeroOffset = 0; uint32_t offset = 0; if ((srcLength == 1) && (*src == '0')) { if (targetLength < 2) { return false; } target[0] = '0'; target[1] = '\0'; return true; } if (srcLength <= decimals) { uint32_t delta = decimals - srcLength; if (targetLength < srcLength + 1 + 2 + delta) { return false; } target[offset++] = '0'; target[offset++] = '.'; for (uint32_t i = 0; i < delta; i++) { target[offset++] = '0'; } startOffset = offset; for (uint32_t i = 0; i < srcLength; i++) { target[offset++] = src[i]; } target[offset] = '\0'; } else { uint32_t sourceOffset = 0; uint32_t delta = srcLength - decimals; if (targetLength < srcLength + 1 + 1) { return false; } while (offset < delta) { target[offset++] = src[sourceOffset++]; } if (decimals != 0) { target[offset++] = '.'; } startOffset = offset; while (sourceOffset < srcLength) { target[offset++] = src[sourceOffset++]; } target[offset] = '\0'; } for (uint32_t i = startOffset; i < offset; i++) { if (target[i] == '0') { if (lastZeroOffset == 0) { lastZeroOffset = i; } } else { lastZeroOffset = 0; } } if (lastZeroOffset != 0) { target[lastZeroOffset] = '\0'; if (target[lastZeroOffset - 1] == '.') { target[lastZeroOffset - 1] = '\0'; } } return true; } // Returns the chain ID. Defaults to 0 if txType was not found (For TX). uint64_t get_tx_chain_id(void) { uint64_t chain_id = 0; switch (txContext.txType) { case LEGACY: chain_id = u64_from_BE(txContext.content->v, txContext.content->vLength); break; case EIP2930: case EIP1559: chain_id = u64_from_BE(tmpContent.txContent.chainID.value, tmpContent.txContent.chainID.length); break; default: PRINTF("Txtype `%d` not supported while generating chainID\n", txContext.txType); break; } return chain_id; } const char *get_displayable_ticker(const uint64_t *chain_id) { const char *ticker = get_network_ticker_from_chain_id(chain_id); if (ticker == NULL) { ticker = chainConfig->coinName; } return ticker; }