diff --git a/Makefile b/Makefile index 687867d..8ba8a4a 100755 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ APP_LOAD_PARAMS= --curve secp256k1 $(COMMON_LOAD_PARAMS) APPVERSION_M=1 APPVERSION_N=2 -APPVERSION_P=4 +APPVERSION_P=5 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) # simplify for tests @@ -80,6 +80,11 @@ else ifeq ($(COIN),zcash) DEFINES += COIN_P2PKH_VERSION=7352 COIN_P2SH_VERSION=7357 COIN_FAMILY=1 COIN_COINID=\"Zcash\" COIN_COINID_HEADER=\"ZCASH\" COIN_COLOR_HDR=0x3790CA COIN_COLOR_DB=0x9BC8E5 COIN_COINID_NAME=\"Zcash\" COIN_COINID_SHORT=\"ZEC\" COIN_KIND=COIN_KIND_ZCASH APPNAME ="Zcash" APP_LOAD_PARAMS += --path $(APP_PATH) +else ifeq ($(COIN),zencash) +# ZenCash +DEFINES += COIN_P2PKH_VERSION=8329 COIN_P2SH_VERSION=8342 COIN_FAMILY=4 COIN_COINID=\"Zencash\" COINID_UPCASE=\"ZENCASH\" COLOR_HDR=0xFF4300 COLOR_DB=0xFF8356 COIN_COINID_NAME=\"Zencash\" COINID=$(COIN) COIN_COINID_SHORT=\"ZEN\" COIN_KIND=COIN_KIND_ZENCASH +APPNAME ="ZenCash" +APP_LOAD_PARAMS += --path $(APP_PATH) else ifeq ($(COIN),komodo) # Komodo DEFINES += COIN_P2PKH_VERSION=60 COIN_P2SH_VERSION=85 COIN_FAMILY=1 COIN_COINID=\"Komodo\" COIN_COINID_HEADER=\"KMD\" COIN_COLOR_HDR=0x326464 COIN_COLOR_DB=0x99b2b2 COIN_COINID_NAME=\"Komodo\" COIN_COINID_SHORT=\"KMD\" COIN_KIND=COIN_KIND_KOMODO @@ -125,16 +130,22 @@ APPNAME ="Digibyte" APP_LOAD_PARAMS += --path $(APP_PATH) else ifeq ($(COIN),qtum) # Qtum -DEFINES += COIN_P2PKH_VERSION=58 COIN_P2SH_VERSION=50 COIN_FAMILY=3 COIN_COINID=\"Qtum\" COIN_COINID_HEADER=\"QTUM\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"QTUM\" COIN_COINID_SHORT=\"QTUM\" COIN_KIND=COIN_KIND_QTUM COIN_FLAGS=FLAG_QTUM_SUPPORT +DEFINES += COIN_P2PKH_VERSION=58 COIN_P2SH_VERSION=50 COIN_FAMILY=3 COIN_COINID=\"Qtum\" COIN_COINID_HEADER=\"QTUM\" COIN_COLOR_HDR=0x2E9AD0 COIN_COLOR_DB=0x97CDE8 COIN_COINID_NAME=\"QTUM\" COIN_COINID_SHORT=\"QTUM\" COIN_KIND=COIN_KIND_QTUM APPNAME ="Qtum" APP_LOAD_PARAMS += --path "44'/88'" --path "0'/45342'" --path "20698'/3053'/12648430'" else ifeq ($(COIN),hcash) +# HCash DEFINES += COIN_P2PKH_VERSION=40 COIN_P2SH_VERSION=100 COIN_FAMILY=2 COIN_COINID=\"HShare\" COIN_COINID_HEADER=\"HCASH\" COIN_COLOR_HDR=0x57448D COIN_COLOR_DB=0xABA2C6 COIN_COINID_NAME=\"HCash\" COIN_COINID_SHORT=\"HCASH\" COIN_KIND=COIN_KIND_HCASH COIN_FLAGS=FLAG_PEERCOIN_SUPPORT APPNAME ="HCash" APP_LOAD_PARAMS += --path $(APP_PATH) +else ifeq ($(COIN),bitcoin_private) +# Bitcoin Private +DEFINES += COIN_P2PKH_VERSION=4901 COIN_P2SH_VERSION=5039 COIN_FAMILY=1 COIN_COINID=\"BPrivate\" COIN_COINID_HEADER=\"BPRIVATE\" COIN_COLOR_HDR=0x85bb65 COIN_COLOR_DB=0xc2ddb2 COIN_COINID_NAME=\"BPrivate\" COIN_COINID_SHORT=\"BTCP\" COIN_KIND=COIN_KIND_BITCOIN_PRIVATE COIN_FORKID=42 +APPNAME ="Bitcoin Private" +APP_LOAD_PARAMS += --path $(APP_PATH) else ifeq ($(filter clean,$(MAKECMDGOALS)),) -$(error Unsupported COIN - use bitcoin_testnet, bitcoin, bitcoin_cash, bitcoin_gold, litecoin, dogecoin, dash, zcash, komodo, stratis, peercoin, posw, pivx, viacoin, vertcoin, stealthcoin, digibyte, qtum, hcash) +$(error Unsupported COIN - use bitcoin_testnet, bitcoin, bitcoin_cash, bitcoin_gold, litecoin, dogecoin, dash, zcash, zencash, komodo, stratis, peercoin, posw, pivx, viacoin, vertcoin, stealthcoin, digibyte, qtum, hcash, bitcoin_private) endif endif @@ -159,14 +170,16 @@ all: default DEFINES += OS_IO_SEPROXYHAL IO_SEPROXYHAL_BUFFER_SIZE_B=300 DEFINES += HAVE_BAGL HAVE_SPRINTF -#DEFINES += HAVE_PRINTF PRINTF=screen_printf -DEFINES += PRINTF\(...\)= +DEFINES += HAVE_PRINTF PRINTF=screen_printf +#DEFINES += PRINTF\(...\)= DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=6 IO_HID_EP_LENGTH=64 HAVE_USB_APDU DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) TCS_LOADER_PATCH_VERSION=0 DEFINES += UNUSED\(x\)=\(void\)x DEFINES += APPVERSION=\"$(APPVERSION)\" +DEFINES += CX_COMPLIANCE_141 + ############## # Compiler # ############## diff --git a/glyphs/nanos_badge_bitcoin_private.gif b/glyphs/nanos_badge_bitcoin_private.gif new file mode 100644 index 0000000..51d4575 Binary files /dev/null and b/glyphs/nanos_badge_bitcoin_private.gif differ diff --git a/glyphs/nanos_badge_zencash.gif b/glyphs/nanos_badge_zencash.gif new file mode 100644 index 0000000..d90503d Binary files /dev/null and b/glyphs/nanos_badge_zencash.gif differ diff --git a/include/btchip_context.h b/include/btchip_context.h index c6ab1ac..272bc85 100644 --- a/include/btchip_context.h +++ b/include/btchip_context.h @@ -183,6 +183,7 @@ struct btchip_context_s { unsigned char transactionVersion[4]; unsigned char inputValue[8]; unsigned char usingSegwit; + unsigned char usingCashAddr; unsigned char segwitParsedOnce; /* /Segregated Witness changes */ @@ -239,8 +240,7 @@ typedef struct btchip_context_s btchip_context_t; typedef enum btchip_coin_flags_e { FLAG_PEERCOIN_UNITS=1, FLAG_PEERCOIN_SUPPORT=2, - FLAG_SEGWIT_CHANGE_SUPPORT=4, - FLAG_QTUM_SUPPORT=8, + FLAG_SEGWIT_CHANGE_SUPPORT=4 } btchip_coin_flags_t; @@ -265,6 +265,8 @@ typedef enum btchip_coin_kind_e { COIN_KIND_DIGIBYTE, COIN_KIND_QTUM, COIN_KIND_HCASH, + COIN_KIND_BITCOIN_PRIVATE, + COIN_KIND_ZENCASH } btchip_coin_kind_t; typedef struct btchip_altcoin_config_s { diff --git a/nanos_app_bitcoin_private.gif b/nanos_app_bitcoin_private.gif new file mode 100644 index 0000000..8eae073 Binary files /dev/null and b/nanos_app_bitcoin_private.gif differ diff --git a/nanos_app_zencash.gif b/nanos_app_zencash.gif new file mode 100644 index 0000000..aecd4e0 Binary files /dev/null and b/nanos_app_zencash.gif differ diff --git a/src/btchip_apdu_get_wallet_public_key.c b/src/btchip_apdu_get_wallet_public_key.c index 82c8803..f62e6ff 100644 --- a/src/btchip_apdu_get_wallet_public_key.c +++ b/src/btchip_apdu_get_wallet_public_key.c @@ -21,12 +21,15 @@ #include "btchip_bagl_extensions.h" #include "segwit_addr.h" +#include "cashaddr.h" + #define P1_NO_DISPLAY 0x00 #define P1_DISPLAY 0x01 #define P2_LEGACY 0x00 #define P2_SEGWIT 0x01 #define P2_NATIVE_SEGWIT 0x02 +#define P2_CASHADDR 0x03 unsigned short btchip_apdu_get_wallet_public_key() { unsigned char keyLength; @@ -37,6 +40,7 @@ unsigned short btchip_apdu_get_wallet_public_key() { bool display = (G_io_apdu_buffer[ISO_OFFSET_P1] == P1_DISPLAY); bool segwit = (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_SEGWIT); bool nativeSegwit = (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NATIVE_SEGWIT); + bool cashAddr = (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_CASHADDR); switch (G_io_apdu_buffer[ISO_OFFSET_P1]) { case P1_NO_DISPLAY: @@ -54,6 +58,11 @@ unsigned short btchip_apdu_get_wallet_public_key() { case P2_LEGACY: case P2_SEGWIT: break; + case P2_CASHADDR: + if (G_coin_config->kind != COIN_KIND_BITCOIN_CASH) { + return BTCHIP_SW_INCORRECT_P1_P2; + } + break; default: return BTCHIP_SW_INCORRECT_P1_P2; } @@ -78,7 +87,10 @@ unsigned short btchip_apdu_get_wallet_public_key() { return BTCHIP_SW_SECURITY_STATUS_NOT_SATISFIED; } + PRINTF("pin ok\n"); + btchip_private_derive_keypair(keyPath, 1, chainCode); + G_io_apdu_buffer[0] = 65; // Then encode it @@ -91,7 +103,14 @@ unsigned short btchip_apdu_get_wallet_public_key() { os_memmove(G_io_apdu_buffer + 1, btchip_public_key_D.W, sizeof(btchip_public_key_D.W)); - if (!(segwit || nativeSegwit)) { + if (cashAddr) { + uint8_t tmp[20]; + btchip_public_key_hash160(G_io_apdu_buffer + 1, // IN + keyLength, // INLEN + tmp); + keyLength = + cashaddr_encode(tmp, 20, G_io_apdu_buffer + 67, 50, CASHADDR_P2PKH); + } else if (!(segwit || nativeSegwit)) { keyLength = btchip_public_key_to_encoded_base58( G_io_apdu_buffer + 1, // IN keyLength, // INLEN diff --git a/src/btchip_apdu_hash_input_finalize_full.c b/src/btchip_apdu_hash_input_finalize_full.c index 91dc304..76fe2a9 100644 --- a/src/btchip_apdu_hash_input_finalize_full.c +++ b/src/btchip_apdu_hash_input_finalize_full.c @@ -26,6 +26,8 @@ #define FINALIZE_P1_LAST 0x80 #define FINALIZE_P1_CHANGEINFO 0xFF +#define FINALIZE_P2_DEFAULT 0x00 + #define FLAG_SIGNATURE 0x01 #define FLAG_CHANGE_VALIDATED 0x80 @@ -66,10 +68,10 @@ static bool check_output_displayable() { btchip_output_script_is_op_create(btchip_context_D.currentOutput + 8); isOpCall = btchip_output_script_is_op_call(btchip_context_D.currentOutput + 8); - if (((G_coin_config->flags & FLAG_QTUM_SUPPORT) && + if (((G_coin_config->kind == COIN_KIND_QTUM) && !btchip_output_script_is_regular(btchip_context_D.currentOutput + 8) && !isP2sh && !(nullAmount && isOpReturn) && !isOpCreate && !isOpCall) || - (!(G_coin_config->flags & FLAG_QTUM_SUPPORT) && + (!(G_coin_config->kind == COIN_KIND_QTUM) && !btchip_output_script_is_regular(btchip_context_D.currentOutput + 8) && !isP2sh && !(nullAmount && isOpReturn))) { PRINTF("Error : Unrecognized input script"); @@ -326,7 +328,7 @@ unsigned short btchip_apdu_hash_input_finalize_full_internal( btchip_context_D.currentOutputOffset += apduLength; // Check if the legacy UI can be applied - if (!(G_coin_config->flags & FLAG_QTUM_SUPPORT) && + if (!(G_coin_config->kind == COIN_KIND_QTUM) && (G_io_apdu_buffer[ISO_OFFSET_P1] == FINALIZE_P1_LAST) && !btchip_context_D.tmpCtx.output.multipleOutput && prepare_full_output(1)) { diff --git a/src/btchip_apdu_hash_input_start.c b/src/btchip_apdu_hash_input_start.c index a2e85ea..ebbda86 100644 --- a/src/btchip_apdu_hash_input_start.c +++ b/src/btchip_apdu_hash_input_start.c @@ -22,6 +22,7 @@ #define P1_NEXT 0x80 #define P2_NEW 0x00 #define P2_NEW_SEGWIT 0x02 +#define P2_NEW_SEGWIT_CASHADDR 0x03 #define P2_CONTINUE 0x80 unsigned short btchip_apdu_hash_input_start() { @@ -49,12 +50,16 @@ unsigned short btchip_apdu_hash_input_start() { } if ((G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW) || - (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT)) { + (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT) || + (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT_CASHADDR)) { // btchip_context_D.transactionContext.consumeP2SH = // ((N_btchip.bkp.config.options & BTCHIP_OPTION_SKIP_2FA_P2SH) != 0); if (G_io_apdu_buffer[ISO_OFFSET_P1] == P1_FIRST) { unsigned char usingSegwit = - (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT); + (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT) || + ((G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT_CASHADDR)); + unsigned char usingCashAddr = + (G_io_apdu_buffer[ISO_OFFSET_P2] == P2_NEW_SEGWIT_CASHADDR); // Request PIN validation // Only request PIN validation (user presence) to start a new // transaction signing flow. @@ -70,6 +75,9 @@ unsigned short btchip_apdu_hash_input_start() { btchip_context_D.transactionContext.consumeP2SH = 0; btchip_context_D.transactionContext.relaxed = 0; btchip_context_D.usingSegwit = usingSegwit; + btchip_context_D.usingCashAddr = + (G_coin_config->kind == COIN_KIND_BITCOIN_CASH ? usingCashAddr + : 0); btchip_context_D.segwitParsedOnce = 0; btchip_set_check_internal_structure_integrity(1); // Initialize for screen pairing diff --git a/src/btchip_apdu_set_alternate_coin_version.c b/src/btchip_apdu_set_alternate_coin_version.c index 42ca463..3ff942b 100644 --- a/src/btchip_apdu_set_alternate_coin_version.c +++ b/src/btchip_apdu_set_alternate_coin_version.c @@ -59,7 +59,7 @@ unsigned short btchip_apdu_set_alternate_coin_version() { } break; case BTCHIP_FAMILY_QTUM: - if (!(G_coin_config->flags & FLAG_QTUM_SUPPORT)) { + if (!(G_coin_config->kind == COIN_KIND_QTUM)) { goto incorrect_family; } break; diff --git a/src/btchip_helpers.c b/src/btchip_helpers.c index 16311b0..65d419d 100644 --- a/src/btchip_helpers.c +++ b/src/btchip_helpers.c @@ -31,6 +31,19 @@ const unsigned char TRANSACTION_OUTPUT_SCRIPT_P2SH_POST[] = {0x87}; // OP_EQUAL const unsigned char TRANSACTION_OUTPUT_SCRIPT_P2WPKH_PRE[] = {0x16, 0x00, 0x14}; const unsigned char TRANSACTION_OUTPUT_SCRIPT_P2WSH_PRE[] = {0x22, 0x00, 0x20}; +const unsigned char ZEN_OUTPUT_SCRIPT_PRE[] = { + 0x3F, 0x76, 0xA9, + 0x14}; // script length, OP_DUP, OP_HASH160, address length +const unsigned char ZEN_OUTPUT_SCRIPT_POST[] = { + 0x88, 0xAC, // OP_EQUALVERIFY, OP_CHECKSIG + 0x20, 0x9e, 0xc9, 0x84, 0x5a, 0xcb, 0x02, 0xfa, 0xb2, 0x4e, 0x1c, 0x03, + 0x68, 0xb3, 0xb5, 0x17, 0xc1, 0xa4, 0x48, 0x8f, 0xba, 0x97, 0xf0, 0xe3, + 0x45, 0x9a, 0xc0, 0x53, 0xea, 0x01, 0x00, 0x00, 0x00, // ParamHash + 0x03, // Push 3 bytes to stack to make ParamHeight line up properly + 0xc0, 0x1f, 0x02, // ParamHeight (139200) -> hex -> endianness swapped + 0xb4 // OP_CHECKBLOCKATHEIGHT +}; // BIP0115 Replay Protection + unsigned char btchip_output_script_is_regular(unsigned char *buffer) { if (G_coin_config->native_segwit_prefix) { if ((os_memcmp(buffer, TRANSACTION_OUTPUT_SCRIPT_P2WPKH_PRE, @@ -40,12 +53,22 @@ unsigned char btchip_output_script_is_regular(unsigned char *buffer) { return 1; } } - if ((os_memcmp(buffer, TRANSACTION_OUTPUT_SCRIPT_PRE, - sizeof(TRANSACTION_OUTPUT_SCRIPT_PRE)) == 0) && - (os_memcmp(buffer + sizeof(TRANSACTION_OUTPUT_SCRIPT_PRE) + 20, - TRANSACTION_OUTPUT_SCRIPT_POST, - sizeof(TRANSACTION_OUTPUT_SCRIPT_POST)) == 0)) { - return 1; + if (G_coin_config->kind == COIN_KIND_ZENCASH) { + if ((os_memcmp(buffer, ZEN_OUTPUT_SCRIPT_PRE, + sizeof(ZEN_OUTPUT_SCRIPT_PRE)) == 0) && + (os_memcmp(buffer + sizeof(ZEN_OUTPUT_SCRIPT_PRE) + 20, + ZEN_OUTPUT_SCRIPT_POST, + sizeof(ZEN_OUTPUT_SCRIPT_POST)) == 0)) { + return 1; + } + } else { + if ((os_memcmp(buffer, TRANSACTION_OUTPUT_SCRIPT_PRE, + sizeof(TRANSACTION_OUTPUT_SCRIPT_PRE)) == 0) && + (os_memcmp(buffer + sizeof(TRANSACTION_OUTPUT_SCRIPT_PRE) + 20, + TRANSACTION_OUTPUT_SCRIPT_POST, + sizeof(TRANSACTION_OUTPUT_SCRIPT_POST)) == 0)) { + return 1; + } } return 0; } diff --git a/src/cashaddr.c b/src/cashaddr.c new file mode 100644 index 0000000..45a9570 --- /dev/null +++ b/src/cashaddr.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2017 Pieter Wuille + * Modified work Copyright (c) 2018 Jonas Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "os.h" +#include "cx.h" +#include +#include +#include +#include + +#include "cashaddr.h" + +static const char *charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +uint64_t cashaddr_polymod_step(uint64_t pre) { + uint8_t b = pre >> 35; + return ((pre & 0x07ffffffff) << 5) ^ (-((b >> 0) & 1) & 0x98f2bc8e61UL) ^ + (-((b >> 1) & 1) & 0x79b76d99e2UL) ^ + (-((b >> 2) & 1) & 0xf33e5fb3c4UL) ^ + (-((b >> 3) & 1) & 0xae2eabe2a8UL) ^ + (-((b >> 4) & 1) & 0x1e4f43e470UL); +} + +uint64_t PolyMod(uint8_t *prefix, uint8_t *payload, size_t payload_length) { + size_t i; + uint64_t c = 1; + while (*prefix != 0) { + c = cashaddr_polymod_step(c) ^ (*prefix++ & 0x1f); // Prefix + } + c = cashaddr_polymod_step(c); // The zero valued separator + for (i = 0; i < payload_length; ++i) { + c = cashaddr_polymod_step(c) ^ (*payload++); // Hash + } + for (i = 0; i < 8; ++i) { + c = cashaddr_polymod_step(c); // 8 zeros for empty checksum + } + return c ^ 1; +} + +static int convert_bits(uint8_t *out, size_t *outlen, int outbits, + const uint8_t *in, size_t inlen, int inbits, int pad) { + uint32_t val = 0; + int bits = 0; + uint32_t maxv = (((uint32_t)1) << outbits) - 1; + while (inlen--) { + val = (val << inbits) | *(in++); + bits += inbits; + while (bits >= outbits) { + bits -= outbits; + out[(*outlen)++] = (val >> bits) & maxv; + } + } + if (pad) { + if (bits) { + out[(*outlen)++] = (val << (outbits - bits)) & maxv; + } + } else if (((val << (outbits - bits)) & maxv) || bits >= inbits) { + return 0; + } + return 1; +} + +void create_checksum(uint8_t *payload, size_t payload_length, + uint8_t *checksum) { + uint8_t *prefix = (uint8_t *)"bitcoincash"; + uint64_t mod = PolyMod(prefix, payload, payload_length); + + for (size_t i = 0; i < 8; ++i) { + // Convert the 5-bit groups in mod to checksum values. + *checksum++ = (mod >> (5 * (7 - i))) & 0x1f; + } +} + +int cashaddr_encode(uint8_t *hash, const size_t hash_length, uint8_t *addr, + const size_t max_addr_len, const unsigned short version) { + uint8_t version_byte; + uint8_t checksum[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // 5-bit bytes. + uint8_t + tmp[40]; // 8-bit bytes. Should be enough for 1 version byte + 160 bit + uint8_t payload[40]; // 5-bit bytes. Should be enough for 1 version byte + + // 160 bit hash + uint8_t *addr_start; + size_t payload_length = 0; + size_t addr_length = 0; + size_t i; + + addr_start = addr; + *addr_start = 0; + + if (hash_length != 20) // Only support 160 bit hash + return 0; + if (version == CASHADDR_P2PKH) { // Support P2PKH = 0, P2SH = 1 + version_byte = 0; + } else if (version == CASHADDR_P2SH) { + version_byte = 8; + } else { + return 0; + } + + tmp[0] = version_byte; + os_memmove(tmp + 1, hash, hash_length); + convert_bits(payload, &payload_length, 5, tmp, hash_length + 1, 8, 1); + + create_checksum(payload, payload_length, + checksum); // Assume prefix is 'bitcoincash' + + for (i = 0; i < payload_length; ++i) { + if (*payload >> 5) { + *addr_start = 0; + return 0; + } + addr_length++; + if (max_addr_len < addr_length) { + *addr_start = 0; + return 0; + } + *(addr++) = charset[payload[i]]; + } + for (i = 0; i < 8; ++i) { + if (*checksum >> 5) { + *addr_start = 0; + return 0; + } + addr_length++; + if (max_addr_len < addr_length) { + *addr_start = 0; + return 0; + } + *(addr++) = charset[checksum[i]]; + } + *addr = 0; + + return addr_length; +} diff --git a/src/cashaddr.h b/src/cashaddr.h new file mode 100644 index 0000000..e5fd851 --- /dev/null +++ b/src/cashaddr.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2017 Pieter Wuille + * Modified work Copyright (c) 2018 Jonas Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _CASHADDR_H_ +#define _CASHADDR_H_ 1 + +#include + +#define CASHADDR_P2PKH 0 +#define CASHADDR_P2SH 1 + +/** Encode a Bitcoin Cash address + * + * In: hash: Pointer to the hash + * hash_length: Length of hash (bytes). Only 20 bytes (160 bits) + * supported. + * max_addr_len: Maximum length of encoded address + * version: P2PKH = 0, P2SH = 1 + * + * Out: addr: Pointer to a buffer with encoded address. The encoded + * address end with a '\0'. + * + * Returns the length of the address if successful, 0 if unsuccessful. + */ + +int cashaddr_encode(uint8_t *hash, const size_t hash_length, uint8_t *addr, + const size_t max_addr_len, const unsigned short version); + +#endif diff --git a/src/main.c b/src/main.c index bd2a58b..d94b998 100644 --- a/src/main.c +++ b/src/main.c @@ -26,6 +26,7 @@ #include "btchip_bagl_extensions.h" #include "segwit_addr.h" +#include "cashaddr.h" #include "glyphs.h" @@ -2002,11 +2003,11 @@ uint8_t prepare_single_output() { if (btchip_output_script_is_op_return(btchip_context_D.currentOutput + offset)) { strcpy(vars.tmp.fullAddress, "OP_RETURN"); - } else if ((G_coin_config->flags & FLAG_QTUM_SUPPORT) && + } else if ((G_coin_config->kind == COIN_KIND_QTUM) && btchip_output_script_is_op_create( btchip_context_D.currentOutput + offset)) { strcpy(vars.tmp.fullAddress, "OP_CREATE"); - } else if ((G_coin_config->flags & FLAG_QTUM_SUPPORT) && + } else if ((G_coin_config->kind == COIN_KIND_QTUM) && btchip_output_script_is_op_call(btchip_context_D.currentOutput + offset)) { strcpy(vars.tmp.fullAddress, "OP_CALL"); @@ -2034,10 +2035,18 @@ uint8_t prepare_single_output() { btchip_context_D.currentOutput + addressOffset, 20); // Prepare address - textSize = btchip_public_key_to_encoded_base58( - address, 20 + versionSize, (unsigned char *)tmp, sizeof(tmp), - version, 1); - tmp[textSize] = '\0'; + if (btchip_context_D.usingCashAddr) { + cashaddr_encode( + address + versionSize, 20, tmp, sizeof(tmp), + (version == btchip_context_D.payToScriptHashVersion + ? CASHADDR_P2SH + : CASHADDR_P2PKH)); + } else { + textSize = btchip_public_key_to_encoded_base58( + address, 20 + versionSize, (unsigned char *)tmp, + sizeof(tmp), version, 1); + tmp[textSize] = '\0'; + } } else if (G_coin_config->native_segwit_prefix) { textSize = segwit_addr_encode( tmp, PIC(G_coin_config->native_segwit_prefix), 0, @@ -2124,7 +2133,7 @@ uint8_t prepare_full_output(uint8_t checkOnly) { if (!btchip_output_script_is_regular(btchip_context_D.currentOutput + offset) && !isP2sh && !(nullAmount && isOpReturn) && - (!(G_coin_config->flags & FLAG_QTUM_SUPPORT) || + (!(G_coin_config->kind == COIN_KIND_QTUM) || (!isOpCreate && !isOpCall))) { if (!checkOnly) { PRINTF("Error : Unrecognized input script"); @@ -2138,10 +2147,10 @@ uint8_t prepare_full_output(uint8_t checkOnly) { } goto error; } - if (((G_coin_config->flags & FLAG_QTUM_SUPPORT) && + if (((G_coin_config->kind == COIN_KIND_QTUM) && btchip_context_D.tmpCtx.output.changeInitialized && !isOpReturn && !isOpCreate && !isOpCall) || - (!(G_coin_config->flags & FLAG_QTUM_SUPPORT) && + (!(G_coin_config->kind == COIN_KIND_QTUM) && btchip_context_D.tmpCtx.output.changeInitialized && !isOpReturn)) { unsigned char addressOffset = (isNativeSegwit ? OUTPUT_SCRIPT_NATIVE_WITNESS_PROGRAM_OFFSET @@ -2185,14 +2194,14 @@ uint8_t prepare_full_output(uint8_t checkOnly) { offset = 1; btchip_context_D.tmp = (unsigned char *)tmp; for (i = 0; i < numberOutputs; i++) { - if (((G_coin_config->flags & FLAG_QTUM_SUPPORT) && + if (((G_coin_config->kind == COIN_KIND_QTUM) && !btchip_output_script_is_op_return( btchip_context_D.currentOutput + offset + 8) && !btchip_output_script_is_op_create( btchip_context_D.currentOutput + offset + 8) && !btchip_output_script_is_op_call( btchip_context_D.currentOutput + offset + 8)) || - (!(G_coin_config->flags & FLAG_QTUM_SUPPORT) && + (!(G_coin_config->kind == COIN_KIND_QTUM) && !btchip_output_script_is_op_return( btchip_context_D.currentOutput + offset + 8))) { unsigned char versionSize; @@ -2230,10 +2239,19 @@ uint8_t prepare_full_output(uint8_t checkOnly) { unsigned short textSize = 0; if (!isNativeSegwit) { // Prepare address - textSize = btchip_public_key_to_encoded_base58( - address, 20 + versionSize, (unsigned char *)tmp, - sizeof(tmp), version, 1); - tmp[textSize] = '\0'; + if (btchip_context_D.usingCashAddr) { + cashaddr_encode( + address + versionSize, 20, tmp, sizeof(tmp), + (version == + btchip_context_D.payToScriptHashVersion + ? CASHADDR_P2SH + : CASHADDR_P2PKH)); + } else { + textSize = btchip_public_key_to_encoded_base58( + address, 20 + versionSize, (unsigned char *)tmp, + sizeof(tmp), version, 1); + tmp[textSize] = '\0'; + } } else if (G_coin_config->native_segwit_prefix) { textSize = segwit_addr_encode( tmp, PIC(G_coin_config->native_segwit_prefix), 0,