/******************************************************************************* * 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. ********************************************************************************/ #include #include #include "asset_info.h" #include "common_utils.h" #include "lcx_ecfp.h" #include "lcx_sha3.h" void array_hexstr(char *strbuf, const void *bin, unsigned int len) { while (len--) { *strbuf++ = HEXDIGITS[((*((char *) bin)) >> 4) & 0xF]; *strbuf++ = HEXDIGITS[(*((char *) bin)) & 0xF]; bin = (const void *) ((unsigned int) bin + 1); } *strbuf = 0; // EOS } uint64_t u64_from_BE(const uint8_t *in, uint8_t size) { uint8_t i = 0; uint64_t res = 0; while (i < size && i < sizeof(res)) { res <<= 8; res |= in[i]; i++; } return res; } 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 uint256_to_decimal(const uint8_t *value, size_t value_len, char *out, size_t out_len) { if (value_len > INT256_LENGTH) { // value len is bigger than INT256_LENGTH ?! return false; } uint16_t n[16] = {0}; // Copy and right-align the number memcpy((uint8_t *) n + INT256_LENGTH - value_len, value, value_len); // Special case when value is 0 if (allzeroes(n, INT256_LENGTH)) { if (out_len < 2) { // Not enough space to hold "0" and \0. return false; } strlcpy(out, "0", out_len); return true; } uint16_t *p = n; for (int i = 0; i < 16; i++) { n[i] = __builtin_bswap16(*p++); } int pos = out_len; while (!allzeroes(n, sizeof(n))) { if (pos == 0) { return false; } pos -= 1; unsigned int carry = 0; for (int i = 0; i < 16; i++) { int rem = ((carry << 16) | n[i]) % 10; n[i] = ((carry << 16) | n[i]) / 10; carry = rem; } out[pos] = '0' + carry; } memmove(out, out + pos, out_len - pos); out[out_len - pos] = 0; 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; } bool amountToString(const uint8_t *amount, uint8_t amount_size, uint8_t decimals, const char *ticker, char *out_buffer, size_t out_buffer_size) { char tmp_buffer[100] = {0}; if (uint256_to_decimal(amount, amount_size, tmp_buffer, sizeof(tmp_buffer)) == false) { return false; } uint8_t amount_len = strnlen(tmp_buffer, sizeof(tmp_buffer)); uint8_t ticker_len = strnlen(ticker, MAX_TICKER_LEN); memcpy(out_buffer, ticker, MIN(out_buffer_size, ticker_len)); if (ticker_len > 0) { out_buffer[ticker_len++] = ' '; } if (adjustDecimals(tmp_buffer, amount_len, out_buffer + ticker_len, out_buffer_size - ticker_len - 1, decimals) == false) { return false; } out_buffer[out_buffer_size - 1] = '\0'; return true; } void getEthAddressFromRawKey(const uint8_t raw_pubkey[static 65], uint8_t out[static ADDRESS_LENGTH]) { uint8_t hashAddress[CX_KECCAK_256_SIZE]; CX_ASSERT(cx_keccak_256_hash(raw_pubkey + 1, 64, hashAddress)); memmove(out, hashAddress + 12, ADDRESS_LENGTH); } void getEthAddressStringFromRawKey(const uint8_t raw_pubkey[static 65], char out[static ADDRESS_LENGTH * 2], uint64_t chainId) { uint8_t hashAddress[CX_KECCAK_256_SIZE]; CX_ASSERT(cx_keccak_256_hash(raw_pubkey + 1, 64, hashAddress)); getEthAddressStringFromBinary(hashAddress + 12, out, chainId); } bool getEthAddressStringFromBinary(uint8_t *address, char out[static ADDRESS_LENGTH * 2], 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_256_hash(locals_union.tmp, offset + 40, locals_union.hashChecksum) != 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" ).*/ bool getEthDisplayableAddress(uint8_t *in, char *out, size_t out_len, 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, chainId)) { strlcpy(out, "ERROR", out_len); return false; } return true; }