From ab028965fe97bd623dc2535eff0281dbf44a9ba5 Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Fri, 26 Apr 2019 11:45:52 +0200 Subject: [PATCH 1/5] python: add slip39 option to device recovery --- python/trezorctl | 3 +++ python/trezorlib/device.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/python/trezorctl b/python/trezorctl index 2f77e3c14..9d15f9cee 100755 --- a/python/trezorctl +++ b/python/trezorctl @@ -505,6 +505,7 @@ def recovery_device( @click.option("-u", "--u2f-counter", default=0) @click.option("-s", "--skip-backup", is_flag=True) @click.option("-n", "--no-backup", is_flag=True) +@click.option("-x", "--slip39", is_flag=True) @click.pass_obj def reset_device( connect, @@ -516,6 +517,7 @@ def reset_device( u2f_counter, skip_backup, no_backup, + slip39, ): if strength: strength = int(strength) @@ -530,6 +532,7 @@ def reset_device( u2f_counter=u2f_counter, skip_backup=skip_backup, no_backup=no_backup, + slip39=slip39, ) diff --git a/python/trezorlib/device.py b/python/trezorlib/device.py index 245c468bc..5d2339f22 100644 --- a/python/trezorlib/device.py +++ b/python/trezorlib/device.py @@ -103,7 +103,6 @@ def wipe(client): return ret -@expect(proto.Success, field="message") def recover( client, word_count=24, @@ -168,6 +167,7 @@ def reset( u2f_counter=0, skip_backup=False, no_backup=False, + slip39=False, ): if client.features.initialized: raise RuntimeError( @@ -191,6 +191,7 @@ def reset( u2f_counter=u2f_counter, skip_backup=bool(skip_backup), no_backup=bool(no_backup), + slip39=bool(slip39), ) resp = client.call(msg) From 132519123e6397eae5b411f3782f3f1758de5d4c Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Sun, 23 Jun 2019 12:15:09 +0200 Subject: [PATCH 2/5] common: add slip39 flag to reset_device --- common/protob/messages-management.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/common/protob/messages-management.proto b/common/protob/messages-management.proto index 1e6d6d2ac..0b2ebe894 100644 --- a/common/protob/messages-management.proto +++ b/common/protob/messages-management.proto @@ -191,6 +191,7 @@ message ResetDevice { optional uint32 u2f_counter = 7; // U2F counter optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow optional bool no_backup = 9; // indicate that no backup is going to be made + optional bool slip39 = 10; // indicate that we want SLIP-39 backup not BIP-39 } /** From 49d6a35249b8092c5e61b45c51301e28fb8a67bb Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Sun, 23 Jun 2019 12:18:24 +0200 Subject: [PATCH 3/5] core: add slip39 keyboard --- core/SConscript.firmware | 1 + core/SConscript.unix | 1 + .../modtrezorcrypto/modtrezorcrypto-slip39.h | 122 ++ .../extmod/modtrezorcrypto/modtrezorcrypto.c | 2 + .../ui/{mnemonic.py => mnemonic_bip39.py} | 2 +- core/src/trezor/ui/mnemonic_slip39.py | 205 +++ crypto/Makefile | 1 + crypto/slip39.c | 136 ++ crypto/slip39.h | 36 + crypto/slip39_wordlist.h | 1238 +++++++++++++++++ crypto/tests/test_check.c | 98 ++ 11 files changed, 1841 insertions(+), 1 deletion(-) create mode 100644 core/embed/extmod/modtrezorcrypto/modtrezorcrypto-slip39.h rename core/src/trezor/ui/{mnemonic.py => mnemonic_bip39.py} (99%) create mode 100644 core/src/trezor/ui/mnemonic_slip39.py create mode 100644 crypto/slip39.c create mode 100644 crypto/slip39.h create mode 100644 crypto/slip39_wordlist.h diff --git a/core/SConscript.firmware b/core/SConscript.firmware index d36585a89..1b9fffbe7 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -88,6 +88,7 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/sha2.c', 'vendor/trezor-crypto/sha3.c', 'vendor/trezor-crypto/shamir.c', + 'vendor/trezor-crypto/slip39.c', ] # libsecp256k1-zkp diff --git a/core/SConscript.unix b/core/SConscript.unix index 624682f64..b201997c3 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -86,6 +86,7 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/sha2.c', 'vendor/trezor-crypto/sha3.c', 'vendor/trezor-crypto/shamir.c', + 'vendor/trezor-crypto/slip39.c', ] # libsecp256k1-zkp diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-slip39.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-slip39.h new file mode 100644 index 000000000..43d2e664d --- /dev/null +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-slip39.h @@ -0,0 +1,122 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "py/obj.h" +#include "py/runtime.h" + +#include "slip39.h" + +/// package: trezorcrypto.slip39 + +/// def compute_mask(prefix: int) -> int: +/// """ +/// Calculates which buttons still can be pressed after some already were. +/// Returns a 9-bit bitmask, where each bit specifies which buttons +/// can be further pressed (there are still words in this combination). +/// LSB denotes first button. +/// +/// Example: 110000110 - second, third, eighth and ninth button still can be +/// pressed. +/// """ +STATIC mp_obj_t mod_trezorcrypto_slip39_compute_mask(mp_obj_t _prefix) { + uint16_t prefix = mp_obj_get_int(_prefix); + + if (prefix < 1 || prefix > 9999) { + mp_raise_ValueError( + "Invalid button prefix (range between 1 and 9999 is allowed)"); + } + return mp_obj_new_int_from_uint(compute_mask(prefix)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_slip39_compute_mask_obj, + mod_trezorcrypto_slip39_compute_mask); + +/// def button_sequence_to_word(prefix: int) -> str: +/// """ +/// Finds the first word that fits the given button prefix. +/// """ +STATIC mp_obj_t +mod_trezorcrypto_slip39_button_sequence_to_word(mp_obj_t _prefix) { + uint16_t prefix = mp_obj_get_int(_prefix); + + if (prefix < 1 || prefix > 9999) { + mp_raise_ValueError( + "Invalid button prefix (range between 1 and 9999 is allowed)"); + } + const char *word = button_sequence_to_word(prefix); + return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)word, strlen(word)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_slip39_button_sequence_to_word_obj, + mod_trezorcrypto_slip39_button_sequence_to_word); + +/// def word_index(word: str) -> int: +/// """ +/// Finds index of given word. +/// Raises ValueError if not found. +/// """ +STATIC mp_obj_t mod_trezorcrypto_slip39_word_index(mp_obj_t _word) { + mp_buffer_info_t word; + + mp_get_buffer_raise(_word, &word, MP_BUFFER_READ); + + uint16_t result = 0; + if (word_index(&result, word.buf, word.len) == false) { + mp_raise_ValueError("Invalid mnemonic word"); + } + return mp_obj_new_int_from_uint(result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_slip39_word_index_obj, + mod_trezorcrypto_slip39_word_index); + +/// def get_word(index: int) -> str: +/// """ +/// Returns word on position 'index'. +/// """ +STATIC mp_obj_t mod_trezorcrypto_slip39_get_word(mp_obj_t _index) { + uint16_t index = mp_obj_get_int(_index); + + if (index > 1023) { + mp_raise_ValueError( + "Invalid wordlist index (range between 0 and 1024 is allowed)"); + } + + const char *word = get_word(index); + return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)word, strlen(word)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_slip39_get_word_obj, + mod_trezorcrypto_slip39_get_word); + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_slip39_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_slip39)}, + {MP_ROM_QSTR(MP_QSTR_compute_mask), + MP_ROM_PTR(&mod_trezorcrypto_slip39_compute_mask_obj)}, + {MP_ROM_QSTR(MP_QSTR_button_sequence_to_word), + MP_ROM_PTR(&mod_trezorcrypto_slip39_button_sequence_to_word_obj)}, + {MP_ROM_QSTR(MP_QSTR_word_index), + MP_ROM_PTR(&mod_trezorcrypto_slip39_word_index_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_word), + MP_ROM_PTR(&mod_trezorcrypto_slip39_get_word_obj)}, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_slip39_globals, + mod_trezorcrypto_slip39_globals_table); + +STATIC const mp_obj_module_t mod_trezorcrypto_slip39_module = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&mod_trezorcrypto_slip39_globals, +}; diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index 28aaf4514..265942f15 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -51,6 +51,7 @@ #include "modtrezorcrypto-sha3-512.h" #include "modtrezorcrypto-sha512.h" #include "modtrezorcrypto-shamir.h" +#include "modtrezorcrypto-slip39.h" STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorcrypto)}, @@ -91,6 +92,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_sha3_512), MP_ROM_PTR(&mod_trezorcrypto_Sha3_512_type)}, {MP_ROM_QSTR(MP_QSTR_shamir), MP_ROM_PTR(&mod_trezorcrypto_shamir_module)}, + {MP_ROM_QSTR(MP_QSTR_slip39), MP_ROM_PTR(&mod_trezorcrypto_slip39_module)}, }; STATIC MP_DEFINE_CONST_DICT(mp_module_trezorcrypto_globals, mp_module_trezorcrypto_globals_table); diff --git a/core/src/trezor/ui/mnemonic.py b/core/src/trezor/ui/mnemonic_bip39.py similarity index 99% rename from core/src/trezor/ui/mnemonic.py rename to core/src/trezor/ui/mnemonic_bip39.py index 3da8041d6..4c4ae0c67 100644 --- a/core/src/trezor/ui/mnemonic.py +++ b/core/src/trezor/ui/mnemonic_bip39.py @@ -90,7 +90,7 @@ class Prompt(ui.Control): self.repaint = False -class MnemonicKeyboard(ui.Layout): +class Bip39Keyboard(ui.Layout): def __init__(self, prompt): self.prompt = Prompt(prompt) diff --git a/core/src/trezor/ui/mnemonic_slip39.py b/core/src/trezor/ui/mnemonic_slip39.py new file mode 100644 index 000000000..65ad6214b --- /dev/null +++ b/core/src/trezor/ui/mnemonic_slip39.py @@ -0,0 +1,205 @@ +from trezor import io, loop, res, ui +from trezor.crypto import slip39 +from trezor.ui import display +from trezor.ui.button import Button, ButtonClear, ButtonMono, ButtonMonoConfirm + + +def check_mask(mask: int, index: int) -> bool: + return bool((1 << (index - 1)) & mask) + + +# TODO: ask UX if we want to finish sooner than 4 words +# example: 'hairy' +def _is_final(content: str) -> bool: + return len(content) > 3 + + +class KeyButton(Button): + def __init__(self, area, content, keyboard, index): + self.keyboard = keyboard + self.index = index + super().__init__(area, content) + + def on_click(self): + self.keyboard.on_key_click(self) + + +class InputButton(Button): + def __init__(self, area, content, word): + super().__init__(area, content) + self.word = word + self.pending_button = None + self.pending_index = None + self.icon = None # rendered icon + self.disable() + + def edit(self, content, word, pending_button, pending_index): + self.word = word + self.content = content + self.pending_button = pending_button + self.pending_index = pending_index + self.repaint = True + if word: + self.enable() + self.normal_style = ButtonMonoConfirm.normal + self.active_style = ButtonMonoConfirm.active + self.icon = ui.ICON_CONFIRM + else: # disabled button + self.disabled_style = ButtonMono.normal + self.disable() + self.icon = None + + def render_content(self, s, ax, ay, aw, ah): + text_style = s.text_style + fg_color = s.fg_color + bg_color = s.bg_color + + tx = ax + 24 # x-offset of the content + ty = ay + ah // 2 + 8 # y-offset of the content + + if not _is_final(self.content): + to_display = len(self.content) * "*" + if self.pending_button: + to_display = ( + to_display[:-1] + self.pending_button.content[self.pending_index] + ) + else: + to_display = self.word + + display.text(tx, ty, to_display, text_style, fg_color, bg_color) + + if self.pending_button and not _is_final(self.content): + width = display.text_width(to_display, text_style) + pw = display.text_width(self.content[-1:], text_style) + px = tx + width - pw + display.bar(px, ty + 2, pw + 1, 3, fg_color) + + if self.icon: + ix = ax + aw - 16 * 2 + iy = ty - 16 + display.icon(ix, iy, res.load(self.icon), fg_color, bg_color) + + +class Prompt(ui.Control): + def __init__(self, prompt): + self.prompt = prompt + self.repaint = True + + def on_render(self): + if self.repaint: + display.bar(0, 8, ui.WIDTH, 60, ui.BG) + display.text(20, 40, self.prompt, ui.BOLD, ui.GREY, ui.BG) + self.repaint = False + + +class Slip39Keyboard(ui.Layout): + def __init__(self, prompt): + self.prompt = Prompt(prompt) + + icon_back = res.load(ui.ICON_BACK) + self.back = Button(ui.grid(0, n_x=4, n_y=4), icon_back, ButtonClear) + self.back.on_click = self.on_back_click + + self.input = InputButton(ui.grid(1, n_x=4, n_y=4, cells_x=3), "", "") + self.input.on_click = self.on_input_click + + self.keys = [ + KeyButton(ui.grid(i + 3, n_y=4), k, self, i + 1) + for i, k in enumerate( + ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz") + ) + ] + self.pending_button = None + self.pending_index = 0 + self.button_sequence = "" + + def dispatch(self, event: int, x: int, y: int): + for btn in self.keys: + btn.dispatch(event, x, y) + if self.input.content: + self.input.dispatch(event, x, y) + self.back.dispatch(event, x, y) + else: + self.prompt.dispatch(event, x, y) + + def on_back_click(self): + # Backspace was clicked, let's delete the last character of input. + self.button_sequence = self.button_sequence[:-1] + self.edit() + + def on_input_click(self): + # Input button was clicked. If the content matches the suggested word, + # let's confirm it, otherwise just auto-complete. + result = self.input.word + if _is_final(self.input.content): + self.button_sequence = "" + self.edit() + self.on_confirm(result) + + def on_key_click(self, btn: KeyButton): + # Key button was clicked. If this button is pending, let's cycle the + # pending character in input. If not, let's just append the first + # character. + if self.pending_button is btn: + index = (self.pending_index + 1) % len(btn.content) + else: + index = 0 + self.button_sequence += str(btn.index) + self.edit(btn, index) + + def on_timeout(self): + # Timeout occurred. Let's redraw to draw asterisks. + self.edit() + + def on_confirm(self, word): + # Word was confirmed by the user. + raise ui.Result(word) + + def edit(self, button: KeyButton = None, index: int = 0): + self.pending_button = button + self.pending_index = index + + # find the completions + mask = 0 + word = "" + if _is_final(self.button_sequence): + word = slip39.button_sequence_to_word(self.button_sequence) + else: + mask = slip39.compute_mask(self.button_sequence) + + # modify the input state + self.input.edit( + self.button_sequence, word, self.pending_button, self.pending_index + ) + + # enable or disable key buttons + for btn in self.keys: + if (not _is_final(self.button_sequence) and btn is button) or check_mask( + mask, btn.index + ): + btn.enable() + else: + btn.disable() + + # invalidate the prompt if we display it next frame + if not self.input.content: + self.prompt.repaint = True + + async def handle_input(self): + touch = loop.wait(io.TOUCH) + timeout = loop.sleep(1000 * 1000 * 1) + spawn_touch = loop.spawn(touch) + spawn_timeout = loop.spawn(touch, timeout) + + while True: + if self.pending_button is not None: + spawn = spawn_timeout + else: + spawn = spawn_touch + result = await spawn + + if touch in spawn.finished: + event, x, y = result + self.dispatch(event, x, y) + else: + self.on_timeout() diff --git a/crypto/Makefile b/crypto/Makefile index 7b018482d..1c5f1672f 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -66,6 +66,7 @@ SRCS += memzero.c SRCS += shamir.c SRCS += hmac_drbg.c SRCS += rfc6979.c +SRCS += slip39.c OBJS = $(SRCS:.c=.o) diff --git a/crypto/slip39.c b/crypto/slip39.c new file mode 100644 index 000000000..9258e637f --- /dev/null +++ b/crypto/slip39.c @@ -0,0 +1,136 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * 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 "slip39.h" +#include +#include +#include "slip39_wordlist.h" + +/** + * Returns word on position `index`. + */ +const char* get_word(uint16_t index) { return wordlist[index]; } + +/** + * Finds index of given word, if found. + * Returns true on success and stores result in `index`. + */ +bool word_index(uint16_t* index, const char* word, uint8_t word_length) { + uint16_t lo = 0; + uint16_t hi = WORDS_COUNT; + uint16_t mid = 0; + + while ((hi - lo) > 1) { + mid = (hi + lo) / 2; + if (strncmp(wordlist[mid], word, word_length) > 0) { + hi = mid; + } else { + lo = mid; + } + } + if (strncmp(wordlist[lo], word, word_length) != 0) { + return false; + } + *index = lo; + return true; +} + +/** + * Calculates which buttons still can be pressed after some already were. + * Returns a 9-bit bitmask, where each bit specifies which buttons + * can be further pressed (there are still words in this combination). + * LSB denotes first button. + * + * Example: 110000110 - second, third, eighth and ninth button still can be + * pressed. + */ +uint16_t compute_mask(uint16_t prefix) { return find(prefix, false); } + +/** + * Converts sequence to word index. + */ +const char* button_sequence_to_word(uint16_t prefix) { + return wordlist[find(prefix, true)]; +} + +/** + * Computes mask if find_index is false. + * Finds the first word index that suits the prefix otherwise. + */ +uint16_t find(uint16_t prefix, bool find_index) { + uint16_t min = prefix; + uint16_t max = 0; + uint16_t for_max = 0; + uint8_t multiplier = 0; + uint8_t divider = 0; + uint16_t bitmap = 0; + uint8_t digit = 0; + uint16_t i = 0; + + max = prefix + 1; + while (min < 1000) { + min = min * 10; + max = max * 10; + multiplier++; + } + + // Four char prefix -> the mask is zero + if (!multiplier && !find_index) { + return 0; + } + for_max = min - (min % 1000) + 1000; + + // We can't use binary search because the numbers are not sorted. + // They are sorted using the words' alphabet (so we can use the index). + // Example: axle (1953), beam (1315) + // The first digit is sorted so we can loop only upto `for_max`. + while (words_button_seq[i] < for_max) { + if (words_button_seq[i] >= min && words_button_seq[i] < max) { + if (find_index) { + return i; + } + + switch (multiplier) { + case 1: + divider = 1; + break; + case 2: + divider = 10; + break; + case 3: + divider = 100; + break; + default: + divider = 1; + break; + } + + digit = (words_button_seq[i] / divider) % 10; + bitmap |= 1 << (digit - 1); + } + i++; + } + + return bitmap; +} diff --git a/crypto/slip39.h b/crypto/slip39.h new file mode 100644 index 000000000..ba314303a --- /dev/null +++ b/crypto/slip39.h @@ -0,0 +1,36 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * 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 +#include + +const char* get_word(uint16_t index); + +bool word_index(uint16_t* index, const char* word, uint8_t word_length); + +uint16_t compute_mask(uint16_t prefix); + +const char* button_sequence_to_word(uint16_t prefix); + +uint16_t find(uint16_t prefix, bool find_index); diff --git a/crypto/slip39_wordlist.h b/crypto/slip39_wordlist.h new file mode 100644 index 000000000..34fe1991d --- /dev/null +++ b/crypto/slip39_wordlist.h @@ -0,0 +1,1238 @@ +/** + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * 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 + +#define WORDS_COUNT 1024 + +static const char* const wordlist[WORDS_COUNT] = { + "academic", "acid", "acne", "acquire", "acrobat", "activity", + "actress", "adapt", "adequate", "adjust", "admit", "adorn", + "adult", "advance", "advocate", "afraid", "again", "agency", + "agree", "aide", "aircraft", "airline", "airport", "ajar", + "alarm", "album", "alcohol", "alien", "alive", "alpha", + "already", "alto", "aluminum", "always", "amazing", "ambition", + "amount", "amuse", "analysis", "anatomy", "ancestor", "ancient", + "angel", "angry", "animal", "answer", "antenna", "anxiety", + "apart", "aquatic", "arcade", "arena", "argue", "armed", + "artist", "artwork", "aspect", "auction", "august", "aunt", + "average", "aviation", "avoid", "award", "away", "axis", + "axle", "beam", "beard", "beaver", "become", "bedroom", + "behavior", "being", "believe", "belong", "benefit", "best", + "beyond", "bike", "biology", "birthday", "bishop", "black", + "blanket", "blessing", "blimp", "blind", "blue", "body", + "bolt", "boring", "born", "both", "boundary", "bracelet", + "branch", "brave", "breathe", "briefing", "broken", "brother", + "browser", "bucket", "budget", "building", "bulb", "bulge", + "bumpy", "bundle", "burden", "burning", "busy", "buyer", + "cage", "calcium", "camera", "campus", "canyon", "capacity", + "capital", "capture", "carbon", "cards", "careful", "cargo", + "carpet", "carve", "category", "cause", "ceiling", "center", + "ceramic", "champion", "change", "charity", "check", "chemical", + "chest", "chew", "chubby", "cinema", "civil", "class", + "clay", "cleanup", "client", "climate", "clinic", "clock", + "clogs", "closet", "clothes", "club", "cluster", "coal", + "coastal", "coding", "column", "company", "corner", "costume", + "counter", "course", "cover", "cowboy", "cradle", "craft", + "crazy", "credit", "cricket", "criminal", "crisis", "critical", + "crowd", "crucial", "crunch", "crush", "crystal", "cubic", + "cultural", "curious", "curly", "custody", "cylinder", "daisy", + "damage", "dance", "darkness", "database", "daughter", "deadline", + "deal", "debris", "debut", "decent", "decision", "declare", + "decorate", "decrease", "deliver", "demand", "density", "deny", + "depart", "depend", "depict", "deploy", "describe", "desert", + "desire", "desktop", "destroy", "detailed", "detect", "device", + "devote", "diagnose", "dictate", "diet", "dilemma", "diminish", + "dining", "diploma", "disaster", "discuss", "disease", "dish", + "dismiss", "display", "distance", "dive", "divorce", "document", + "domain", "domestic", "dominant", "dough", "downtown", "dragon", + "dramatic", "dream", "dress", "drift", "drink", "drove", + "drug", "dryer", "duckling", "duke", "duration", "dwarf", + "dynamic", "early", "earth", "easel", "easy", "echo", + "eclipse", "ecology", "edge", "editor", "educate", "either", + "elbow", "elder", "election", "elegant", "element", "elephant", + "elevator", "elite", "else", "email", "emerald", "emission", + "emperor", "emphasis", "employer", "empty", "ending", "endless", + "endorse", "enemy", "energy", "enforce", "engage", "enjoy", + "enlarge", "entrance", "envelope", "envy", "epidemic", "episode", + "equation", "equip", "eraser", "erode", "escape", "estate", + "estimate", "evaluate", "evening", "evidence", "evil", "evoke", + "exact", "example", "exceed", "exchange", "exclude", "excuse", + "execute", "exercise", "exhaust", "exotic", "expand", "expect", + "explain", "express", "extend", "extra", "eyebrow", "facility", + "fact", "failure", "faint", "fake", "false", "family", + "famous", "fancy", "fangs", "fantasy", "fatal", "fatigue", + "favorite", "fawn", "fiber", "fiction", "filter", "finance", + "findings", "finger", "firefly", "firm", "fiscal", "fishing", + "fitness", "flame", "flash", "flavor", "flea", "flexible", + "flip", "float", "floral", "fluff", "focus", "forbid", + "force", "forecast", "forget", "formal", "fortune", "forward", + "founder", "fraction", "fragment", "frequent", "freshman", "friar", + "fridge", "friendly", "frost", "froth", "frozen", "fumes", + "funding", "furl", "fused", "galaxy", "game", "garbage", + "garden", "garlic", "gasoline", "gather", "general", "genius", + "genre", "genuine", "geology", "gesture", "glad", "glance", + "glasses", "glen", "glimpse", "goat", "golden", "graduate", + "grant", "grasp", "gravity", "gray", "greatest", "grief", + "grill", "grin", "grocery", "gross", "group", "grownup", + "grumpy", "guard", "guest", "guilt", "guitar", "gums", + "hairy", "hamster", "hand", "hanger", "harvest", "have", + "havoc", "hawk", "hazard", "headset", "health", "hearing", + "heat", "helpful", "herald", "herd", "hesitate", "hobo", + "holiday", "holy", "home", "hormone", "hospital", "hour", + "huge", "human", "humidity", "hunting", "husband", "hush", + "husky", "hybrid", "idea", "identify", "idle", "image", + "impact", "imply", "improve", "impulse", "include", "income", + "increase", "index", "indicate", "industry", "infant", "inform", + "inherit", "injury", "inmate", "insect", "inside", "install", + "intend", "intimate", "invasion", "involve", "iris", "island", + "isolate", "item", "ivory", "jacket", "jerky", "jewelry", + "join", "judicial", "juice", "jump", "junction", "junior", + "junk", "jury", "justice", "kernel", "keyboard", "kidney", + "kind", "kitchen", "knife", "knit", "laden", "ladle", + "ladybug", "lair", "lamp", "language", "large", "laser", + "laundry", "lawsuit", "leader", "leaf", "learn", "leaves", + "lecture", "legal", "legend", "legs", "lend", "length", + "level", "liberty", "library", "license", "lift", "likely", + "lilac", "lily", "lips", "liquid", "listen", "literary", + "living", "lizard", "loan", "lobe", "location", "losing", + "loud", "loyalty", "luck", "lunar", "lunch", "lungs", + "luxury", "lying", "lyrics", "machine", "magazine", "maiden", + "mailman", "main", "makeup", "making", "mama", "manager", + "mandate", "mansion", "manual", "marathon", "march", "market", + "marvel", "mason", "material", "math", "maximum", "mayor", + "meaning", "medal", "medical", "member", "memory", "mental", + "merchant", "merit", "method", "metric", "midst", "mild", + "military", "mineral", "minister", "miracle", "mixed", "mixture", + "mobile", "modern", "modify", "moisture", "moment", "morning", + "mortgage", "mother", "mountain", "mouse", "move", "much", + "mule", "multiple", "muscle", "museum", "music", "mustang", + "nail", "national", "necklace", "negative", "nervous", "network", + "news", "nuclear", "numb", "numerous", "nylon", "oasis", + "obesity", "object", "observe", "obtain", "ocean", "often", + "olympic", "omit", "oral", "orange", "orbit", "order", + "ordinary", "organize", "ounce", "oven", "overall", "owner", + "paces", "pacific", "package", "paid", "painting", "pajamas", + "pancake", "pants", "papa", "paper", "parcel", "parking", + "party", "patent", "patrol", "payment", "payroll", "peaceful", + "peanut", "peasant", "pecan", "penalty", "pencil", "percent", + "perfect", "permit", "petition", "phantom", "pharmacy", "photo", + "phrase", "physics", "pickup", "picture", "piece", "pile", + "pink", "pipeline", "pistol", "pitch", "plains", "plan", + "plastic", "platform", "playoff", "pleasure", "plot", "plunge", + "practice", "prayer", "preach", "predator", "pregnant", "premium", + "prepare", "presence", "prevent", "priest", "primary", "priority", + "prisoner", "privacy", "prize", "problem", "process", "profile", + "program", "promise", "prospect", "provide", "prune", "public", + "pulse", "pumps", "punish", "puny", "pupal", "purchase", + "purple", "python", "quantity", "quarter", "quick", "quiet", + "race", "racism", "radar", "railroad", "rainbow", "raisin", + "random", "ranked", "rapids", "raspy", "reaction", "realize", + "rebound", "rebuild", "recall", "receiver", "recover", "regret", + "regular", "reject", "relate", "remember", "remind", "remove", + "render", "repair", "repeat", "replace", "require", "rescue", + "research", "resident", "response", "result", "retailer", "retreat", + "reunion", "revenue", "review", "reward", "rhyme", "rhythm", + "rich", "rival", "river", "robin", "rocky", "romantic", + "romp", "roster", "round", "royal", "ruin", "ruler", + "rumor", "sack", "safari", "salary", "salon", "salt", + "satisfy", "satoshi", "saver", "says", "scandal", "scared", + "scatter", "scene", "scholar", "science", "scout", "scramble", + "screw", "script", "scroll", "seafood", "season", "secret", + "security", "segment", "senior", "shadow", "shaft", "shame", + "shaped", "sharp", "shelter", "sheriff", "short", "should", + "shrimp", "sidewalk", "silent", "silver", "similar", "simple", + "single", "sister", "skin", "skunk", "slap", "slavery", + "sled", "slice", "slim", "slow", "slush", "smart", + "smear", "smell", "smirk", "smith", "smoking", "smug", + "snake", "snapshot", "sniff", "society", "software", "soldier", + "solution", "soul", "source", "space", "spark", "speak", + "species", "spelling", "spend", "spew", "spider", "spill", + "spine", "spirit", "spit", "spray", "sprinkle", "square", + "squeeze", "stadium", "staff", "standard", "starting", "station", + "stay", "steady", "step", "stick", "stilt", "story", + "strategy", "strike", "style", "subject", "submit", "sugar", + "suitable", "sunlight", "superior", "surface", "surprise", "survive", + "sweater", "swimming", "swing", "switch", "symbolic", "sympathy", + "syndrome", "system", "tackle", "tactics", "tadpole", "talent", + "task", "taste", "taught", "taxi", "teacher", "teammate", + "teaspoon", "temple", "tenant", "tendency", "tension", "terminal", + "testify", "texture", "thank", "that", "theater", "theory", + "therapy", "thorn", "threaten", "thumb", "thunder", "ticket", + "tidy", "timber", "timely", "ting", "tofu", "together", + "tolerate", "total", "toxic", "tracks", "traffic", "training", + "transfer", "trash", "traveler", "treat", "trend", "trial", + "tricycle", "trip", "triumph", "trouble", "true", "trust", + "twice", "twin", "type", "typical", "ugly", "ultimate", + "umbrella", "uncover", "undergo", "unfair", "unfold", "unhappy", + "union", "universe", "unkind", "unknown", "unusual", "unwrap", + "upgrade", "upstairs", "username", "usher", "usual", "valid", + "valuable", "vampire", "vanish", "various", "vegan", "velvet", + "venture", "verdict", "verify", "very", "veteran", "vexed", + "victim", "video", "view", "vintage", "violence", "viral", + "visitor", "visual", "vitamins", "vocal", "voice", "volume", + "voter", "voting", "walnut", "warmth", "warn", "watch", + "wavy", "wealthy", "weapon", "webcam", "welcome", "welfare", + "western", "width", "wildlife", "window", "wine", "wireless", + "wisdom", "withdraw", "wits", "wolf", "woman", "work", + "worthy", "wrap", "wrist", "writing", "wrote", "year", + "yelp", "yield", "yoga", "zero", +}; + +/** + * This array contains number representations of SLIP-39 words. + * These numbers are determined how the words were entered on a + * T9 keyboard with the following layout: + * ab (1) cd (2) ef (3) + * ghij (4) klm (5) nopq (6) + * rs (7) tuv (8) wxyz (9) + * + * Each word is uniquely defined by four buttons. + */ +static const uint16_t words_button_seq[WORDS_COUNT] = { + 1212, // academic + 1242, // acid + 1263, // acne + 1268, // acquire + 1276, // acrobat + 1284, // activity + 1287, // actress + 1216, // adapt + 1236, // adequate + 1248, // adjust + 1254, // admit + 1267, // adorn + 1285, // adult + 1281, // advance + 1286, // advocate + 1371, // afraid + 1414, // again + 1436, // agency + 1473, // agree + 1423, // aide + 1472, // aircraft + 1475, // airline + 1476, // airport + 1417, // ajar + 1517, // alarm + 1518, // album + 1526, // alcohol + 1543, // alien + 1548, // alive + 1564, // alpha + 1573, // already + 1586, // alto + 1585, // aluminum + 1591, // always + 1519, // amazing + 1514, // ambition + 1568, // amount + 1587, // amuse + 1615, // analysis + 1618, // anatomy + 1623, // ancestor + 1624, // ancient + 1643, // angel + 1647, // angry + 1645, // animal + 1679, // answer + 1683, // antenna + 1694, // anxiety + 1617, // apart + 1681, // aquatic + 1721, // arcade + 1736, // arena + 1748, // argue + 1753, // armed + 1784, // artist + 1789, // artwork + 1763, // aspect + 1828, // auction + 1848, // august + 1868, // aunt + 1837, // average + 1841, // aviation + 1864, // avoid + 1917, // award + 1919, // away + 1947, // axis + 1953, // axle + 1315, // beam + 1317, // beard + 1318, // beaver + 1326, // become + 1327, // bedroom + 1341, // behavior + 1346, // being + 1354, // believe + 1356, // belong + 1363, // benefit + 1378, // best + 1396, // beyond + 1453, // bike + 1465, // biology + 1478, // birthday + 1474, // bishop + 1512, // black + 1516, // blanket + 1537, // blessing + 1545, // blimp + 1546, // blind + 1583, // blue + 1629, // body + 1658, // bolt + 1674, // boring + 1676, // born + 1684, // both + 1686, // boundary + 1712, // bracelet + 1716, // branch + 1718, // brave + 1731, // breathe + 1743, // briefing + 1765, // broken + 1768, // brother + 1769, // browser + 1825, // bucket + 1824, // budget + 1845, // building + 1851, // bulb + 1854, // bulge + 1856, // bumpy + 1862, // bundle + 1872, // burden + 1876, // burning + 1879, // busy + 1893, // buyer + 2143, // cage + 2152, // calcium + 2153, // camera + 2156, // campus + 2169, // canyon + 2161, // capacity + 2164, // capital + 2168, // capture + 2171, // carbon + 2172, // cards + 2173, // careful + 2174, // cargo + 2176, // carpet + 2178, // carve + 2183, // category + 2187, // cause + 2345, // ceiling + 2368, // center + 2371, // ceramic + 2415, // champion + 2416, // change + 2417, // charity + 2432, // check + 2435, // chemical + 2437, // chest + 2439, // chew + 2481, // chubby + 2463, // cinema + 2484, // civil + 2517, // class + 2519, // clay + 2531, // cleanup + 2543, // client + 2545, // climate + 2546, // clinic + 2562, // clock + 2564, // clogs + 2567, // closet + 2568, // clothes + 2581, // club + 2587, // cluster + 2615, // coal + 2617, // coastal + 2624, // coding + 2658, // column + 2656, // company + 2676, // corner + 2678, // costume + 2686, // counter + 2687, // course + 2683, // cover + 2691, // cowboy + 2712, // cradle + 2713, // craft + 2719, // crazy + 2732, // credit + 2742, // cricket + 2745, // criminal + 2747, // crisis + 2748, // critical + 2769, // crowd + 2782, // crucial + 2786, // crunch + 2787, // crush + 2797, // crystal + 2814, // cubic + 2858, // cultural + 2874, // curious + 2875, // curly + 2878, // custody + 2954, // cylinder + 2147, // daisy + 2151, // damage + 2162, // dance + 2175, // darkness + 2181, // database + 2184, // daughter + 2312, // deadline + 2315, // deal + 2317, // debris + 2318, // debut + 2323, // decent + 2324, // decision + 2325, // declare + 2326, // decorate + 2327, // decrease + 2354, // deliver + 2351, // demand + 2367, // density + 2369, // deny + 2361, // depart + 2363, // depend + 2364, // depict + 2365, // deploy + 2372, // describe + 2373, // desert + 2374, // desire + 2375, // desktop + 2378, // destroy + 2381, // detailed + 2383, // detect + 2384, // device + 2386, // devote + 2414, // diagnose + 2428, // dictate + 2438, // diet + 2453, // dilemma + 2454, // diminish + 2464, // dining + 2465, // diploma + 2471, // disaster + 2472, // discuss + 2473, // disease + 2474, // dish + 2475, // dismiss + 2476, // display + 2478, // distance + 2483, // dive + 2486, // divorce + 2628, // document + 2651, // domain + 2653, // domestic + 2654, // dominant + 2684, // dough + 2696, // downtown + 2714, // dragon + 2715, // dramatic + 2731, // dream + 2737, // dress + 2743, // drift + 2746, // drink + 2768, // drove + 2784, // drug + 2793, // dryer + 2825, // duckling + 2853, // duke + 2871, // duration + 2917, // dwarf + 2961, // dynamic + 3175, // early + 3178, // earth + 3173, // easel + 3179, // easy + 3246, // echo + 3254, // eclipse + 3265, // ecology + 3243, // edge + 3248, // editor + 3282, // educate + 3484, // either + 3516, // elbow + 3523, // elder + 3532, // election + 3534, // elegant + 3535, // element + 3536, // elephant + 3538, // elevator + 3548, // elite + 3573, // else + 3514, // email + 3537, // emerald + 3547, // emission + 3563, // emperor + 3564, // emphasis + 3565, // employer + 3568, // empty + 3624, // ending + 3625, // endless + 3626, // endorse + 3635, // enemy + 3637, // energy + 3636, // enforce + 3641, // engage + 3646, // enjoy + 3651, // enlarge + 3687, // entrance + 3683, // envelope + 3689, // envy + 3642, // epidemic + 3647, // episode + 3681, // equation + 3684, // equip + 3717, // eraser + 3762, // erode + 3721, // escape + 3781, // estate + 3784, // estimate + 3815, // evaluate + 3836, // evening + 3842, // evidence + 3845, // evil + 3865, // evoke + 3912, // exact + 3915, // example + 3923, // exceed + 3924, // exchange + 3925, // exclude + 3928, // excuse + 3932, // execute + 3937, // exercise + 3941, // exhaust + 3968, // exotic + 3961, // expand + 3963, // expect + 3965, // explain + 3967, // express + 3983, // extend + 3987, // extra + 3931, // eyebrow + 3124, // facility + 3128, // fact + 3145, // failure + 3146, // faint + 3153, // fake + 3157, // false + 3154, // family + 3156, // famous + 3162, // fancy + 3164, // fangs + 3168, // fantasy + 3181, // fatal + 3184, // fatigue + 3186, // favorite + 3196, // fawn + 3413, // fiber + 3428, // fiction + 3458, // filter + 3461, // finance + 3462, // findings + 3464, // finger + 3473, // firefly + 3475, // firm + 3472, // fiscal + 3474, // fishing + 3486, // fitness + 3515, // flame + 3517, // flash + 3518, // flavor + 3531, // flea + 3539, // flexible + 3546, // flip + 3561, // float + 3567, // floral + 3583, // fluff + 3628, // focus + 3671, // forbid + 3672, // force + 3673, // forecast + 3674, // forget + 3675, // formal + 3678, // fortune + 3679, // forward + 3686, // founder + 3712, // fraction + 3714, // fragment + 3736, // frequent + 3737, // freshman + 3741, // friar + 3742, // fridge + 3743, // friendly + 3767, // frost + 3768, // froth + 3769, // frozen + 3853, // fumes + 3862, // funding + 3875, // furl + 3873, // fused + 4151, // galaxy + 4153, // game + 4171, // garbage + 4172, // garden + 4175, // garlic + 4176, // gasoline + 4184, // gather + 4363, // general + 4364, // genius + 4367, // genre + 4368, // genuine + 4365, // geology + 4378, // gesture + 4512, // glad + 4516, // glance + 4517, // glasses + 4536, // glen + 4545, // glimpse + 4618, // goat + 4652, // golden + 4712, // graduate + 4716, // grant + 4717, // grasp + 4718, // gravity + 4719, // gray + 4731, // greatest + 4743, // grief + 4745, // grill + 4746, // grin + 4762, // grocery + 4767, // gross + 4768, // group + 4769, // grownup + 4785, // grumpy + 4817, // guard + 4837, // guest + 4845, // guilt + 4848, // guitar + 4857, // gums + 4147, // hairy + 4157, // hamster + 4162, // hand + 4164, // hanger + 4178, // harvest + 4183, // have + 4186, // havoc + 4195, // hawk + 4191, // hazard + 4312, // headset + 4315, // health + 4317, // hearing + 4318, // heat + 4356, // helpful + 4371, // herald + 4372, // herd + 4374, // hesitate + 4616, // hobo + 4654, // holiday + 4659, // holy + 4653, // home + 4675, // hormone + 4676, // hospital + 4687, // hour + 4843, // huge + 4851, // human + 4854, // humidity + 4868, // hunting + 4871, // husband + 4874, // hush + 4875, // husky + 4917, // hybrid + 4231, // idea + 4236, // identify + 4253, // idle + 4514, // image + 4561, // impact + 4565, // imply + 4567, // improve + 4568, // impulse + 4625, // include + 4626, // income + 4627, // increase + 4623, // index + 4624, // indicate + 4628, // industry + 4631, // infant + 4636, // inform + 4643, // inherit + 4648, // injury + 4651, // inmate + 4673, // insect + 4674, // inside + 4678, // install + 4683, // intend + 4684, // intimate + 4681, // invasion + 4686, // involve + 4747, // iris + 4751, // island + 4765, // isolate + 4835, // item + 4867, // ivory + 4125, // jacket + 4375, // jerky + 4393, // jewelry + 4646, // join + 4824, // judicial + 4842, // juice + 4856, // jump + 4862, // junction + 4864, // junior + 4865, // junk + 4879, // jury + 4878, // justice + 5376, // kernel + 5391, // keyboard + 5426, // kidney + 5462, // kind + 5482, // kitchen + 5643, // knife + 5648, // knit + 5123, // laden + 5125, // ladle + 5129, // ladybug + 5147, // lair + 5156, // lamp + 5164, // language + 5174, // large + 5173, // laser + 5186, // laundry + 5197, // lawsuit + 5312, // leader + 5313, // leaf + 5317, // learn + 5318, // leaves + 5328, // lecture + 5341, // legal + 5343, // legend + 5347, // legs + 5362, // lend + 5364, // length + 5383, // level + 5413, // liberty + 5417, // library + 5423, // license + 5438, // lift + 5453, // likely + 5451, // lilac + 5459, // lily + 5467, // lips + 5468, // liquid + 5478, // listen + 5483, // literary + 5484, // living + 5491, // lizard + 5616, // loan + 5613, // lobe + 5621, // location + 5674, // losing + 5682, // loud + 5691, // loyalty + 5825, // luck + 5861, // lunar + 5862, // lunch + 5864, // lungs + 5898, // luxury + 5946, // lying + 5974, // lyrics + 5124, // machine + 5141, // magazine + 5142, // maiden + 5145, // mailman + 5146, // main + 5153, // makeup + 5154, // making + 5151, // mama + 5161, // manager + 5162, // mandate + 5167, // mansion + 5168, // manual + 5171, // marathon + 5172, // march + 5175, // market + 5178, // marvel + 5176, // mason + 5183, // material + 5184, // math + 5194, // maximum + 5196, // mayor + 5316, // meaning + 5321, // medal + 5324, // medical + 5351, // member + 5356, // memory + 5368, // mental + 5372, // merchant + 5374, // merit + 5384, // method + 5387, // metric + 5427, // midst + 5452, // mild + 5454, // military + 5463, // mineral + 5464, // minister + 5471, // miracle + 5493, // mixed + 5498, // mixture + 5614, // mobile + 5623, // modern + 5624, // modify + 5647, // moisture + 5653, // moment + 5676, // morning + 5678, // mortgage + 5684, // mother + 5686, // mountain + 5687, // mouse + 5683, // move + 5824, // much + 5853, // mule + 5858, // multiple + 5872, // muscle + 5873, // museum + 5874, // music + 5878, // mustang + 6145, // nail + 6184, // national + 6325, // necklace + 6341, // negative + 6378, // nervous + 6389, // network + 6397, // news + 6825, // nuclear + 6851, // numb + 6853, // numerous + 6956, // nylon + 6174, // oasis + 6137, // obesity + 6143, // object + 6173, // observe + 6181, // obtain + 6231, // ocean + 6383, // often + 6595, // olympic + 6548, // omit + 6715, // oral + 6716, // orange + 6714, // orbit + 6723, // order + 6724, // ordinary + 6741, // organize + 6862, // ounce + 6836, // oven + 6837, // overall + 6963, // owner + 6123, // paces + 6124, // pacific + 6125, // package + 6142, // paid + 6146, // painting + 6141, // pajamas + 6162, // pancake + 6168, // pants + 6161, // papa + 6163, // paper + 6172, // parcel + 6175, // parking + 6178, // party + 6183, // patent + 6187, // patrol + 6195, // payment + 6197, // payroll + 6312, // peaceful + 6316, // peanut + 6317, // peasant + 6321, // pecan + 6361, // penalty + 6362, // pencil + 6372, // percent + 6373, // perfect + 6375, // permit + 6384, // petition + 6416, // phantom + 6417, // pharmacy + 6468, // photo + 6471, // phrase + 6497, // physics + 6425, // pickup + 6428, // picture + 6432, // piece + 6453, // pile + 6465, // pink + 6463, // pipeline + 6478, // pistol + 6482, // pitch + 6514, // plains + 6516, // plan + 6517, // plastic + 6518, // platform + 6519, // playoff + 6531, // pleasure + 6568, // plot + 6586, // plunge + 6712, // practice + 6719, // prayer + 6731, // preach + 6732, // predator + 6734, // pregnant + 6735, // premium + 6736, // prepare + 6737, // presence + 6738, // prevent + 6743, // priest + 6745, // primary + 6746, // priority + 6747, // prisoner + 6748, // privacy + 6749, // prize + 6761, // problem + 6762, // process + 6763, // profile + 6764, // program + 6765, // promise + 6767, // prospect + 6768, // provide + 6786, // prune + 6815, // public + 6857, // pulse + 6856, // pumps + 6864, // punish + 6869, // puny + 6861, // pupal + 6872, // purchase + 6876, // purple + 6984, // python + 6816, // quantity + 6817, // quarter + 6842, // quick + 6843, // quiet + 7123, // race + 7124, // racism + 7121, // radar + 7145, // railroad + 7146, // rainbow + 7147, // raisin + 7162, // random + 7165, // ranked + 7164, // rapids + 7176, // raspy + 7312, // reaction + 7315, // realize + 7316, // rebound + 7318, // rebuild + 7321, // recall + 7323, // receiver + 7326, // recover + 7347, // regret + 7348, // regular + 7343, // reject + 7351, // relate + 7353, // remember + 7354, // remind + 7356, // remove + 7362, // render + 7361, // repair + 7363, // repeat + 7365, // replace + 7368, // require + 7372, // rescue + 7373, // research + 7374, // resident + 7376, // response + 7378, // result + 7381, // retailer + 7387, // retreat + 7386, // reunion + 7383, // revenue + 7384, // review + 7391, // reward + 7495, // rhyme + 7498, // rhythm + 7424, // rich + 7481, // rival + 7483, // river + 7614, // robin + 7625, // rocky + 7651, // romantic + 7656, // romp + 7678, // roster + 7686, // round + 7691, // royal + 7846, // ruin + 7853, // ruler + 7856, // rumor + 7125, // sack + 7131, // safari + 7151, // salary + 7156, // salon + 7158, // salt + 7184, // satisfy + 7186, // satoshi + 7183, // saver + 7197, // says + 7216, // scandal + 7217, // scared + 7218, // scatter + 7236, // scene + 7246, // scholar + 7243, // science + 7268, // scout + 7271, // scramble + 7273, // screw + 7274, // script + 7276, // scroll + 7313, // seafood + 7317, // season + 7327, // secret + 7328, // security + 7345, // segment + 7364, // senior + 7412, // shadow + 7413, // shaft + 7415, // shame + 7416, // shaped + 7417, // sharp + 7435, // shelter + 7437, // sheriff + 7467, // short + 7468, // should + 7474, // shrimp + 7423, // sidewalk + 7453, // silent + 7458, // silver + 7454, // similar + 7456, // simple + 7464, // single + 7478, // sister + 7546, // skin + 7586, // skunk + 7516, // slap + 7518, // slavery + 7532, // sled + 7542, // slice + 7545, // slim + 7569, // slow + 7587, // slush + 7517, // smart + 7531, // smear + 7535, // smell + 7547, // smirk + 7548, // smith + 7565, // smoking + 7584, // smug + 7615, // snake + 7616, // snapshot + 7643, // sniff + 7624, // society + 7638, // software + 7652, // soldier + 7658, // solution + 7685, // soul + 7687, // source + 7612, // space + 7617, // spark + 7631, // speak + 7632, // species + 7635, // spelling + 7636, // spend + 7639, // spew + 7642, // spider + 7645, // spill + 7646, // spine + 7647, // spirit + 7648, // spit + 7671, // spray + 7674, // sprinkle + 7681, // square + 7683, // squeeze + 7812, // stadium + 7813, // staff + 7816, // standard + 7817, // starting + 7818, // station + 7819, // stay + 7831, // steady + 7836, // step + 7842, // stick + 7845, // stilt + 7867, // story + 7871, // strategy + 7874, // strike + 7895, // style + 7814, // subject + 7815, // submit + 7841, // sugar + 7848, // suitable + 7865, // sunlight + 7863, // superior + 7873, // surface + 7876, // surprise + 7878, // survive + 7931, // sweater + 7945, // swimming + 7946, // swing + 7948, // switch + 7951, // symbolic + 7956, // sympathy + 7962, // syndrome + 7978, // system + 8125, // tackle + 8128, // tactics + 8126, // tadpole + 8153, // talent + 8175, // task + 8178, // taste + 8184, // taught + 8194, // taxi + 8312, // teacher + 8315, // teammate + 8317, // teaspoon + 8356, // temple + 8361, // tenant + 8362, // tendency + 8367, // tension + 8375, // terminal + 8378, // testify + 8398, // texture + 8416, // thank + 8418, // that + 8431, // theater + 8436, // theory + 8437, // therapy + 8467, // thorn + 8473, // threaten + 8485, // thumb + 8486, // thunder + 8425, // ticket + 8429, // tidy + 8451, // timber + 8453, // timely + 8464, // ting + 8638, // tofu + 8643, // together + 8653, // tolerate + 8681, // total + 8694, // toxic + 8712, // tracks + 8713, // traffic + 8714, // training + 8716, // transfer + 8717, // trash + 8718, // traveler + 8731, // treat + 8736, // trend + 8741, // trial + 8742, // tricycle + 8746, // trip + 8748, // triumph + 8768, // trouble + 8783, // true + 8787, // trust + 8942, // twice + 8946, // twin + 8963, // type + 8964, // typical + 8459, // ugly + 8584, // ultimate + 8517, // umbrella + 8626, // uncover + 8623, // undergo + 8631, // unfair + 8636, // unfold + 8641, // unhappy + 8646, // union + 8648, // universe + 8654, // unkind + 8656, // unknown + 8687, // unusual + 8697, // unwrap + 8647, // upgrade + 8678, // upstairs + 8737, // username + 8743, // usher + 8781, // usual + 8154, // valid + 8158, // valuable + 8156, // vampire + 8164, // vanish + 8174, // various + 8341, // vegan + 8358, // velvet + 8368, // venture + 8372, // verdict + 8374, // verify + 8379, // very + 8383, // veteran + 8393, // vexed + 8428, // victim + 8423, // video + 8439, // view + 8468, // vintage + 8465, // violence + 8471, // viral + 8474, // visitor + 8478, // visual + 8481, // vitamins + 8621, // vocal + 8642, // voice + 8658, // volume + 8683, // voter + 8684, // voting + 9156, // walnut + 9175, // warmth + 9176, // warn + 9182, // watch + 9189, // wavy + 9315, // wealthy + 9316, // weapon + 9312, // webcam + 9352, // welcome + 9353, // welfare + 9378, // western + 9428, // width + 9452, // wildlife + 9462, // window + 9463, // wine + 9473, // wireless + 9472, // wisdom + 9484, // withdraw + 9487, // wits + 9653, // wolf + 9651, // woman + 9675, // work + 9678, // worthy + 9716, // wrap + 9747, // wrist + 9748, // writing + 9768, // wrote + 9317, // year + 9356, // yelp + 9435, // yield + 9641, // yoga + 9376, // zero +}; diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c index 310640ced..2170c25da 100644 --- a/crypto/tests/test_check.c +++ b/crypto/tests/test_check.c @@ -67,6 +67,7 @@ #include "sha2.h" #include "sha3.h" #include "shamir.h" +#include "slip39.h" #if VALGRIND /* @@ -5096,6 +5097,96 @@ START_TEST(test_mnemonic_to_entropy) { } END_TEST +START_TEST(test_slip39_get_word) { + static const struct { + const int index; + const char *expected_word; + } vectors[] = {{573, "member"}, + {0, "academic"}, + {1023, "zero"}, + {245, "drove"}, + {781, "satoshi"}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + const char *a = get_word(vectors[i].index); + ck_assert_str_eq(a, vectors[i].expected_word); + } +} +END_TEST + +START_TEST(test_slip39_word_index) { + uint16_t index; + static const struct { + const char *word; + bool expected_result; + uint16_t expected_index; + } vectors[] = {{"academic", true, 0}, + {"zero", true, 1023}, + {"drove", true, 245}, + {"satoshi", true, 781}, + {"member", true, 573}, + // 9999 value is never checked since the word is not in list + {"fakeword", false, 9999}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + bool result = word_index(&index, vectors[i].word, sizeof(vectors[i].word)); + ck_assert_int_eq(result, vectors[i].expected_result); + if (result) { + ck_assert_int_eq(index, vectors[i].expected_index); + } + } +} +END_TEST + +START_TEST(test_slip39_compute_mask) { + static const struct { + const uint16_t prefix; + const uint16_t expected_mask; + } vectors[] = {{ + 12, + 0xFD // 011111101 + }, + { + 21, + 0xF8 // 011111000 + }, + { + 75, + 0xAD // 010101101 + }, + { + 4, + 0x1F7 // 111110111 + }, + { + 738, + 0x6D // 001101101 + }, + { + 9, + 0x6D // 001101101 + }}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + uint16_t mask = compute_mask(vectors[i].prefix); + ck_assert_int_eq(mask, vectors[i].expected_mask); + } +} +END_TEST + +START_TEST(test_slip39_sequence_to_word) { + static const struct { + const uint16_t prefix; + const char *expected_word; + } vectors[] = {{7945, "swimming"}, + {646, "photo"}, + {5, "kernel"}, + {34, "either"}, + {62, "ocean"}}; + for (size_t i = 0; i < (sizeof(vectors) / sizeof(*vectors)); i++) { + const char *word = button_sequence_to_word(vectors[i].prefix); + ck_assert_str_eq(word, vectors[i].expected_word); + } +} +END_TEST + START_TEST(test_shamir) { #define SHAMIR_MAX_COUNT 16 static const struct { @@ -8676,6 +8767,13 @@ Suite *test_suite(void) { tcase_add_test(tc, test_mnemonic_to_entropy); suite_add_tcase(s, tc); + tc = tcase_create("slip39"); + tcase_add_test(tc, test_slip39_get_word); + tcase_add_test(tc, test_slip39_word_index); + tcase_add_test(tc, test_slip39_compute_mask); + tcase_add_test(tc, test_slip39_sequence_to_word); + suite_add_tcase(s, tc); + tc = tcase_create("shamir"); tcase_add_test(tc, test_shamir); suite_add_tcase(s, tc); From 80f8f7900db9dcfbb7a226f4b930cceb3551f806 Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Sun, 23 Jun 2019 12:18:59 +0200 Subject: [PATCH 4/5] core: add slip39 support to reset and recovery device --- core/mocks/generated/trezorcrypto/slip39.py | 35 ++ core/src/apps/cardano/seed.py | 8 +- core/src/apps/common/confirm.py | 12 +- core/src/apps/common/mnemonic.py | 56 --- core/src/apps/common/mnemonic/__init__.py | 59 +++ core/src/apps/common/mnemonic/bip39.py | 36 ++ core/src/apps/common/mnemonic/slip39.py | 87 ++++ core/src/apps/common/storage.py | 122 ++++- core/src/apps/homescreen/homescreen.py | 42 +- core/src/apps/management/backup_device.py | 24 +- core/src/apps/management/common/layout.py | 519 ++++++++++++++++++++ core/src/apps/management/load_device.py | 10 +- core/src/apps/management/recovery_device.py | 154 +++--- core/src/apps/management/reset_device.py | 243 ++++----- core/src/trezor/crypto/slip39.py | 161 +++--- core/src/trezor/crypto/slip39_wordlist.py | 1 - core/src/trezor/messages/ResetDevice.py | 3 + core/src/trezor/ui/checklist.py | 55 +++ core/src/trezor/ui/confirm.py | 5 + core/src/trezor/ui/info.py | 63 +++ core/src/trezor/ui/loader.py | 20 + core/src/trezor/ui/shamir.py | 49 ++ core/src/trezor/ui/text.py | 78 ++- core/src/trezor/ui/word_select.py | 20 +- core/tests/test_trezor.crypto.slip39.py | 42 +- python/trezorlib/coins.json | 2 +- python/trezorlib/messages/ResetDevice.py | 3 + 27 files changed, 1448 insertions(+), 461 deletions(-) create mode 100644 core/mocks/generated/trezorcrypto/slip39.py delete mode 100644 core/src/apps/common/mnemonic.py create mode 100644 core/src/apps/common/mnemonic/__init__.py create mode 100644 core/src/apps/common/mnemonic/bip39.py create mode 100644 core/src/apps/common/mnemonic/slip39.py create mode 100644 core/src/apps/management/common/layout.py delete mode 100644 core/src/trezor/crypto/slip39_wordlist.py create mode 100644 core/src/trezor/ui/checklist.py create mode 100644 core/src/trezor/ui/info.py create mode 100644 core/src/trezor/ui/shamir.py diff --git a/core/mocks/generated/trezorcrypto/slip39.py b/core/mocks/generated/trezorcrypto/slip39.py new file mode 100644 index 000000000..056be93f5 --- /dev/null +++ b/core/mocks/generated/trezorcrypto/slip39.py @@ -0,0 +1,35 @@ +from typing import * + + +# extmod/modtrezorcrypto/modtrezorcrypto-slip39.h +def compute_mask(prefix: int) -> int: + """ + Calculates which buttons still can be pressed after some already were. + Returns a 9-bit bitmask, where each bit specifies which buttons + can be further pressed (there are still words in this combination). + LSB denotes first button. + Example: 110000110 - second, third, eighth and ninth button still can be + pressed. + """ + + +# extmod/modtrezorcrypto/modtrezorcrypto-slip39.h +def button_sequence_to_word(prefix: int) -> str: + """ + Finds the first word that fits the given button prefix. + """ + + +# extmod/modtrezorcrypto/modtrezorcrypto-slip39.h +def word_index(word: str) -> int: + """ + Finds index of given word. + Raises ValueError if not found. + """ + + +# extmod/modtrezorcrypto/modtrezorcrypto-slip39.h +def get_word(index: int) -> str: + """ + Returns word on position 'index'. + """ diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index f8efa24b4..d914b5312 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -37,7 +37,13 @@ async def get_keychain(ctx: wire.Context) -> Keychain: if passphrase is None: passphrase = await protect_by_passphrase(ctx) cache.set_passphrase(passphrase) - root = bip32.from_mnemonic_cardano(mnemonic.restore(), passphrase) + # TODO fix for SLIP-39! + mnemonic_secret, mnemonic_type = mnemonic.get() + if mnemonic_type == mnemonic.TYPE_SLIP39: + # TODO: we need to modify bip32.from_mnemonic_cardano to accept entropy directly + raise NotImplementedError("SLIP-39 currently does not support Cardano") + else: + root = bip32.from_mnemonic_cardano(mnemonic_secret.decode(), passphrase) # derive the namespaced root node for i in SEED_NAMESPACE: diff --git a/core/src/apps/common/confirm.py b/core/src/apps/common/confirm.py index 9f3b5e50c..280f3a0e1 100644 --- a/core/src/apps/common/confirm.py +++ b/core/src/apps/common/confirm.py @@ -15,16 +15,24 @@ async def confirm( confirm_style=Confirm.DEFAULT_CONFIRM_STYLE, cancel=Confirm.DEFAULT_CANCEL, cancel_style=Confirm.DEFAULT_CANCEL_STYLE, + major_confirm=None, ): await ctx.call(ButtonRequest(code=code), MessageType.ButtonAck) if content.__class__.__name__ == "Paginated": content.pages[-1] = Confirm( - content.pages[-1], confirm, confirm_style, cancel, cancel_style + content.pages[-1], + confirm, + confirm_style, + cancel, + cancel_style, + major_confirm, ) dialog = content else: - dialog = Confirm(content, confirm, confirm_style, cancel, cancel_style) + dialog = Confirm( + content, confirm, confirm_style, cancel, cancel_style, major_confirm + ) if __debug__: return await ctx.wait(dialog, confirm_signal) is CONFIRMED diff --git a/core/src/apps/common/mnemonic.py b/core/src/apps/common/mnemonic.py deleted file mode 100644 index 22f60f62b..000000000 --- a/core/src/apps/common/mnemonic.py +++ /dev/null @@ -1,56 +0,0 @@ -from trezor import ui, workflow -from trezor.crypto import bip39 - -from apps.common import storage - -TYPE_BIP39 = 0 - - -def get() -> (bytes, int): - mnemonic_secret = storage.get_mnemonic_secret() - mnemonic_type = storage.get_mnemonic_type() or TYPE_BIP39 - return mnemonic_secret, mnemonic_type - - -def get_seed(passphrase: str = "", progress_bar=True): - secret, mnemonic_type = get() - if mnemonic_type == TYPE_BIP39: - module = bip39 - if progress_bar: - _start_progress() - result = module.seed(secret.decode(), passphrase, _render_progress) - _stop_progress() - else: - result = module.seed(secret.decode(), passphrase) - return result - - -def process(mnemonics: list, mnemonic_type: int): - if mnemonic_type == TYPE_BIP39: - return mnemonics[0].encode() - else: - raise RuntimeError("Unknown mnemonic type") - - -def restore() -> str: - secret, mnemonic_type = get() - if mnemonic_type == TYPE_BIP39: - return secret.decode() - - -def _start_progress(): - ui.backlight_fade(ui.BACKLIGHT_DIM) - ui.display.clear() - ui.header("Please wait") - ui.display.refresh() - ui.backlight_fade(ui.BACKLIGHT_NORMAL) - - -def _render_progress(progress: int, total: int): - p = 1000 * progress // total - ui.display.loader(p, False, 18, ui.WHITE, ui.BG) - ui.display.refresh() - - -def _stop_progress(): - workflow.restartdefault() diff --git a/core/src/apps/common/mnemonic/__init__.py b/core/src/apps/common/mnemonic/__init__.py new file mode 100644 index 000000000..b729bb3a3 --- /dev/null +++ b/core/src/apps/common/mnemonic/__init__.py @@ -0,0 +1,59 @@ +from micropython import const + +from trezor import ui, wire +from trezor.crypto.hashlib import sha256 +from trezor.messages.Success import Success +from trezor.utils import consteq + +from . import bip39, slip39 + +from apps.common import storage + +TYPE_BIP39 = const(0) +TYPE_SLIP39 = const(1) + +TYPES_WORD_COUNT = {12: bip39, 18: bip39, 24: bip39, 20: slip39, 33: slip39} + + +def get() -> (bytes, int): + mnemonic_secret = storage.get_mnemonic_secret() + mnemonic_type = storage.get_mnemonic_type() or TYPE_BIP39 + return mnemonic_secret, mnemonic_type + + +def get_seed(passphrase: str = ""): + mnemonic_secret, mnemonic_type = get() + if mnemonic_type == TYPE_BIP39: + return bip39.get_seed(mnemonic_secret, passphrase) + elif mnemonic_type == TYPE_SLIP39: + return slip39.get_seed(mnemonic_secret, passphrase) + + +def dry_run(secret: bytes): + digest_input = sha256(secret).digest() + stored, _ = get() + digest_stored = sha256(stored).digest() + if consteq(digest_stored, digest_input): + return Success(message="The seed is valid and matches the one in the device") + else: + raise wire.ProcessError( + "The seed is valid but does not match the one in the device" + ) + + +def module_from_words_count(count: int): + return TYPES_WORD_COUNT[count] + + +def _start_progress(): + ui.backlight_fade(ui.BACKLIGHT_DIM) + ui.display.clear() + ui.header("Please wait") + ui.display.refresh() + ui.backlight_fade(ui.BACKLIGHT_NORMAL) + + +def _render_progress(progress: int, total: int): + p = 1000 * progress // total + ui.display.loader(p, False, 18, ui.WHITE, ui.BG) + ui.display.refresh() diff --git a/core/src/apps/common/mnemonic/bip39.py b/core/src/apps/common/mnemonic/bip39.py new file mode 100644 index 000000000..81ac42967 --- /dev/null +++ b/core/src/apps/common/mnemonic/bip39.py @@ -0,0 +1,36 @@ +from trezor.crypto import bip39 + +from apps.common import mnemonic, storage + + +def get_type(): + return mnemonic.TYPE_BIP39 + + +def process_single(mnemonic: str) -> bytes: + """ + Receives single mnemonic and processes it. Returns what is then stored in storage or + None if more shares are needed. + """ + return mnemonic.encode() + + +def process_all(mnemonics: list) -> bytes: + """ + Receives all mnemonics (just one in case of BIP-39) and processes it into a master + secret which is usually then stored in storage. + """ + return mnemonics[0].encode() + + +def store(secret: bytes, needs_backup: bool, no_backup: bool): + storage.store_mnemonic(secret, mnemonic.TYPE_BIP39, needs_backup, no_backup) + + +def get_seed(secret: bytes, passphrase: str): + mnemonic._start_progress() + return bip39.seed(secret.decode(), passphrase, mnemonic._render_progress) + + +def check(secret: bytes): + return bip39.check(secret) diff --git a/core/src/apps/common/mnemonic/slip39.py b/core/src/apps/common/mnemonic/slip39.py new file mode 100644 index 000000000..040f79aef --- /dev/null +++ b/core/src/apps/common/mnemonic/slip39.py @@ -0,0 +1,87 @@ +from trezor.crypto import slip39 + +from apps.common import mnemonic, storage + + +def generate_from_secret(master_secret: bytes, count: int, threshold: int) -> str: + """ + Generates new Shamir backup for 'master_secret'. Multiple groups are not yet supported. + """ + identifier, group_mnemonics = slip39.generate_single_group_mnemonics_from_data( + master_secret, threshold, count + ) + storage.set_slip39_iteration_exponent(slip39.DEFAULT_ITERATION_EXPONENT) + storage.set_slip39_identifier(identifier) + return group_mnemonics + + +def get_type(): + return mnemonic.TYPE_SLIP39 + + +def process_single(mnemonic: str) -> bytes: + """ + Receives single mnemonic and processes it. Returns what is then stored in storage or + None if more shares are needed. + """ + identifier, iteration_exponent, _, _, _, index, threshold, value = slip39.decode_mnemonic( + mnemonic + ) # TODO: use better data structure for this + if threshold == 1: + raise ValueError("Threshold equal to 1 is not allowed.") + + # if recovery is not in progress already, start it and wait for more mnemonics + if not storage.is_slip39_in_progress(): + storage.set_slip39_in_progress(True) + storage.set_slip39_iteration_exponent(iteration_exponent) + storage.set_slip39_identifier(identifier) + storage.set_slip39_threshold(threshold) + storage.set_slip39_remaining(threshold - 1) + storage.set_slip39_words_count(len(mnemonic.split())) + storage.set_slip39_mnemonic(index, mnemonic) + return None # we need more shares + + # check identifier and member index of this share against stored values + if identifier != storage.get_slip39_identifier(): + # TODO: improve UX (tell user) + raise ValueError("Share identifiers do not match") + if storage.get_slip39_mnemonic(index): + # TODO: improve UX (tell user) + raise ValueError("This mnemonic was already entered") + + # append to storage + remaining = storage.get_slip39_remaining() - 1 + storage.set_slip39_remaining(remaining) + storage.set_slip39_mnemonic(index, mnemonic) + if remaining != 0: + return None # we need more shares + + # combine shares and return the master secret + mnemonics = storage.get_slip39_mnemonics() + if len(mnemonics) != threshold: + raise ValueError("Some mnemonics are still missing.") + _, _, secret = slip39.combine_mnemonics(mnemonics) + return secret + + +def process_all(mnemonics: list) -> bytes: + """ + Receives all mnemonics and processes it into pre-master secret which is usually then + stored in the storage. + """ + _, _, secret = slip39.combine_mnemonics(mnemonics) + return secret + + +def store(secret: bytes, needs_backup: bool, no_backup: bool): + storage.store_mnemonic(secret, mnemonic.TYPE_SLIP39, needs_backup, no_backup) + storage.clear_slip39_data() + + +def get_seed(encrypted_master_secret: bytes, passphrase: str): + mnemonic._start_progress() + identifier = storage.get_slip39_identifier() + iteration_exponent = storage.get_slip39_iteration_exponent() + return slip39.decrypt( + identifier, iteration_exponent, encrypted_master_secret, passphrase + ) diff --git a/core/src/apps/common/storage.py b/core/src/apps/common/storage.py index ec5e55173..0974c67b1 100644 --- a/core/src/apps/common/storage.py +++ b/core/src/apps/common/storage.py @@ -2,7 +2,7 @@ from micropython import const from ubinascii import hexlify from trezor import config -from trezor.crypto import random +from trezor.crypto import random, slip39 from apps.common import cache @@ -32,9 +32,100 @@ _AUTOLOCK_DELAY_MS = const(0x0C) # int _NO_BACKUP = const(0x0D) # bool (0x01 or empty) _MNEMONIC_TYPE = const(0x0E) # int _ROTATION = const(0x0F) # int + +_SLIP39 = const(0x02) # SLIP-39 namespace +_SLIP39_IN_PROGRESS = const(0x00) # bool +_SLIP39_IDENTIFIER = const(0x01) # bytes +_SLIP39_THRESHOLD = const(0x02) # int +_SLIP39_REMAINING = const(0x03) # int +_SLIP39_WORDS_COUNT = const(0x04) # int +_SLIP39_ITERATION_EXPONENT = const(0x05) # int + +# Mnemonics stored during SLIP-39 recovery process. +# Each mnemonic is stored under key = index. +_SLIP39_MNEMONICS = const(0x03) # SLIP-39 mnemonics namespace + # fmt: on +def set_slip39_in_progress(val: bool): + _set_bool(_SLIP39, _SLIP39_IN_PROGRESS, val) + + +def is_slip39_in_progress(): + return _get_bool(_SLIP39, _SLIP39_IN_PROGRESS) + + +def set_slip39_identifier(identifier: int): + _set_uint16(_SLIP39, _SLIP39_IDENTIFIER, identifier) + + +def get_slip39_identifier() -> int: + return _get_uint16(_SLIP39, _SLIP39_IDENTIFIER) + + +def set_slip39_threshold(threshold: int): + _set_uint8(_SLIP39, _SLIP39_THRESHOLD, threshold) + + +def get_slip39_threshold() -> int: + return _get_uint8(_SLIP39, _SLIP39_THRESHOLD) + + +def set_slip39_remaining(remaining: int): + _set_uint8(_SLIP39, _SLIP39_REMAINING, remaining) + + +def get_slip39_remaining() -> int: + return _get_uint8(_SLIP39, _SLIP39_REMAINING) + + +def set_slip39_words_count(count: int): + _set_uint8(_SLIP39, _SLIP39_WORDS_COUNT, count) + + +def get_slip39_words_count() -> int: + return _get_uint8(_SLIP39, _SLIP39_WORDS_COUNT) + + +def set_slip39_iteration_exponent(exponent: int): + # TODO: check if not > 5 bits + _set_uint8(_SLIP39, _SLIP39_ITERATION_EXPONENT, exponent) + + +def get_slip39_iteration_exponent() -> int: + return _get_uint8(_SLIP39, _SLIP39_ITERATION_EXPONENT) + + +def set_slip39_mnemonic(index: int, mnemonic: str): + config.set(_SLIP39_MNEMONICS, index, mnemonic.encode()) + + +def get_slip39_mnemonic(index: int) -> str: + m = config.get(_SLIP39_MNEMONICS, index) + if m: + return m.decode() + return False + + +def get_slip39_mnemonics() -> list: + mnemonics = [] + for index in range(0, slip39.MAX_SHARE_COUNT): + m = get_slip39_mnemonic(index) + if m: + mnemonics.append(m) + return mnemonics + + +def clear_slip39_data(): + config.delete(_SLIP39, _SLIP39_IN_PROGRESS) + config.delete(_SLIP39, _SLIP39_REMAINING) + config.delete(_SLIP39, _SLIP39_THRESHOLD) + config.delete(_SLIP39, _SLIP39_WORDS_COUNT) + for index in (0, slip39.MAX_SHARE_COUNT): + config.delete(_SLIP39_MNEMONICS, index) + + def _set_bool(app: int, key: int, value: bool, public: bool = False) -> None: if value: config.set(app, key, _TRUE_BYTE, public) @@ -57,6 +148,17 @@ def _get_uint8(app: int, key: int) -> int: return int.from_bytes(val, "big") +def _set_uint16(app: int, key: int, val: int): + config.set(app, key, val.to_bytes(2, "big")) + + +def _get_uint16(app: int, key: int) -> int: + val = config.get(app, key) + if not val: + return None + return int.from_bytes(val, "big") + + def _new_device_id() -> str: return hexlify(random.bytes(12)).decode().upper() @@ -77,7 +179,9 @@ def get_rotation() -> int: def is_initialized() -> bool: - return bool(config.get(_APP, _VERSION)) + return bool(config.get(_APP, _VERSION)) and not bool( + config.get(_SLIP39, _SLIP39_IN_PROGRESS) + ) def get_label() -> str: @@ -88,10 +192,7 @@ def get_label() -> str: def get_mnemonic_secret() -> bytes: - mnemonic = config.get(_APP, _MNEMONIC_SECRET) - if mnemonic is None: - return None - return mnemonic + return config.get(_APP, _MNEMONIC_SECRET) def get_mnemonic_type() -> int: @@ -107,10 +208,17 @@ def get_homescreen() -> bytes: def store_mnemonic( - secret: bytes, mnemonic_type: int, needs_backup: bool, no_backup: bool + secret: bytes, + mnemonic_type: int, + needs_backup: bool = False, + no_backup: bool = False, ) -> None: config.set(_APP, _MNEMONIC_SECRET, secret) _set_uint8(_APP, _MNEMONIC_TYPE, mnemonic_type) + _init(needs_backup, no_backup) + + +def _init(needs_backup=False, no_backup=False): config.set(_APP, _VERSION, _STORAGE_VERSION) _set_bool(_APP, _NO_BACKUP, no_backup) if not no_backup: diff --git a/core/src/apps/homescreen/homescreen.py b/core/src/apps/homescreen/homescreen.py index a89af2e91..4a229063a 100644 --- a/core/src/apps/homescreen/homescreen.py +++ b/core/src/apps/homescreen/homescreen.py @@ -16,9 +16,11 @@ async def homescreen(): def display_homescreen(): - if not storage.is_initialized(): + image = None + if storage.is_slip39_in_progress(): + label = "Waiting for other shares" + elif not storage.is_initialized(): label = "Go to trezor.io/start" - image = None else: label = storage.get_label() or "My Trezor" image = storage.get_homescreen() @@ -27,28 +29,28 @@ def display_homescreen(): image = res.load("apps/homescreen/res/bg.toif") if storage.is_initialized() and storage.no_backup(): - ui.display.bar(0, 0, ui.WIDTH, 30, ui.RED) - ui.display.text_center(ui.WIDTH // 2, 22, "SEEDLESS", ui.BOLD, ui.WHITE, ui.RED) - ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) + _err("SEEDLESS") elif storage.is_initialized() and storage.unfinished_backup(): - ui.display.bar(0, 0, ui.WIDTH, 30, ui.RED) - ui.display.text_center( - ui.WIDTH // 2, 22, "BACKUP FAILED!", ui.BOLD, ui.WHITE, ui.RED - ) - ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) + _err("BACKUP FAILED!") elif storage.is_initialized() and storage.needs_backup(): - ui.display.bar(0, 0, ui.WIDTH, 30, ui.YELLOW) - ui.display.text_center( - ui.WIDTH // 2, 22, "NEEDS BACKUP!", ui.BOLD, ui.BLACK, ui.YELLOW - ) - ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) + _warn("NEEDS BACKUP!") elif storage.is_initialized() and not config.has_pin(): - ui.display.bar(0, 0, ui.WIDTH, 30, ui.YELLOW) - ui.display.text_center( - ui.WIDTH // 2, 22, "PIN NOT SET!", ui.BOLD, ui.BLACK, ui.YELLOW - ) - ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) + _warn("PIN NOT SET!") + elif storage.is_slip39_in_progress(): + _warn("SHAMIR IN PROGRESS!") else: ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT, ui.BG) ui.display.avatar(48, 48 - 10, image, ui.WHITE, ui.BLACK) ui.display.text_center(ui.WIDTH // 2, 220, label, ui.BOLD, ui.FG, ui.BG) + + +def _warn(message: str): + ui.display.bar(0, 0, ui.WIDTH, 30, ui.YELLOW) + ui.display.text_center(ui.WIDTH // 2, 22, message, ui.BOLD, ui.BLACK, ui.YELLOW) + ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) + + +def _err(message: str): + ui.display.bar(0, 0, ui.WIDTH, 30, ui.RED) + ui.display.text_center(ui.WIDTH // 2, 22, message, ui.BOLD, ui.WHITE, ui.RED) + ui.display.bar(0, 30, ui.WIDTH, ui.HEIGHT - 30, ui.BG) diff --git a/core/src/apps/management/backup_device.py b/core/src/apps/management/backup_device.py index 15fec3942..2e6579e38 100644 --- a/core/src/apps/management/backup_device.py +++ b/core/src/apps/management/backup_device.py @@ -2,12 +2,8 @@ from trezor import wire from trezor.messages.Success import Success from apps.common import mnemonic, storage -from apps.management.reset_device import ( - check_mnemonic, - show_backup_warning, - show_mnemonic, - show_wrong_entry, -) +from apps.management.common import layout +from apps.management.reset_device import backup_slip39_wallet async def backup_device(ctx, msg): @@ -16,20 +12,18 @@ async def backup_device(ctx, msg): if not storage.needs_backup(): raise wire.ProcessError("Seed already backed up") - words = mnemonic.restore() - # warn user about mnemonic safety - await show_backup_warning(ctx) + await layout.bip39_show_backup_warning(ctx) storage.set_unfinished_backup(True) storage.set_backed_up() - while True: - # show mnemonic and require confirmation of a random word - await show_mnemonic(ctx, words) - if await check_mnemonic(ctx, words): - break - await show_wrong_entry(ctx) + mnemonic_secret, mnemonic_type = mnemonic.get() + if mnemonic_type == mnemonic.TYPE_BIP39: + await layout.bip39_show_and_confirm_mnemonic(ctx, mnemonic_secret.decode()) + + elif mnemonic_type == mnemonic.TYPE_SLIP39: + await backup_slip39_wallet(ctx, mnemonic_secret) storage.set_unfinished_backup(False) diff --git a/core/src/apps/management/common/layout.py b/core/src/apps/management/common/layout.py new file mode 100644 index 000000000..4c348ad31 --- /dev/null +++ b/core/src/apps/management/common/layout.py @@ -0,0 +1,519 @@ +import ubinascii +from micropython import const + +from trezor import ui, utils +from trezor.crypto import random +from trezor.messages import ButtonRequestType +from trezor.ui.button import Button, ButtonDefault +from trezor.ui.checklist import Checklist +from trezor.ui.info import InfoConfirm +from trezor.ui.loader import LoadingAnimation +from trezor.ui.scroll import Paginated +from trezor.ui.shamir import NumInput +from trezor.ui.text import Text + +from apps.common.confirm import confirm, hold_to_confirm, require_confirm + +if __debug__: + from apps import debug + + +async def show_reset_device_warning(ctx, use_slip39: bool): + text = Text("Create new wallet", ui.ICON_RESET, new_lines=False) + text.bold("Do you want to create") + text.br() + if use_slip39: + text.bold("a new Shamir wallet?") + else: + text.bold("a new wallet?") + text.br() + text.br_half() + text.normal("By continuing you agree") + text.br() + text.normal("to") + text.bold("https://trezor.io/tos") + await require_confirm(ctx, text, ButtonRequestType.ResetDevice, major_confirm=True) + await LoadingAnimation() + + +async def show_internal_entropy(ctx, entropy: bytes): + entropy_str = ubinascii.hexlify(entropy).decode() + lines = utils.chunks(entropy_str, 16) + text = Text("Internal entropy", ui.ICON_RESET) + text.mono(*lines) + await require_confirm(ctx, text, ButtonRequestType.ResetDevice) + + +async def show_backup_success(ctx): + text = Text("Backup is done!", ui.ICON_CONFIRM, ui.GREEN) + text.normal( + "Never make a digital", + "copy of your recovery", + "seed and never upload", + "it online!", + ) + await require_confirm( + ctx, text, ButtonRequestType.ResetDevice, confirm="Finish setup", cancel=None + ) + + +async def confirm_backup(ctx): + text = Text("Backup wallet", ui.ICON_RESET, new_lines=False) + text.bold("New wallet created") + text.br() + text.bold("successfully!") + text.br() + text.br_half() + text.normal("You should back your") + text.br() + text.normal("new wallet right now.") + return await confirm( + ctx, + text, + ButtonRequestType.ResetDevice, + cancel="Skip", + confirm="Backup", + major_confirm=True, + ) + + +async def confirm_backup_again(ctx): + text = Text("Backup wallet", ui.ICON_RESET, new_lines=False) + text.bold("Are you sure you want") + text.br() + text.bold("to skip the backup?") + text.br() + text.br_half() + text.normal("You can backup Trezor") + text.br() + text.normal("anytime later.") + return await confirm( + ctx, + text, + ButtonRequestType.ResetDevice, + cancel="Skip", + confirm="Backup", + major_confirm=True, + ) + + +async def _confirm_share_words(ctx, share_index, share_words): + numbered = list(enumerate(share_words)) + + # check a word from the first half + first_half = numbered[: len(numbered) // 2] + if not await _confirm_word(ctx, share_index, first_half): + return False + + # check a word from the second half + second_half = numbered[len(numbered) // 2 :] + if not await _confirm_word(ctx, share_index, second_half): + return False + + return True + + +async def _confirm_word(ctx, share_index, numbered_share_words): + # TODO: duplicated words in the choice list + + # shuffle the numbered seed half, slice off the choices we need + random.shuffle(numbered_share_words) + numbered_choices = numbered_share_words[: MnemonicWordSelect.NUM_OF_CHOICES] + + # we always confirm the first (random) word index + checked_index, checked_word = numbered_choices[0] + if __debug__: + debug.reset_word_index = checked_index + + # shuffle again so the confirmed word is not always the first choice + random.shuffle(numbered_choices) + + # let the user pick a word + choices = [word for _, word in numbered_choices] + select = MnemonicWordSelect(choices, share_index, checked_index) + if __debug__: + selected_word = await ctx.wait(select, debug.input_signal) + else: + selected_word = await ctx.wait(select) + + # confirm it is the correct one + return selected_word == checked_word + + +async def _show_confirmation_success(ctx, share_index): + if share_index is None: + text = Text("Recovery seed", ui.ICON_RESET) + text.bold("Recovery seed") + text.bold("checked successfully.") + else: + text = Text("Recovery share #%s" % (share_index + 1), ui.ICON_RESET) + text.bold("Seed share #%s" % (share_index + 1)) + text.bold("checked successfully.") + text.normal("Let's continue with") + text.normal("share #%s." % (share_index + 2)) + return await confirm( + ctx, text, ButtonRequestType.ResetDevice, cancel=None, confirm="Continue" + ) + + +async def _show_confirmation_failure(ctx, share_index): + if share_index is None: + text = Text("Recovery seed", ui.ICON_WRONG, ui.RED) + else: + text = Text("Recovery share #%s" % (share_index + 1), ui.ICON_WRONG, ui.RED) + text.bold("You have entered") + text.bold("wrong seed word.") + text.bold("Please check again.") + await require_confirm( + ctx, text, ButtonRequestType.ResetDevice, confirm="Check again", cancel=None + ) + + +# BIP39 +# === + + +async def bip39_show_and_confirm_mnemonic(ctx, mnemonic: str): + words = mnemonic.split() + + # require confirmation of the mnemonic safety + await bip39_show_backup_warning(ctx) + + while True: + # display paginated mnemonic on the screen + await _bip39_show_mnemonic(ctx, words) + + # make the user confirm 2 words from the mnemonic + if await _confirm_share_words(ctx, None, words): + await _show_confirmation_success(ctx, None) + break # this share is confirmed, go to next one + else: + await _show_confirmation_failure(ctx, None) + + +async def bip39_show_backup_warning(ctx): + text = Text("Backup your seed", ui.ICON_NOCOPY) + text.normal( + "Never make a digital", + "copy of your recovery", + "seed and never upload", + "it online!", + ) + await require_confirm( + ctx, text, ButtonRequestType.ResetDevice, confirm="I understand", cancel=None + ) + + +async def _bip39_show_mnemonic(ctx, words: list): + # split mnemonic words into pages + PER_PAGE = const(4) + words = list(enumerate(words)) + words = list(utils.chunks(words, PER_PAGE)) + + # display the pages, with a confirmation dialog on the last one + pages = [_get_mnemonic_page(page) for page in words] + paginated = Paginated(pages) + + if __debug__: + + def export_displayed_words(): + # export currently displayed mnemonic words into debuglink + debug.reset_current_words = [w for _, w in words[paginated.page]] + + paginated.on_change = export_displayed_words + export_displayed_words() + + await hold_to_confirm(ctx, paginated, ButtonRequestType.ResetDevice) + + +def _get_mnemonic_page(words: list): + text = Text("Recovery seed", ui.ICON_RESET) + for index, word in words: + text.mono("%2d. %s" % (index + 1, word)) + return text + + +# SLIP39 +# === + +# TODO: yellow cancel style? +# TODO: loading animation style? +# TODO: smaller font or tighter rows to fit more text in +# TODO: icons in checklist + + +async def slip39_show_checklist_set_shares(ctx): + checklist = Checklist("Backup checklist", ui.ICON_RESET) + checklist.add("Set number of shares") + checklist.add("Set the threshold") + checklist.add(("Write down and check", "all seed shares")) + checklist.select(0) + checklist.process() + return await confirm( + ctx, checklist, ButtonRequestType.ResetDevice, cancel=None, confirm="Set shares" + ) + + +async def slip39_show_checklist_set_threshold(ctx, num_of_shares): + checklist = Checklist("Backup checklist", ui.ICON_RESET) + checklist.add("Set number of shares") + checklist.add("Set the threshold") + checklist.add(("Write down and check", "all seed shares")) + checklist.select(1) + checklist.process() + return await confirm( + ctx, + checklist, + ButtonRequestType.ResetDevice, + cancel=None, + confirm="Set threshold", + ) + + +async def slip39_show_checklist_show_shares(ctx, num_of_shares, threshold): + checklist = Checklist("Backup checklist", ui.ICON_RESET) + checklist.add("Set number of shares") + checklist.add("Set the threshold") + checklist.add(("Write down and check", "all seed shares")) + checklist.select(2) + checklist.process() + return await confirm( + ctx, + checklist, + ButtonRequestType.ResetDevice, + cancel=None, + confirm="Show seed shares", + ) + + +async def slip39_prompt_number_of_shares(ctx): + count = 5 + min_count = 2 + max_count = 16 + + while True: + shares = ShamirNumInput(ShamirNumInput.SET_SHARES, count, min_count, max_count) + info = InfoConfirm( + "Shares are parts of " + "the recovery seed, " + "each containing 20 " + "words. You can later set " + "how many shares you " + "need to recover your " + "wallet." + ) + confirmed = await confirm( + ctx, + shares, + ButtonRequestType.ResetDevice, + cancel="Info", + confirm="Set", + major_confirm=True, + cancel_style=ButtonDefault, + ) + count = shares.input.count + if confirmed: + break + else: + await info + + return count + + +async def slip39_prompt_threshold(ctx, num_of_shares): + count = num_of_shares // 2 + min_count = 2 + max_count = num_of_shares + + while True: + shares = ShamirNumInput( + ShamirNumInput.SET_THRESHOLD, count, min_count, max_count + ) + info = InfoConfirm( + "Threshold sets number " + "shares that you need " + "to recover your wallet. " + "i.e. Set it to %s and " + "you'll need any %s shares " + "of the total number." % (count, count) + ) + confirmed = await confirm( + ctx, + shares, + ButtonRequestType.ResetDevice, + cancel="Info", + confirm="Set", + major_confirm=True, + cancel_style=ButtonDefault, + ) + count = shares.input.count + if confirmed: + break + else: + await info + + return count + + +async def slip39_show_and_confirm_shares(ctx, shares): + for index, share in enumerate(shares): + share_words = share.split(" ") + while True: + # display paginated share on the screen + await _slip39_show_share_words(ctx, index, share_words) + + # make the user confirm 2 words from the share + if await _confirm_share_words(ctx, index, share_words): + await _show_confirmation_success(ctx, index) + break # this share is confirmed, go to next one + else: + await _show_confirmation_failure(ctx, index) + + +async def _slip39_show_share_words(ctx, share_index, share_words): + first, chunks, last = _slip39_split_share_into_pages(share_words) + + if share_index is None: + header_title = "Recovery share #%s" % (share_index + 1) + else: + header_title = "Recovery seed" + header_icon = ui.ICON_RESET + pages = [] # ui page components + shares_words_check = [] # check we display correct data + + # first page + text = Text(header_title, header_icon) + text.normal("Write down %s words" % len(share_words)) + text.normal("onto paper booklet:") + text.br_half() + for index, word in first: + text.mono("%s. %s" % (index + 1, word)) + shares_words_check.append(word) + pages.append(text) + + # middle pages + for chunk in chunks: + text = Text(header_title, header_icon) + text.br_half() + for index, word in chunk: + text.mono("%s. %s" % (index + 1, word)) + shares_words_check.append(word) + pages.append(text) + + # last page + text = Text(header_title, header_icon) + for index, word in last: + text.mono("%s. %s" % (index + 1, word)) + shares_words_check.append(word) + text.br_half() + text.normal("I confirm that I wrote") + text.normal("down all %s words." % len(share_words)) + pages.append(text) + + # pagination + paginated = Paginated(pages) + + if __debug__: + + word_pages = [first] + chunks + [last] + + def export_displayed_words(): + # export currently displayed mnemonic words into debuglink + debug.reset_current_words = word_pages[paginated.page] + + paginated.on_change = export_displayed_words + export_displayed_words() + + # make sure we display correct data + utils.ensure(share_words == shares_words_check) + + # confirm the share + await hold_to_confirm(ctx, paginated) # TODO: customize the loader here + + +def _slip39_split_share_into_pages(share_words): + share = list(enumerate(share_words)) # we need to keep track of the word indices + first = share[:2] # two words on the first page + middle = share[2:-2] + last = share[-2:] # two words on the last page + chunks = utils.chunks(middle, 4) # 4 words on the middle pages + return first, list(chunks), last + + +class ShamirNumInput(ui.Control): + SET_SHARES = object() + SET_THRESHOLD = object() + + def __init__(self, step, count, min_count, max_count): + self.step = step + self.input = NumInput(count, min_count=min_count, max_count=max_count) + self.input.on_change = self.on_change + self.repaint = True + + def dispatch(self, event, x, y): + self.input.dispatch(event, x, y) + if event is ui.RENDER: + self.on_render() + + def on_render(self): + if self.repaint: + count = self.input.count + + # render the headline + if self.step is ShamirNumInput.SET_SHARES: + header = "Set num. of shares" + elif self.step is ShamirNumInput.SET_THRESHOLD: + header = "Set the threshold" + ui.header(header, ui.ICON_RESET, ui.TITLE_GREY, ui.BG, ui.ORANGE_ICON) + + # render the counter + if self.step is ShamirNumInput.SET_SHARES: + ui.display.text( + 12, 130, "%s people or locations" % count, ui.BOLD, ui.FG, ui.BG + ) + ui.display.text( + 12, 156, "will each host one share.", ui.NORMAL, ui.FG, ui.BG + ) + elif self.step is ShamirNumInput.SET_THRESHOLD: + ui.display.text( + 12, 130, "For recovery you'll need", ui.NORMAL, ui.FG, ui.BG + ) + ui.display.text( + 12, 156, "any %s of shares." % count, ui.BOLD, ui.FG, ui.BG + ) + + self.repaint = False + + def on_change(self, count): + self.repaint = True + + +class MnemonicWordSelect(ui.Layout): + NUM_OF_CHOICES = 3 + + def __init__(self, words, share_index, word_index): + self.words = words + self.share_index = share_index + self.word_index = word_index + self.buttons = [] + for i, word in enumerate(words): + area = ui.grid(i + 2, n_x=1) + btn = Button(area, word) + btn.on_click = self.select(word) + self.buttons.append(btn) + if share_index is None: + self.text = Text("Recovery seed") + else: + self.text = Text("Recovery share #%s" % (share_index + 1)) + self.text.normal("Choose the %s word:" % utils.format_ordinal(word_index + 1)) + + def dispatch(self, event, x, y): + for btn in self.buttons: + btn.dispatch(event, x, y) + self.text.dispatch(event, x, y) + + def select(self, word): + def fn(): + raise ui.Result(word) + + return fn diff --git a/core/src/apps/management/load_device.py b/core/src/apps/management/load_device.py index a0b1b971a..375674b57 100644 --- a/core/src/apps/management/load_device.py +++ b/core/src/apps/management/load_device.py @@ -1,15 +1,15 @@ from trezor import config, wire -from trezor.crypto import bip39 from trezor.messages.Success import Success from trezor.pin import pin_to_int from trezor.ui.text import Text -from apps.common import mnemonic, storage +from apps.common import storage from apps.common.confirm import require_confirm +from apps.common.mnemonic import bip39 async def load_device(ctx, msg): - + # TODO implement SLIP-39 if storage.is_initialized(): raise wire.UnexpectedMessage("Already initialized") @@ -24,10 +24,10 @@ async def load_device(ctx, msg): text.normal("Continue only if you", "know what you are doing!") await require_confirm(ctx, text) - secret = mnemonic.process([msg.mnemonic], mnemonic.TYPE_BIP39) + secret = bip39.process_all([msg.mnemonic]) storage.store_mnemonic( secret=secret, - mnemonic_type=mnemonic.TYPE_BIP39, + mnemonic_type=bip39.get_type(), needs_backup=True, no_backup=False, ) diff --git a/core/src/apps/management/recovery_device.py b/core/src/apps/management/recovery_device.py index 6b9038d45..ecb1dfff0 100644 --- a/core/src/apps/management/recovery_device.py +++ b/core/src/apps/management/recovery_device.py @@ -1,31 +1,28 @@ from trezor import config, ui, wire -from trezor.crypto import bip39 -from trezor.crypto.hashlib import sha256 +from trezor.messages import ButtonRequestType from trezor.messages.ButtonRequest import ButtonRequest -from trezor.messages.ButtonRequestType import ( - MnemonicInput, - MnemonicWordCount, - ProtectCall, -) from trezor.messages.MessageType import ButtonAck from trezor.messages.Success import Success from trezor.pin import pin_to_int -from trezor.ui.mnemonic import MnemonicKeyboard +from trezor.ui.info import InfoConfirm +from trezor.ui.mnemonic_bip39 import Bip39Keyboard +from trezor.ui.mnemonic_slip39 import Slip39Keyboard from trezor.ui.text import Text from trezor.ui.word_select import WordSelector -from trezor.utils import consteq, format_ordinal +from trezor.utils import format_ordinal from apps.common import mnemonic, storage from apps.common.confirm import require_confirm +from apps.homescreen.homescreen import display_homescreen from apps.management.change_pin import request_pin_ack, request_pin_confirm if __debug__: - from apps.debug import input_signal + from apps.debug import confirm_signal, input_signal async def recovery_device(ctx, msg): """ - Recover BIP39 seed into empty device. + Recover BIP39/SLIP39 seed into empty device. 1. Ask for the number of words in recovered seed. 2. Let user type in the mnemonic words one by one. @@ -36,34 +33,53 @@ async def recovery_device(ctx, msg): if not msg.dry_run and storage.is_initialized(): raise wire.UnexpectedMessage("Already initialized") - if not msg.dry_run: - title = "Device recovery" - text = Text(title, ui.ICON_RECOVERY) - text.normal("Do you really want to", "recover the device?", "") - else: - title = "Simulated recovery" - text = Text(title, ui.ICON_RECOVERY) - text.normal("Do you really want to", "check the recovery", "seed?") - - await require_confirm(ctx, text, code=ProtectCall) - - if msg.dry_run: - if config.has_pin(): - curpin = await request_pin_ack(ctx, "Enter PIN", config.get_pin_rem()) + if not storage.is_slip39_in_progress(): + if not msg.dry_run: + title = "Device recovery" + text = Text(title, ui.ICON_RECOVERY) + text.normal("Do you really want to", "recover the device?", "") else: - curpin = "" - if not config.check_pin(pin_to_int(curpin)): - raise wire.PinInvalid("PIN invalid") - - # ask for the number of words - wordcount = await request_wordcount(ctx, title) - - # ask for mnemonic words one by one - words = await request_mnemonic(ctx, wordcount) + title = "Simulated recovery" + text = Text(title, ui.ICON_RECOVERY) + text.normal("Do you really want to", "check the recovery", "seed?") + await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall) + + if msg.dry_run: + if config.has_pin(): + curpin = await request_pin_ack(ctx, "Enter PIN", config.get_pin_rem()) + else: + curpin = "" + if not config.check_pin(pin_to_int(curpin)): + raise wire.PinInvalid("PIN invalid") + + # ask for the number of words + wordcount = await request_wordcount(ctx, title) + mnemonic_module = mnemonic.module_from_words_count(wordcount) + else: + wordcount = storage.get_slip39_words_count() + mnemonic_module = mnemonic.slip39 + + if mnemonic_module == mnemonic.slip39: + # show a note about the keyboard + await show_keyboard_info(ctx) + + secret = None + while secret is None: + # ask for mnemonic words one by one + words = await request_mnemonic( + ctx, wordcount, mnemonic_module == mnemonic.slip39 + ) + secret = mnemonic_module.process_single(words) + # show a number of remaining mnemonics for SLIP39 + if secret is None and mnemonic_module == mnemonic.slip39: + await show_remaining_slip39_mnemonics( + ctx, title, storage.get_slip39_remaining() + ) # check mnemonic validity - if msg.enforce_wordlist or msg.dry_run: - if not bip39.check(words): + # it is checked automatically in SLIP-39 + if mnemonic_module == mnemonic.bip39 and (msg.enforce_wordlist or msg.dry_run): + if not mnemonic_module.check(secret): raise wire.ProcessError("Mnemonic is not valid") # ask for pin repeatedly @@ -72,39 +88,24 @@ async def recovery_device(ctx, msg): else: newpin = "" - secret = mnemonic.process([words], mnemonic.TYPE_BIP39) - # dry run if msg.dry_run: - digest_input = sha256(secret).digest() - stored, _ = mnemonic.get() - digest_stored = sha256(stored).digest() - if consteq(digest_stored, digest_input): - return Success( - message="The seed is valid and matches the one in the device" - ) - else: - raise wire.ProcessError( - "The seed is valid but does not match the one in the device" - ) + mnemonic.dry_run(secret) # save into storage - if newpin: + if msg.pin_protection: config.change_pin(pin_to_int(""), pin_to_int(newpin)) storage.set_u2f_counter(msg.u2f_counter) storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection) - storage.store_mnemonic( - secret=secret, - mnemonic_type=mnemonic.TYPE_BIP39, - needs_backup=False, - no_backup=False, - ) + mnemonic_module.store(secret=secret, needs_backup=False, no_backup=False) + + display_homescreen() return Success(message="Device recovered") async def request_wordcount(ctx, title: str) -> int: - await ctx.call(ButtonRequest(code=MnemonicWordCount), ButtonAck) + await ctx.call(ButtonRequest(code=ButtonRequestType.MnemonicWordCount), ButtonAck) text = Text(title, ui.ICON_RECOVERY) text.normal("Number of words?") @@ -118,12 +119,15 @@ async def request_wordcount(ctx, title: str) -> int: return count -async def request_mnemonic(ctx, count: int) -> str: - await ctx.call(ButtonRequest(code=MnemonicInput), ButtonAck) +async def request_mnemonic(ctx, count: int, slip39: bool) -> str: + await ctx.call(ButtonRequest(code=ButtonRequestType.MnemonicInput), ButtonAck) words = [] for i in range(count): - keyboard = MnemonicKeyboard("Type the %s word:" % format_ordinal(i + 1)) + if slip39: + keyboard = Slip39Keyboard("Type the %s word:" % format_ordinal(i + 1)) + else: + keyboard = Bip39Keyboard("Type the %s word:" % format_ordinal(i + 1)) if __debug__: word = await ctx.wait(keyboard, input_signal) else: @@ -131,3 +135,33 @@ async def request_mnemonic(ctx, count: int) -> str: words.append(word) return " ".join(words) + + +async def show_keyboard_info(ctx): + await ctx.call(ButtonRequest(code=ButtonRequestType.Other), ButtonAck) + + info = InfoConfirm( + "One more thing. " + "You can type the letters " + "the old-fashioned way " + "one by one or use our " + "T9 keyboard and press " + "buttons only once." + ) + if __debug__: + await ctx.wait(info, confirm_signal) + else: + await ctx.wait(info) + + +async def show_remaining_slip39_mnemonics(ctx, title, remaining: int): + text = Text(title, ui.ICON_RECOVERY) + text.bold("Good job!") + if remaining > 1: + text.normal("%s more shares" % remaining) + else: + text.normal("%s more share" % remaining) + text.normal("needed to enter.") + await require_confirm( + ctx, text, ButtonRequestType.ProtectCall, cancel=None, confirm="Continue" + ) diff --git a/core/src/apps/management/reset_device.py b/core/src/apps/management/reset_device.py index 25fab76b3..a72b338d4 100644 --- a/core/src/apps/management/reset_device.py +++ b/core/src/apps/management/reset_device.py @@ -1,20 +1,13 @@ -from micropython import const -from ubinascii import hexlify - -from trezor import config, ui, wire +from trezor import config, wire from trezor.crypto import bip39, hashlib, random -from trezor.messages import ButtonRequestType, MessageType +from trezor.messages import MessageType from trezor.messages.EntropyRequest import EntropyRequest from trezor.messages.Success import Success from trezor.pin import pin_to_int -from trezor.ui.mnemonic import MnemonicKeyboard -from trezor.ui.scroll import Paginated -from trezor.ui.text import Text -from trezor.utils import chunks, format_ordinal from apps.common import mnemonic, storage -from apps.common.confirm import hold_to_confirm, require_confirm from apps.management.change_pin import request_pin_confirm +from apps.management.common import layout if __debug__: from apps import debug @@ -22,15 +15,10 @@ if __debug__: async def reset_device(ctx, msg): # validate parameters and device state - if msg.strength not in (128, 192, 256): - raise wire.ProcessError("Invalid strength (has to be 128, 192 or 256 bits)") - if msg.display_random and (msg.skip_backup or msg.no_backup): - raise wire.ProcessError("Can't show internal entropy when backup is skipped") - if storage.is_initialized(): - raise wire.UnexpectedMessage("Already initialized") + _validate_reset_device(msg) - # make sure use knows he's setting up a new wallet - await show_reset_warning(ctx) + # make sure user knows he's setting up a new wallet + await layout.show_reset_device_warning(ctx, msg.slip39) # request new PIN if msg.pin_protection: @@ -39,165 +27,100 @@ async def reset_device(ctx, msg): newpin = "" # generate and display internal entropy - internal_ent = random.bytes(32) + int_entropy = random.bytes(32) if __debug__: - debug.reset_internal_entropy = internal_ent + debug.reset_internal_entropy = int_entropy if msg.display_random: - await show_entropy(ctx, internal_ent) - - # request external entropy and compute mnemonic - ent_ack = await ctx.call(EntropyRequest(), MessageType.EntropyAck) - words = generate_mnemonic(msg.strength, internal_ent, ent_ack.entropy) - - if not msg.skip_backup and not msg.no_backup: - # require confirmation of the mnemonic safety - await show_backup_warning(ctx) - - # show mnemonic and require confirmation of a random word - while True: - await show_mnemonic(ctx, words) - if await check_mnemonic(ctx, words): - break - await show_wrong_entry(ctx) + await layout.show_internal_entropy(ctx, int_entropy) + + # request external entropy and compute the master secret + entropy_ack = await ctx.call(EntropyRequest(), MessageType.EntropyAck) + ext_entropy = entropy_ack.entropy + secret = _compute_secret_from_entropy(int_entropy, ext_entropy, msg.strength) + + # should we back up the wallet now? + if not msg.no_backup and not msg.skip_backup: + if not await layout.confirm_backup(ctx): + if not await layout.confirm_backup_again(ctx): + msg.skip_backup = True + + # generate and display backup information for the master secret + if not msg.no_backup and not msg.skip_backup: + if msg.slip39: + await backup_slip39_wallet(ctx, secret) + else: + await backup_bip39_wallet(ctx, secret) # write PIN into storage - if newpin: - if not config.change_pin(pin_to_int(""), pin_to_int(newpin)): - raise wire.ProcessError("Could not change PIN") + if not config.change_pin(pin_to_int(""), pin_to_int(newpin)): + raise wire.ProcessError("Could not change PIN") - secret = mnemonic.process([words], mnemonic.TYPE_BIP39) - # write settings and mnemonic into storage + # write settings and master secret into storage storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection) - storage.store_mnemonic( - secret=secret, - mnemonic_type=mnemonic.TYPE_BIP39, - needs_backup=msg.skip_backup, - no_backup=msg.no_backup, - ) + if msg.slip39: + mnemonic.slip39.store( + secret=secret, needs_backup=msg.skip_backup, no_backup=msg.no_backup + ) + else: + # in BIP-39 we store mnemonic string instead of the secret + mnemonic.bip39.store( + secret=bip39.from_data(secret).encode(), + needs_backup=msg.skip_backup, + no_backup=msg.no_backup, + ) - # show success message - if not msg.skip_backup and not msg.no_backup: - await show_success(ctx) + # if we backed up the wallet, show success message + if not msg.no_backup and not msg.skip_backup: + await layout.show_backup_success(ctx) return Success(message="Initialized") -def generate_mnemonic(strength: int, int_entropy: bytes, ext_entropy: bytes) -> bytes: - ehash = hashlib.sha256() - ehash.update(int_entropy) - ehash.update(ext_entropy) - entropy = ehash.digest() - return bip39.from_data(entropy[: strength // 8]) - - -async def show_reset_warning(ctx): - text = Text("Create a new wallet", ui.ICON_RESET, new_lines=False) - text.normal("Do you really want to") - text.br() - text.normal("create a new wallet?") - text.br() - text.br_half() - text.normal("By continuing you agree") - text.br() - text.normal("to") - text.bold("https://trezor.io/tos") - await require_confirm(ctx, text, code=ButtonRequestType.ResetDevice) - - -async def show_backup_warning(ctx): - text = Text("Backup your seed", ui.ICON_NOCOPY) - text.normal( - "Never make a digital", - "copy of your recovery", - "seed and never upload", - "it online!", - ) - await require_confirm( - ctx, text, ButtonRequestType.ResetDevice, confirm="I understand", cancel=None - ) - - -async def show_wrong_entry(ctx): - text = Text("Wrong entry!", ui.ICON_WRONG, ui.RED) - text.normal("You have entered", "wrong seed word.", "Please check again.") - await require_confirm( - ctx, text, ButtonRequestType.ResetDevice, confirm="Check again", cancel=None - ) - - -async def show_success(ctx): - text = Text("Backup is done!", ui.ICON_CONFIRM, ui.GREEN) - text.normal( - "Never make a digital", - "copy of your recovery", - "seed and never upload", - "it online!", - ) - await require_confirm( - ctx, text, ButtonRequestType.ResetDevice, confirm="Finish setup", cancel=None - ) - - -async def show_entropy(ctx, entropy: bytes): - entropy_str = hexlify(entropy).decode() - lines = chunks(entropy_str, 16) - text = Text("Internal entropy", ui.ICON_RESET) - text.mono(*lines) - await require_confirm(ctx, text, ButtonRequestType.ResetDevice) - - -async def show_mnemonic(ctx, mnemonic: str): - # split mnemonic words into pages - PER_PAGE = const(4) - words = mnemonic.split() - words = list(enumerate(words)) - words = list(chunks(words, PER_PAGE)) - - # display the pages, with a confirmation dialog on the last one - pages = [get_mnemonic_page(page) for page in words] - paginated = Paginated(pages) - - if __debug__: +async def backup_slip39_wallet(ctx, secret: bytes): + # get number of shares + await layout.slip39_show_checklist_set_shares(ctx) + shares_count = await layout.slip39_prompt_number_of_shares(ctx) - def export_displayed_words(): - # export currently displayed mnemonic words into debuglink - debug.reset_current_words = [w for _, w in words[paginated.page]] + # get threshold + await layout.slip39_show_checklist_set_threshold(ctx, shares_count) + threshold = await layout.slip39_prompt_threshold(ctx, shares_count) - paginated.on_change = export_displayed_words - export_displayed_words() + # generate the mnemonics + mnemonics = mnemonic.slip39.generate_from_secret(secret, shares_count, threshold) - await hold_to_confirm(ctx, paginated, ButtonRequestType.ResetDevice) + # show and confirm individual shares + await layout.slip39_show_checklist_show_shares(ctx, shares_count, threshold) + await layout.slip39_show_and_confirm_shares(ctx, mnemonics) -def get_mnemonic_page(words: list): - text = Text("Recovery seed", ui.ICON_RESET) - for index, word in words: - text.mono("%2d. %s" % (index + 1, word)) - return text +async def backup_bip39_wallet(ctx, secret: bytes): + mnemonic = bip39.from_data(secret) + await layout.bip39_show_and_confirm_mnemonic(ctx, mnemonic) -async def check_mnemonic(ctx, mnemonic: str) -> bool: - words = mnemonic.split() - - # check a word from the first half - index = random.uniform(len(words) // 2) - if not await check_word(ctx, words, index): - return False - - # check a word from the second half - index = random.uniform(len(words) // 2) + len(words) // 2 - if not await check_word(ctx, words, index): - return False - - return True +def _validate_reset_device(msg): + if msg.strength not in (128, 256): + if msg.slip39: + raise wire.ProcessError("Invalid strength (has to be 128 or 256 bits)") + elif msg.strength != 192: + raise wire.ProcessError("Invalid strength (has to be 128, 192 or 256 bits)") + if msg.display_random and (msg.skip_backup or msg.no_backup): + raise wire.ProcessError("Can't show internal entropy when backup is skipped") + if storage.is_initialized(): + raise wire.UnexpectedMessage("Already initialized") + if (msg.skip_backup or msg.no_backup) and msg.slip39: + raise wire.ProcessError("Both no/skip backup flag and Shamir SLIP-39 required.") -async def check_word(ctx, words: list, index: int): - if __debug__: - debug.reset_word_index = index - keyboard = MnemonicKeyboard("Type the %s word:" % format_ordinal(index + 1)) - if __debug__: - result = await ctx.wait(keyboard, debug.input_signal) - else: - result = await ctx.wait(keyboard) - return result == words[index] +def _compute_secret_from_entropy( + int_entropy: bytes, ext_entropy: bytes, strength_in_bytes: int +) -> bytes: + # combine internal and external entropy + ehash = hashlib.sha256() + ehash.update(int_entropy) + ehash.update(ext_entropy) + entropy = ehash.digest() + # take a required number of bytes + strength = strength_in_bytes // 8 + secret = entropy[:strength] + return secret diff --git a/core/src/trezor/crypto/slip39.py b/core/src/trezor/crypto/slip39.py index eb6c380a5..ad05e892f 100644 --- a/core/src/trezor/crypto/slip39.py +++ b/core/src/trezor/crypto/slip39.py @@ -21,8 +21,23 @@ from micropython import const from trezor.crypto import hashlib, hmac, pbkdf2, random -from trezor.crypto.slip39_wordlist import wordlist -from trezorcrypto import shamir +from trezorcrypto import shamir, slip39 + +_KEYBOARD_FULL_MASK = const(0x1FF) +"""All buttons are allowed. 9-bit bitmap all set to 1.""" + + +def compute_mask(prefix: str) -> int: + if not prefix: + return _KEYBOARD_FULL_MASK + return slip39.compute_mask(int(prefix)) + + +def button_sequence_to_word(prefix: str) -> str: + if not prefix: + return _KEYBOARD_FULL_MASK + return slip39.button_sequence_to_word(int(prefix)) + _RADIX_BITS = const(10) """The length of the radix in bits.""" @@ -36,6 +51,11 @@ def bits_to_words(n): return (n + _RADIX_BITS - 1) // _RADIX_BITS +MAX_SHARE_COUNT = const(16) +"""The maximum number of shares that can be created.""" + +DEFAULT_ITERATION_EXPONENT = const(0) + _RADIX = 2 ** _RADIX_BITS """The number of words in the wordlist.""" @@ -48,9 +68,6 @@ _ITERATION_EXP_LENGTH_BITS = const(5) _ID_EXP_LENGTH_WORDS = bits_to_words(_ID_LENGTH_BITS + _ITERATION_EXP_LENGTH_BITS) """The length of the random identifier and iteration exponent in words.""" -_MAX_SHARE_COUNT = const(16) -"""The maximum number of shares that can be created.""" - _CHECKSUM_LENGTH_WORDS = const(3) """The length of the RS1024 checksum in words.""" @@ -86,21 +103,6 @@ class MnemonicError(Exception): pass -def word_index(word): - word = word + " " * (8 - len(word)) - lo = 0 - hi = _RADIX - while hi - lo > 1: - mid = (hi + lo) // 2 - if wordlist[mid * 8 : mid * 8 + 8] > word: - hi = mid - else: - lo = mid - if wordlist[lo * 8 : lo * 8 + 8] != word: - raise MnemonicError('Invalid mnemonic word "{}".'.format(word)) - return lo - - def _rs1024_polymod(values): GEN = ( 0xE0E040, @@ -181,11 +183,11 @@ def _int_to_indices(value, length, bits): def mnemonic_from_indices(indices): - return " ".join(wordlist[i * 8 : i * 8 + 8].strip() for i in indices) + return " ".join(slip39.get_word(i) for i in indices) def mnemonic_to_indices(mnemonic): - return (word_index(word.lower()) for word in mnemonic.split()) + return (slip39.word_index(word.lower()) for word in mnemonic.split()) def _round_function(i, passphrase, e, salt, r): @@ -247,10 +249,10 @@ def _split_secret(threshold, share_count, shared_secret): ) ) - if share_count > _MAX_SHARE_COUNT: + if share_count > MAX_SHARE_COUNT: raise ValueError( "The requested number of shares ({}) must not exceed {}.".format( - share_count, _MAX_SHARE_COUNT + share_count, MAX_SHARE_COUNT ) ) @@ -462,16 +464,33 @@ def _decode_mnemonics(mnemonics): ) -def _generate_random_identifier(): +def _generate_random_identifier() -> int: """Returns a randomly generated integer in the range 0, ... , 2**_ID_LENGTH_BITS - 1.""" identifier = int.from_bytes(random.bytes(bits_to_bytes(_ID_LENGTH_BITS)), "big") return identifier & ((1 << _ID_LENGTH_BITS) - 1) -def generate_mnemonics( - group_threshold, groups, master_secret, passphrase=b"", iteration_exponent=0 -): +def generate_single_group_mnemonics_from_data( + master_secret, + threshold, + count, + passphrase=b"", + iteration_exponent=DEFAULT_ITERATION_EXPONENT, +) -> (int, list): + identifier, mnemonics = generate_mnemonics_from_data( + master_secret, 1, [(threshold, count)], passphrase, iteration_exponent + ) + return identifier, mnemonics[0] + + +def generate_mnemonics_from_data( + master_secret, + group_threshold, + groups, + passphrase=b"", + iteration_exponent=DEFAULT_ITERATION_EXPONENT, +) -> (int, list): """ Splits a master secret into mnemonic shares using Shamir's secret sharing scheme. :param int group_threshold: The number of groups required to reconstruct the master secret. @@ -486,6 +505,8 @@ def generate_mnemonics( :param int iteration_exponent: The iteration exponent. :return: List of mnemonics. :rtype: List of byte arrays. + :return: Identifier. + :rtype: int. """ identifier = _generate_random_identifier() @@ -528,68 +549,28 @@ def generate_mnemonics( group_shares = _split_secret(group_threshold, len(groups), encrypted_master_secret) - return [ - [ - encode_mnemonic( - identifier, - iteration_exponent, - group_index, - group_threshold, - len(groups), - member_index, - member_threshold, - value, - ) - for member_index, value in _split_secret( - member_threshold, member_count, group_secret - ) - ] - for (member_threshold, member_count), (group_index, group_secret) in zip( - groups, group_shares - ) - ] - - -def generate_mnemonics_random( - group_threshold, groups, strength_bits=128, passphrase=b"", iteration_exponent=0 -): - """ - Generates a random master secret and splits it into mnemonic shares using Shamir's secret - sharing scheme. - :param int group_threshold: The number of groups required to reconstruct the master secret. - :param groups: A list of (member_threshold, member_count) pairs for each group, where member_count - is the number of shares to generate for the group and member_threshold is the number of members required to - reconstruct the group secret. - :type groups: List of pairs of integers. - :param int strength_bits: The entropy of the randomly generated master secret in bits. - :param passphrase: The passphrase used to encrypt the master secret. - :type passphrase: Array of bytes. - :param int iteration_exponent: The iteration exponent. - :return: List of mnemonics. - :rtype: List of byte arrays. - """ - - if strength_bits < _MIN_STRENGTH_BITS: - raise ValueError( - "The requested strength of the master secret ({} bits) must be at least {} bits.".format( - strength_bits, _MIN_STRENGTH_BITS - ) - ) - - if strength_bits % 16 != 0: - raise ValueError( - "The requested strength of the master secret ({} bits) must be a multiple of 16 bits.".format( - strength_bits + mnemonics = [] + for (member_threshold, member_count), (group_index, group_secret) in zip( + groups, group_shares + ): + group_mnemonics = [] + for member_index, value in _split_secret( + member_threshold, member_count, group_secret + ): + group_mnemonics.append( + encode_mnemonic( + identifier, + iteration_exponent, + group_index, + group_threshold, + len(groups), + member_index, + member_threshold, + value, + ) ) - ) - - return generate_mnemonics( - group_threshold, - groups, - random.bytes(strength_bits // 8), - passphrase, - iteration_exponent, - ) + mnemonics.append(group_mnemonics) + return identifier, mnemonics def combine_mnemonics(mnemonics): @@ -597,7 +578,7 @@ def combine_mnemonics(mnemonics): Combines mnemonic shares to obtain the master secret which was previously split using Shamir's secret sharing scheme. :param mnemonics: List of mnemonics. - :type mnemonics: List of byte arrays. + :type mnemonics: List of strings. :return: Identifier, iteration exponent, the encrypted master secret. :rtype: Integer, integer, array of bytes. """ diff --git a/core/src/trezor/crypto/slip39_wordlist.py b/core/src/trezor/crypto/slip39_wordlist.py deleted file mode 100644 index 95815eaef..000000000 --- a/core/src/trezor/crypto/slip39_wordlist.py +++ /dev/null @@ -1 +0,0 @@ -wordlist = "academicacid acne acquire acrobat activityactress adapt adequateadjust admit adorn adult advance advocateafraid again agency agree aide aircraftairline airport ajar alarm album alcohol alien alive alpha already alto aluminumalways amazing ambitionamount amuse analysisanatomy ancestorancient angel angry animal answer antenna anxiety apart aquatic arcade arena argue armed artist artwork aspect auction august aunt average aviationavoid award away axis axle beam beard beaver become bedroom behaviorbeing believe belong benefit best beyond bike biology birthdaybishop black blanket blessingblimp blind blue body bolt boring born both boundarybraceletbranch brave breathe briefingbroken brother browser bucket budget buildingbulb bulge bumpy bundle burden burning busy buyer cage calcium camera campus canyon capacitycapital capture carbon cards careful cargo carpet carve categorycause ceiling center ceramic championchange charity check chemicalchest chew chubby cinema civil class clay cleanup client climate clinic clock clogs closet clothes club cluster coal coastal coding column company corner costume counter course cover cowboy cradle craft crazy credit cricket criminalcrisis criticalcrowd crucial crunch crush crystal cubic culturalcurious curly custody cylinderdaisy damage dance darknessdatabasedaughterdeadlinedeal debris debut decent decisiondeclare decoratedecreasedeliver demand density deny depart depend depict deploy describedesert desire desktop destroy detaileddetect device devote diagnosedictate diet dilemma diminishdining diploma disasterdiscuss disease dish dismiss display distancedive divorce documentdomain domesticdominantdough downtowndragon dramaticdream dress drift drink drove drug dryer ducklingduke durationdwarf dynamic early earth easel easy echo eclipse ecology edge editor educate either elbow elder electionelegant element elephantelevatorelite else email emerald emissionemperor emphasisemployerempty ending endless endorse enemy energy enforce engage enjoy enlarge entranceenvelopeenvy epidemicepisode equationequip eraser erode escape estate estimateevaluateevening evidenceevil evoke exact example exceed exchangeexclude excuse execute exerciseexhaust exotic expand expect explain express extend extra eyebrow facilityfact failure faint fake false family famous fancy fangs fantasy fatal fatigue favoritefawn fiber fiction filter finance findingsfinger firefly firm fiscal fishing fitness flame flash flavor flea flexibleflip float floral fluff focus forbid force forecastforget formal fortune forward founder fractionfragmentfrequentfreshmanfriar fridge friendlyfrost froth frozen fumes funding furl fused galaxy game garbage garden garlic gasolinegather general genius genre genuine geology gesture glad glance glasses glen glimpse goat golden graduategrant grasp gravity gray greatestgrief grill grin grocery gross group grownup grumpy guard guest guilt guitar gums hairy hamster hand hanger harvest have havoc hawk hazard headset health hearing heat helpful herald herd hesitatehobo holiday holy home hormone hospitalhour huge human humidityhunting husband hush husky hybrid idea identifyidle image impact imply improve impulse include income increaseindex indicateindustryinfant inform inherit injury inmate insect inside install intend intimateinvasioninvolve iris island isolate item ivory jacket jerky jewelry join judicialjuice jump junctionjunior junk jury justice kernel keyboardkidney kind kitchen knife knit laden ladle ladybug lair lamp languagelarge laser laundry lawsuit leader leaf learn leaves lecture legal legend legs lend length level liberty library license lift likely lilac lily lips liquid listen literaryliving lizard loan lobe locationlosing loud loyalty luck lunar lunch lungs luxury lying lyrics machine magazinemaiden mailman main makeup making mama manager mandate mansion manual marathonmarch market marvel mason materialmath maximum mayor meaning medal medical member memory mental merchantmerit method metric midst mild militarymineral ministermiracle mixed mixture mobile modern modify moisturemoment morning mortgagemother mountainmouse move much mule multiplemuscle museum music mustang nail nationalnecklacenegativenervous network news nuclear numb numerousnylon oasis obesity object observe obtain ocean often olympic omit oral orange orbit order ordinaryorganizeounce oven overall owner paces pacific package paid paintingpajamas pancake pants papa paper parcel parking party patent patrol payment payroll peacefulpeanut peasant pecan penalty pencil percent perfect permit petitionphantom pharmacyphoto phrase physics pickup picture piece pile pink pipelinepistol pitch plains plan plastic platformplayoff pleasureplot plunge practiceprayer preach predatorpregnantpremium prepare presenceprevent priest primary priorityprisonerprivacy prize problem process profile program promise prospectprovide prune public pulse pumps punish puny pupal purchasepurple python quantityquarter quick quiet race racism radar railroadrainbow raisin random ranked rapids raspy reactionrealize rebound rebuild recall receiverrecover regret regular reject relate rememberremind remove render repair repeat replace require rescue researchresidentresponseresult retailerretreat reunion revenue review reward rhyme rhythm rich rival river robin rocky romanticromp roster round royal ruin ruler rumor sack safari salary salon salt satisfy satoshi saver says scandal scared scatter scene scholar science scout scramblescrew script scroll seafood season secret securitysegment senior shadow shaft shame shaped sharp shelter sheriff short should shrimp sidewalksilent silver similar simple single sister skin skunk slap slavery sled slice slim slow slush smart smear smell smirk smith smoking smug snake snapshotsniff society softwaresoldier solutionsoul source space spark speak species spellingspend spew spider spill spine spirit spit spray sprinklesquare squeeze stadium staff standardstartingstation stay steady step stick stilt story strategystrike style subject submit sugar suitablesunlightsuperiorsurface surprisesurvive sweater swimmingswing switch symbolicsympathysyndromesystem tackle tactics tadpole talent task taste taught taxi teacher teammateteaspoontemple tenant tendencytension terminaltestify texture thank that theater theory therapy thorn threatenthumb thunder ticket tidy timber timely ting tofu togethertoleratetotal toxic tracks traffic trainingtransfertrash travelertreat trend trial tricycletrip triumph trouble true trust twice twin type typical ugly ultimateumbrellauncover undergo unfair unfold unhappy union universeunkind unknown unusual unwrap upgrade upstairsusernameusher usual valid valuablevampire vanish various vegan velvet venture verdict verify very veteran vexed victim video view vintage violenceviral visitor visual vitaminsvocal voice volume voter voting walnut warmth warn watch wavy wealthy weapon webcam welcome welfare western width wildlifewindow wine wirelesswisdom withdrawwits wolf woman work worthy wrap wrist writing wrote year yelp yield yoga zero " diff --git a/core/src/trezor/messages/ResetDevice.py b/core/src/trezor/messages/ResetDevice.py index 8c41eafc3..f8774381a 100644 --- a/core/src/trezor/messages/ResetDevice.py +++ b/core/src/trezor/messages/ResetDevice.py @@ -17,6 +17,7 @@ class ResetDevice(p.MessageType): u2f_counter: int = None, skip_backup: bool = None, no_backup: bool = None, + slip39: bool = None, ) -> None: self.display_random = display_random self.strength = strength @@ -27,6 +28,7 @@ class ResetDevice(p.MessageType): self.u2f_counter = u2f_counter self.skip_backup = skip_backup self.no_backup = no_backup + self.slip39 = slip39 @classmethod def get_fields(cls): @@ -40,4 +42,5 @@ class ResetDevice(p.MessageType): 7: ('u2f_counter', p.UVarintType, 0), 8: ('skip_backup', p.BoolType, 0), 9: ('no_backup', p.BoolType, 0), + 10: ('slip39', p.BoolType, 0), } diff --git a/core/src/trezor/ui/checklist.py b/core/src/trezor/ui/checklist.py new file mode 100644 index 000000000..27c9933ae --- /dev/null +++ b/core/src/trezor/ui/checklist.py @@ -0,0 +1,55 @@ +from micropython import const + +from trezor import ui +from trezor.ui import text + +_CHECKLIST_MAX_LINES = const(5) +_CHECKLIST_OFFSET_X = const(24) + + +class Checklist(ui.Control): + def __init__(self, title, icon): + self.title = title + self.icon = icon + self.choices = [] + self.words = [] + self.active = 0 + self.repaint = False + + def add(self, choice): + self.choices.append(choice) + + def select(self, active): + self.active = active + + def process(self): + w = self.words + w.clear() + for index, choice in enumerate(self.choices): + if index < self.active: + w.append(ui.BOLD) + w.append(ui.GREEN) + elif index == self.active: + w.append(ui.BOLD) + w.append(ui.FG) + else: # index > self.active + w.append(ui.NORMAL) + w.append(ui.GREY) + if isinstance(choice, str): + w.append(choice) + else: # choice is iterable + w.extend(choice) + w.append(text.BR) + self.words = w + self.repaint = True + + def on_render(self): + if self.repaint: + ui.header(self.title, self.icon) + text.render_text( + self.words, + new_lines=False, # we are adding line breaks manually + max_lines=_CHECKLIST_MAX_LINES, + offset_x=_CHECKLIST_OFFSET_X, + ) + self.repaint = False diff --git a/core/src/trezor/ui/confirm.py b/core/src/trezor/ui/confirm.py index 1be20caa8..b28d1438c 100644 --- a/core/src/trezor/ui/confirm.py +++ b/core/src/trezor/ui/confirm.py @@ -19,12 +19,15 @@ class Confirm(ui.Layout): confirm_style=DEFAULT_CONFIRM_STYLE, cancel=DEFAULT_CANCEL, cancel_style=DEFAULT_CANCEL_STYLE, + major_confirm=False, ): self.content = content if confirm is not None: if cancel is None: area = ui.grid(4, n_x=1) + elif major_confirm: + area = ui.grid(13, cells_x=2) else: area = ui.grid(9, n_x=2) self.confirm = Button(area, confirm, confirm_style) @@ -35,6 +38,8 @@ class Confirm(ui.Layout): if cancel is not None: if confirm is None: area = ui.grid(4, n_x=1) + elif major_confirm: + area = ui.grid(12, cells_x=1) else: area = ui.grid(8, n_x=2) self.cancel = Button(area, cancel, cancel_style) diff --git a/core/src/trezor/ui/info.py b/core/src/trezor/ui/info.py new file mode 100644 index 000000000..07fb77f31 --- /dev/null +++ b/core/src/trezor/ui/info.py @@ -0,0 +1,63 @@ +from trezor import res, ui +from trezor.ui.button import Button, ButtonConfirm +from trezor.ui.confirm import CONFIRMED +from trezor.ui.text import TEXT_LINE_HEIGHT, TEXT_MARGIN_LEFT, render_text + + +class DefaultInfoConfirm: + + fg_color = ui.LIGHT_GREY + bg_color = ui.BLACKISH + + class button(ButtonConfirm): + class normal(ButtonConfirm.normal): + border_color = ui.BLACKISH + + class disabled(ButtonConfirm.disabled): + border_color = ui.BLACKISH + + +class InfoConfirm(ui.Layout): + DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) + DEFAULT_STYLE = DefaultInfoConfirm + + def __init__(self, text, confirm=DEFAULT_CONFIRM, style=DEFAULT_STYLE): + self.text = text.split() + self.style = style + panel_area = ui.grid(0, n_x=1, n_y=1) + self.panel_area = panel_area + confirm_area = ui.grid(4, n_x=1) + self.confirm = Button(confirm_area, confirm, style.button) + self.confirm.on_click = self.on_confirm + self.repaint = True + + def dispatch(self, event, x, y): + if event == ui.RENDER: + self.on_render() + self.confirm.dispatch(event, x, y) + + def on_render(self): + if self.repaint: + x, y, w, h = self.panel_area + fg_color = self.style.fg_color + bg_color = self.style.bg_color + + # render the background panel + ui.display.bar_radius(x, y, w, h, bg_color, ui.BG, ui.RADIUS) + + # render the info text + render_text( + self.text, + new_lines=False, + max_lines=6, + offset_y=y + TEXT_LINE_HEIGHT, + offset_x=x + TEXT_MARGIN_LEFT - ui.VIEWX, + offset_x_max=x + w - ui.VIEWX, + fg=fg_color, + bg=bg_color, + ) + + self.repaint = False + + def on_confirm(self): + raise ui.Result(CONFIRMED) diff --git a/core/src/trezor/ui/loader.py b/core/src/trezor/ui/loader.py index 1e0fd7036..fd992fbb5 100644 --- a/core/src/trezor/ui/loader.py +++ b/core/src/trezor/ui/loader.py @@ -77,6 +77,26 @@ class Loader(ui.Control): self.start_ms = None self.stop_ms = None self.on_start() + if r == target: + self.on_finish() def on_start(self): pass + + def on_finish(self): + pass + + +class LoadingAnimation(ui.Layout): + def __init__(self, style=LoaderDefault): + self.loader = Loader(style) + self.loader.on_finish = self.on_finish + self.loader.start() + + def dispatch(self, event, x, y): + if not self.loader.elapsed_ms(): + self.loader.start() + self.loader.dispatch(event, x, y) + + def on_finish(self): + raise ui.Result(None) diff --git a/core/src/trezor/ui/shamir.py b/core/src/trezor/ui/shamir.py new file mode 100644 index 000000000..5a7bc91ac --- /dev/null +++ b/core/src/trezor/ui/shamir.py @@ -0,0 +1,49 @@ +from trezor import ui +from trezor.ui.button import Button +from trezor.ui.text import LABEL_CENTER, Label + + +class NumInput(ui.Control): + def __init__(self, count=5, max_count=16, min_count=1): + self.count = count + self.max_count = max_count + self.min_count = min_count + + self.minus = Button(ui.grid(3), "-") + self.minus.on_click = self.on_minus + self.plus = Button(ui.grid(5), "+") + self.plus.on_click = self.on_plus + self.text = Label(ui.grid(4), "", LABEL_CENTER, ui.BOLD) + + self.edit(count) + + def dispatch(self, event, x, y): + self.minus.dispatch(event, x, y) + self.plus.dispatch(event, x, y) + self.text.dispatch(event, x, y) + + def on_minus(self): + self.edit(self.count - 1) + + def on_plus(self): + self.edit(self.count + 1) + + def edit(self, count): + count = max(count, self.min_count) + count = min(count, self.max_count) + if self.count != count: + self.on_change(count) + self.count = count + self.text.content = str(count) + self.text.repaint = True + if self.count == self.min_count: + self.minus.disable() + else: + self.minus.enable() + if self.count == self.max_count: + self.plus.disable() + else: + self.plus.enable() + + def on_change(self, count): + pass diff --git a/core/src/trezor/ui/text.py b/core/src/trezor/ui/text.py index 0746001ba..f99c9059c 100644 --- a/core/src/trezor/ui/text.py +++ b/core/src/trezor/ui/text.py @@ -13,15 +13,21 @@ BR = const(-256) BR_HALF = const(-257) -def render_text(words: list, new_lines: bool, max_lines: int) -> None: +def render_text( + words: list, + new_lines: bool, + max_lines: int, + font: int = ui.NORMAL, + fg: int = ui.FG, + bg: int = ui.BG, + offset_x: int = TEXT_MARGIN_LEFT, + offset_y: int = TEXT_HEADER_HEIGHT + TEXT_LINE_HEIGHT, + offset_x_max: int = ui.WIDTH, +) -> None: # initial rendering state - font = ui.NORMAL - fg = ui.FG - bg = ui.BG - offset_x = TEXT_MARGIN_LEFT - offset_y = TEXT_HEADER_HEIGHT + TEXT_LINE_HEIGHT - OFFSET_X_MAX = ui.WIDTH - OFFSET_Y_MAX = TEXT_HEADER_HEIGHT + TEXT_LINE_HEIGHT * max_lines + INITIAL_OFFSET_X = offset_x + offset_y_max = offset_y * max_lines + FONTS = (ui.NORMAL, ui.BOLD, ui.MONO, ui.MONO_BOLD) # sizes of common glyphs @@ -35,10 +41,10 @@ def render_text(words: list, new_lines: bool, max_lines: int) -> None: if isinstance(word, int): if word is BR or word is BR_HALF: # line break or half-line break - if offset_y >= OFFSET_Y_MAX: + if offset_y >= offset_y_max: ui.display.text(offset_x, offset_y, "...", ui.BOLD, ui.GREY, bg) return - offset_x = TEXT_MARGIN_LEFT + offset_x = INITIAL_OFFSET_X offset_y += TEXT_LINE_HEIGHT if word is BR else TEXT_LINE_HEIGHT_HALF elif word in FONTS: # change of font style @@ -50,22 +56,22 @@ def render_text(words: list, new_lines: bool, max_lines: int) -> None: width = ui.display.text_width(word, font) - while offset_x + width > OFFSET_X_MAX or ( - has_next_word and offset_y >= OFFSET_Y_MAX + while offset_x + width > offset_x_max or ( + has_next_word and offset_y >= offset_y_max ): - beginning_of_line = offset_x == TEXT_MARGIN_LEFT - word_fits_in_one_line = width < (OFFSET_X_MAX - TEXT_MARGIN_LEFT) + beginning_of_line = offset_x == INITIAL_OFFSET_X + word_fits_in_one_line = width < (offset_x_max - INITIAL_OFFSET_X) if ( - offset_y < OFFSET_Y_MAX + offset_y < offset_y_max and word_fits_in_one_line and not beginning_of_line ): # line break - offset_x = TEXT_MARGIN_LEFT + offset_x = INITIAL_OFFSET_X offset_y += TEXT_LINE_HEIGHT break # word split - if offset_y < OFFSET_Y_MAX: + if offset_y < offset_y_max: split = "-" splitw = DASH else: @@ -75,7 +81,7 @@ def render_text(words: list, new_lines: bool, max_lines: int) -> None: for index in range(len(word) - 1, 0, -1): letter = word[index] width -= ui.display.text_width(letter, font) - if offset_x + width + splitw < OFFSET_X_MAX: + if offset_x + width + splitw < offset_x_max: break else: index = 0 @@ -84,9 +90,9 @@ def render_text(words: list, new_lines: bool, max_lines: int) -> None: ui.display.text(offset_x, offset_y, span, font, fg, bg) ui.display.text(offset_x + width, offset_y, split, ui.BOLD, ui.GREY, bg) # line break - if offset_y >= OFFSET_Y_MAX: + if offset_y >= offset_y_max: return - offset_x = TEXT_MARGIN_LEFT + offset_x = INITIAL_OFFSET_X offset_y += TEXT_LINE_HEIGHT # continue with the rest word = word[index:] @@ -97,10 +103,10 @@ def render_text(words: list, new_lines: bool, max_lines: int) -> None: if new_lines and has_next_word: # line break - if offset_y >= OFFSET_Y_MAX: + if offset_y >= offset_y_max: ui.display.text(offset_x, offset_y, "...", ui.BOLD, ui.GREY, bg) return - offset_x = TEXT_MARGIN_LEFT + offset_x = INITIAL_OFFSET_X offset_y += TEXT_LINE_HEIGHT else: # shift cursor @@ -158,3 +164,31 @@ class Text(ui.Control): ) render_text(self.content, self.new_lines, self.max_lines) self.repaint = False + + +LABEL_LEFT = const(0) +LABEL_CENTER = const(1) +LABEL_RIGHT = const(2) + + +class Label(ui.Control): + def __init__(self, area, content, align=LABEL_LEFT, style=ui.NORMAL): + self.area = area + self.content = content + self.align = align + self.style = style + self.repaint = True + + def on_render(self): + if self.repaint: + align = self.align + ax, ay, aw, ah = self.area + tx = ax + aw // 2 + ty = ay + ah // 2 + 8 + if align is LABEL_LEFT: + ui.display.text_left(tx, ty, self.content, self.style, ui.FG, ui.BG) + elif align is LABEL_CENTER: + ui.display.text_center(tx, ty, self.content, self.style, ui.FG, ui.BG) + elif align is LABEL_RIGHT: + ui.display.text_right(tx, ty, self.content, self.style, ui.FG, ui.BG) + self.repaint = False diff --git a/core/src/trezor/ui/word_select.py b/core/src/trezor/ui/word_select.py index ee3209550..358c2f705 100644 --- a/core/src/trezor/ui/word_select.py +++ b/core/src/trezor/ui/word_select.py @@ -1,22 +1,30 @@ from trezor import ui from trezor.ui.button import Button +# todo improve? + class WordSelector(ui.Layout): def __init__(self, content): self.content = content - self.w12 = Button(ui.grid(6, n_y=4, n_x=3, cells_y=2), "12") + self.w12 = Button(ui.grid(6, n_y=4), "12") self.w12.on_click = self.on_w12 - self.w18 = Button(ui.grid(7, n_y=4, n_x=3, cells_y=2), "18") + self.w18 = Button(ui.grid(7, n_y=4), "18") self.w18.on_click = self.on_w18 - self.w24 = Button(ui.grid(8, n_y=4, n_x=3, cells_y=2), "24") + self.w20 = Button(ui.grid(8, n_y=4), "20") + self.w20.on_click = self.on_w20 + self.w24 = Button(ui.grid(9, n_y=4), "24") self.w24.on_click = self.on_w24 + self.w33 = Button(ui.grid(10, n_y=4), "33") + self.w33.on_click = self.on_w33 def dispatch(self, event, x, y): self.content.dispatch(event, x, y) self.w12.dispatch(event, x, y) self.w18.dispatch(event, x, y) + self.w20.dispatch(event, x, y) self.w24.dispatch(event, x, y) + self.w33.dispatch(event, x, y) def on_w12(self): raise ui.Result(12) @@ -24,5 +32,11 @@ class WordSelector(ui.Layout): def on_w18(self): raise ui.Result(18) + def on_w20(self): + raise ui.Result(20) + def on_w24(self): raise ui.Result(24) + + def on_w33(self): + raise ui.Result(33) diff --git a/core/tests/test_trezor.crypto.slip39.py b/core/tests/test_trezor.crypto.slip39.py index 937f4aea3..057203daa 100644 --- a/core/tests/test_trezor.crypto.slip39.py +++ b/core/tests/test_trezor.crypto.slip39.py @@ -25,12 +25,15 @@ class TestCryptoSlip39(unittest.TestCase): MS = b"ABCDEFGHIJKLMNOP" def test_basic_sharing_random(self): - mnemonics = slip39.generate_mnemonics_random(1, [(3, 5)])[0] + ms = random.bytes(32) + _, mnemonics = slip39.generate_mnemonics_from_data(ms, 1, [(3, 5)]) + mnemonics = mnemonics[0] self.assertEqual(slip39.combine_mnemonics(mnemonics[:3]), slip39.combine_mnemonics(mnemonics[2:])) def test_basic_sharing_fixed(self): - mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS)[0] + _, mnemonics = slip39.generate_mnemonics_from_data(self.MS, 1, [(3, 5)]) + mnemonics = mnemonics[0] identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[:3]) self.assertEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS) self.assertEqual(slip39.combine_mnemonics(mnemonics[1:4])[2], ems) @@ -39,19 +42,22 @@ class TestCryptoSlip39(unittest.TestCase): def test_passphrase(self): - mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS, b"TREZOR")[0] + _, mnemonics = slip39.generate_mnemonics_from_data(self.MS, 1, [(3, 5)], b"TREZOR") + mnemonics = mnemonics[0] identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[1:4]) self.assertEqual(slip39.decrypt(identifier, exponent, ems, b"TREZOR"), self.MS) self.assertNotEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS) def test_iteration_exponent(self): - mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS, b"TREZOR", 1)[0] + _, mnemonics = slip39.generate_mnemonics_from_data(self.MS, 1, [(3, 5)], b"TREZOR", 1) + mnemonics = mnemonics[0] identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[1:4]) self.assertEqual(slip39.decrypt(identifier, exponent, ems, b"TREZOR"), self.MS) self.assertNotEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS) - mnemonics = slip39.generate_mnemonics(1, [(3, 5)], self.MS, b"TREZOR", 2)[0] + _, mnemonics = slip39.generate_mnemonics_from_data(self.MS, 1, [(3, 5)], b"TREZOR", 2) + mnemonics = mnemonics[0] identifier, exponent, ems = slip39.combine_mnemonics(mnemonics[1:4]) self.assertEqual(slip39.decrypt(identifier, exponent, ems, b"TREZOR"), self.MS) self.assertNotEqual(slip39.decrypt(identifier, exponent, ems, b""), self.MS) @@ -61,8 +67,8 @@ class TestCryptoSlip39(unittest.TestCase): group_threshold = 2 group_sizes = (5, 3, 5, 1) member_thresholds = (3, 2, 2, 1) - mnemonics = slip39.generate_mnemonics( - group_threshold, list(zip(member_thresholds, group_sizes)), self.MS + _, mnemonics = slip39.generate_mnemonics_from_data( + self.MS, group_threshold, list(zip(member_thresholds, group_sizes)) ) # Test all valid combinations of mnemonics. @@ -93,8 +99,8 @@ class TestCryptoSlip39(unittest.TestCase): group_threshold = 1 group_sizes = (5, 3, 5, 1) member_thresholds = (3, 2, 2, 1) - mnemonics = slip39.generate_mnemonics( - group_threshold, list(zip(member_thresholds, group_sizes)), self.MS + _, mnemonics = slip39.generate_mnemonics_from_data( + self.MS, group_threshold, list(zip(member_thresholds, group_sizes)) ) # Test all valid combinations of mnemonics. @@ -108,8 +114,8 @@ class TestCryptoSlip39(unittest.TestCase): def test_all_groups_exist(self): for group_threshold in (1, 2, 5): - mnemonics = slip39.generate_mnemonics( - group_threshold, [(3, 5), (1, 1), (2, 3), (2, 5), (3, 5)], self.MS + _, mnemonics = slip39.generate_mnemonics_from_data( + self.MS, group_threshold, [(3, 5), (1, 1), (2, 3), (2, 5), (3, 5)] ) self.assertEqual(len(mnemonics), 5) self.assertEqual(len(sum(mnemonics, [])), 19) @@ -118,31 +124,31 @@ class TestCryptoSlip39(unittest.TestCase): def test_invalid_sharing(self): # Short master secret. with self.assertRaises(ValueError): - slip39.generate_mnemonics(1, [(2, 3)], self.MS[:14]) + slip39.generate_mnemonics_from_data(self.MS[:14], 1, [(2, 3)]) # Odd length master secret. with self.assertRaises(ValueError): - slip39.generate_mnemonics(1, [(2, 3)], self.MS + b"X") + slip39.generate_mnemonics_from_data(self.MS + b"X", 1, [(2, 3)]) # Group threshold exceeds number of groups. with self.assertRaises(ValueError): - slip39.generate_mnemonics(3, [(3, 5), (2, 5)], self.MS) + slip39.generate_mnemonics_from_data(self.MS, 3, [(3, 5), (2, 5)]) # Invalid group threshold. with self.assertRaises(ValueError): - slip39.generate_mnemonics(0, [(3, 5), (2, 5)], self.MS) + slip39.generate_mnemonics_from_data(self.MS, 0, [(3, 5), (2, 5)]) # Member threshold exceeds number of members. with self.assertRaises(ValueError): - slip39.generate_mnemonics(2, [(3, 2), (2, 5)], self.MS) + slip39.generate_mnemonics_from_data(self.MS, 2, [(3, 2), (2, 5)]) # Invalid member threshold. with self.assertRaises(ValueError): - slip39.generate_mnemonics(2, [(0, 2), (2, 5)], self.MS) + slip39.generate_mnemonics_from_data(self.MS, 2, [(0, 2), (2, 5)]) # Group with multiple members and threshold 1. with self.assertRaises(ValueError): - slip39.generate_mnemonics(2, [(3, 5), (1, 3), (2, 5)], self.MS) + slip39.generate_mnemonics_from_data(self.MS, 2, [(3, 5), (1, 3), (2, 5)]) def test_vectors(self): diff --git a/python/trezorlib/coins.json b/python/trezorlib/coins.json index a9da0f8d1..b4663359f 100644 --- a/python/trezorlib/coins.json +++ b/python/trezorlib/coins.json @@ -1 +1 @@ -[{"address_type": 0, "address_type_p2sh": 5, "bech32_prefix": "bc", "bip115": false, "bitcore": [], "blockbook": ["https://btc1.trezor.io", "https://btc2.trezor.io", "https://btc3.trezor.io", "https://btc4.trezor.io", "https://btc5.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin", "coin_name": "Bitcoin", "coin_shortcut": "BTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bitcoin/bitcoin", "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "key": "bitcoin:BTC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin", "segwit": true, "shortcut": "BTC", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 0, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://bitcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "tb", "bip115": false, "bitcore": [], "blockbook": ["https://tbtc1.trezor.io", "https://tbtc2.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Testnet", "coin_name": "Testnet", "coin_shortcut": "TEST", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bitcoin/bitcoin", "hash_genesis_block": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", "key": "bitcoin:TEST", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Testnet", "segwit": true, "shortcut": "TEST", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://bitcoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 53, "address_type_p2sh": 55, "bech32_prefix": "acm", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Actinium", "coin_name": "Actinium", "coin_shortcut": "ACM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Actinium-project/Actinium", "hash_genesis_block": "28d77872e23714562f49a1be792c276623c1bbe3fdcf21b6035cfde78b00b824", "key": "bitcoin:ACM", "maintainer": "Harris Brakmic ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Actinium", "segwit": true, "shortcut": "ACM", "signed_message_header": "Actinium Signed Message:\n", "slip44": 228, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": true}, "uri_prefix": "actinium", "website": "https://actinium.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 55, "address_type_p2sh": 16, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Axe", "coin_name": "Axe", "coin_shortcut": "AXE", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/axerunners/axe", "hash_genesis_block": "00000c33631ca6f2f61368991ce2dc03306b5bb50bf7cede5cfbba6db38e52e6", "key": "bitcoin:AXE", "maintainer": "Kirill Orlov ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Axe", "segwit": false, "shortcut": "AXE", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 4242, "support": {"connect": true, "trezor1": "1.7.3", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "axe", "website": "https://axerunners.com", "xprv_magic": 50221816, "xpub_magic": 50221772, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 25, "address_type_p2sh": 85, "bech32_prefix": "bm", "bip115": false, "bitcore": [], "blockbook": ["https://bellcoin-blockbook.ilmango.work", "https://bell.blockbook.ovh"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Bellcoin", "coin_name": "Bellcoin", "coin_shortcut": "BELL", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 20}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bellcoin-org/bellcoin", "hash_genesis_block": "000008f3b6bd10c2d03b06674a006b8d9731f6cb58179ef1eee008cee2209603", "key": "bitcoin:BELL", "maintainer": "ilmango-doge ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bellcoin", "segwit": true, "shortcut": "BELL", "signed_message_header": "Bellcoin Signed Message:\n", "slip44": 25252, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bellcoin", "website": "https://bellcoin.web4u.jp", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 230, "address_type_p2sh": 235, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "BitCash", "coin_name": "BitCash", "coin_shortcut": "BITC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/WillyTheCat/BitCash", "hash_genesis_block": "7d57d87ff3c15a521530af60edee1887fba9c193eb518face926785c4cd8f4f1", "key": "bitcoin:BITC", "maintainer": "Christian Kassler ", "max_address_length": 53, "maxfee_kb": 30000000, "min_address_length": 52, "minfee_kb": 1000, "name": "BitCash", "segwit": false, "shortcut": "BITC", "signed_message_header": "Bitcash Signed Message:\n", "slip44": 230, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "bitcash", "website": "https://www.choosebitcash.com", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 81, "address_type_p2sh": 5, "bech32_prefix": "bz", "bip115": false, "bitcore": ["https://insight.bitzeny.jp", "https://zeny.insight.monaco-ex.org"], "blockbook": ["https://zny.blockbook.ovh"], "blocktime_seconds": 90, "cashaddr_prefix": null, "coin_label": "BitZeny", "coin_name": "BitZeny", "coin_shortcut": "ZNY", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 20}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/BitzenyCoreDevelopers/bitzeny", "hash_genesis_block": "000009f7e55e9e3b4781e22bd87a7cfa4acada9e4340d43ca738bf4e9fb8f5ce", "key": "bitcoin:ZNY", "maintainer": "y-chan ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "BitZeny", "segwit": true, "shortcut": "ZNY", "signed_message_header": "BitZeny Signed Message:\n", "slip44": 123, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bitzeny", "website": "https://bitzeny.tech", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 25, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 300, "cashaddr_prefix": null, "coin_label": "Bitcloud", "coin_name": "Bitcloud", "coin_shortcut": "BTDX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/Bitcloud", "hash_genesis_block": "000002d56463941c20eae5cb474cc805b646515d18bc7dc222a0885b206eadb0", "key": "bitcoin:BTDX", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 10000, "name": "Bitcloud", "segwit": false, "shortcut": "BTDX", "signed_message_header": "Diamond Signed Message:\n", "slip44": 218, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "bitcloud", "website": "https://bit-cloud.info", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 0, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://bch1.trezor.io", "https://bch2.trezor.io", "https://bch3.trezor.io", "https://bch4.trezor.io", "https://bch5.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": "bitcoincash", "coin_label": "Bitcoin Cash", "coin_name": "Bcash", "coin_shortcut": "BCH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": true, "fork_id": 0, "github": "https://github.com/Bitcoin-ABC/bitcoin-abc", "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "key": "bitcoin:BCH", "maintainer": "Jochen Hoenicke ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Cash", "segwit": false, "shortcut": "BCH", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 145, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "bitcoincash", "website": "https://www.bitcoincash.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": "bchtest", "coin_label": "Bitcoin Cash Testnet", "coin_name": "Bcash Testnet", "coin_shortcut": "TBCH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": true, "fork_id": 0, "github": "https://github.com/Bitcoin-ABC/bitcoin-abc", "hash_genesis_block": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", "key": "bitcoin:TBCH", "maintainer": "Jochen Hoenicke ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Cash Testnet", "segwit": false, "shortcut": "TBCH", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": false}, "uri_prefix": "bitcoincash", "website": "https://www.bitcoincash.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 38, "address_type_p2sh": 23, "bech32_prefix": "btg", "bip115": false, "bitcore": [], "blockbook": ["https://btg1.trezor.io", "https://btg2.trezor.io", "https://btg3.trezor.io", "https://btg4.trezor.io", "https://btg5.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin Gold", "coin_name": "Bgold", "coin_shortcut": "BTG", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": true, "fork_id": 79, "github": "https://github.com/BTCGPU/BTCGPU", "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "key": "bitcoin:BTG", "maintainer": "Saleem Rashid ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Gold", "segwit": true, "shortcut": "BTG", "signed_message_header": "Bitcoin Gold Signed Message:\n", "slip44": 156, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "bitcoingold", "website": "https://bitcoingold.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "tbtg", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin Gold Testnet", "coin_name": "Bgold Testnet", "coin_shortcut": "TBTG", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": true, "fork_id": 79, "github": "https://github.com/BTCGPU/BTCGPU", "hash_genesis_block": "00000000e0781ebe24b91eedc293adfea2f557b53ec379e78959de3853e6f9f6", "key": "bitcoin:TBTG", "maintainer": "The Bitcoin Gold Developers ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Gold Testnet", "segwit": true, "shortcut": "TBTG", "signed_message_header": "Bitcoin Gold Signed Message:\n", "slip44": 156, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "bitcoingold", "website": "https://bitcoingold.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 4901, "address_type_p2sh": 5039, "bech32_prefix": null, "bip115": false, "bitcore": ["https://explorer.btcprivate.org"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Bitcoin Private", "coin_name": "Bprivate", "coin_shortcut": "BTCP", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": 42, "github": "https://github.com/BTCPrivate/BitcoinPrivate", "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602", "key": "bitcoin:BTCP", "maintainer": "Chris Sulmone ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Bitcoin Private", "segwit": false, "shortcut": "BTCP", "signed_message_header": "BitcoinPrivate Signed Message:\n", "slip44": 183, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "bitcoinprivate", "website": "https://btcprivate.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 61, "address_type_p2sh": 123, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook1.bitcoinrh.org", "https://blockbook2.bitcoinrh.org"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin Rhodium", "coin_name": "Brhodium", "coin_shortcut": "XRC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://gitlab.com/bitcoinrh/BRhodiumNode", "hash_genesis_block": "baff5bfd9dc43fb672d003ec20fd21428f9282ca46bfa1730d73e1f2c75f5fdd", "key": "bitcoin:XRC", "maintainer": "baff5b ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Rhodium", "segwit": false, "shortcut": "XRC", "signed_message_header": "BitCoin Rhodium Signed Message:\n", "slip44": 10291, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bitcoin-rhodium", "website": "https://www.bitcoinrh.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 3, "address_type_p2sh": 125, "bech32_prefix": "btx", "bip115": false, "bitcore": ["https://insight.bitcore.cc"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Bitcore", "coin_name": "Bitcore", "coin_shortcut": "BTX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/BitCore", "hash_genesis_block": "604148281e5c4b7f2487e5d03cd60d8e6f69411d613f6448034508cea52e9574", "key": "bitcoin:BTX", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcore", "segwit": true, "shortcut": "BTX", "signed_message_header": "BitCore Signed Message:\n", "slip44": 160, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "bitcore", "website": "https://bitcore.cc", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 102, "address_type_p2sh": 5, "bech32_prefix": "bsd", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 200, "cashaddr_prefix": null, "coin_label": "Bitsend", "coin_name": "Bitsend", "coin_shortcut": "BSD", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/BitSend", "hash_genesis_block": "0000012e1b8843ac9ce8c18603658eaf8895f99d3f5e7e1b7b1686f35e3c087a", "key": "bitcoin:BSD", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 10000, "name": "Bitsend", "segwit": true, "shortcut": "BSD", "signed_message_header": "Bitsend Signed Message:\n", "slip44": 91, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "bitsend", "website": "https://bitsend.info", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 26, "address_type_p2sh": 5, "bech32_prefix": "bst", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "BlockStamp", "coin_name": "BlockStamp", "coin_shortcut": "BST", "consensus_branch_id": null, "cooldown": 1000, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "duplicate": true, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/BlockStamp/bst", "hash_genesis_block": "8000000049a2e26b0185be50b4b8ed58b707c8893762959f0b1673641cae1828", "key": "bitcoin:BST", "maintainer": "Krzysztof Kuchta ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "BlockStamp", "segwit": true, "shortcut": "BST", "signed_message_header": "BST Signed Message:\n", "slip44": 254, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "blockstamp", "website": "https://blockstamp.info", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 28, "address_type_p2sh": 35, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.capricoin.org", "https://blockbook2.capricoin.org", "https://blockbook3.capricoin.org", "https://blockbook4.capricoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Capricoin", "coin_name": "Capricoin", "coin_shortcut": "CPC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 7, "High": 20, "Low": 1, "Normal": 14}, "duplicate": true, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Capricoinofficial/Capricoin", "hash_genesis_block": "00000d23fa0fc52c90893adb1181c9ddffb6c797a3e41864b9a23aa2f2981fe3", "key": "bitcoin:CPC", "maintainer": "Tibor Arpas ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Capricoin", "segwit": false, "shortcut": "CPC", "signed_message_header": "Capricoin Signed Message:\n", "slip44": 289, "support": {"connect": true, "trezor1": false, "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "capricoin", "website": "https://capricoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 0, "address_type_p2sh": 28, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Crown", "coin_name": "Crown", "coin_shortcut": "CRW", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Crowndev/crowncoin", "hash_genesis_block": "0000000085370d5e122f64f4ab19c68614ff3df78c8d13cb814fd7e69a1dc6da", "key": "bitcoin:CRW", "maintainer": "hypermist ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Crown", "segwit": false, "shortcut": "CRW", "signed_message_header": "Crown Signed Message:\n", "slip44": 72, "support": {"connect": false, "trezor1": false, "trezor2": false, "webwallet": false}, "uri_prefix": "crown", "website": "https://crown.tech", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 76, "address_type_p2sh": 16, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://dash1.trezor.io", "https://dash2.trezor.io", "https://dash3.trezor.io", "https://dash4.trezor.io", "https://dash5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Dash", "coin_name": "Dash", "coin_shortcut": "DASH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/dashpay/dash", "hash_genesis_block": "00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6", "key": "bitcoin:DASH", "maintainer": "Karel Bilek ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Dash", "segwit": false, "shortcut": "DASH", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 5, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "dash", "website": "https://www.dash.org", "xprv_magic": 50221816, "xpub_magic": 50221772, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 140, "address_type_p2sh": 19, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Dash Testnet", "coin_name": "Dash Testnet", "coin_shortcut": "tDASH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/dashpay/dash", "hash_genesis_block": "00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c", "key": "bitcoin:tDASH", "maintainer": "Karel Bilek ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 10000, "name": "Dash Testnet", "segwit": false, "shortcut": "tDASH", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "dash", "website": "https://www.dash.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 1855, "address_type_p2sh": 1818, "bech32_prefix": null, "bip115": false, "bitcore": ["https://mainnet.decred.org"], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Decred", "coin_name": "Decred", "coin_shortcut": "DCR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_decred", "decred": true, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/decred/dcrd", "hash_genesis_block": "298e5cc3d985bfe7f81dc135f360abe089edd4396b86d2de66b0cef42b21d980", "key": "bitcoin:DCR", "maintainer": "Alex Yocom-Piatt ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 10000, "name": "Decred", "segwit": false, "shortcut": "DCR", "signed_message_header": "Decred Signed Message:\n", "slip44": 42, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://www.decred.org", "xprv_magic": 50177256, "xpub_magic": 50178342, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 3873, "address_type_p2sh": 3836, "bech32_prefix": null, "bip115": false, "bitcore": ["https://testnet.decred.org"], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Decred Testnet", "coin_name": "Decred Testnet", "coin_shortcut": "TDCR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_decred", "decred": true, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/decred/dcrd", "hash_genesis_block": "a649dce53918caf422e9c711c858837e08d626ecfcd198969b24f7b634a49bac", "key": "bitcoin:TDCR", "maintainer": "Saleem Rashid ", "max_address_length": 35, "maxfee_kb": 10000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Decred Testnet", "segwit": false, "shortcut": "TDCR", "signed_message_header": "Decred Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://www.decred.org", "xprv_magic": 70615959, "xpub_magic": 70617041, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 30, "address_type_p2sh": 90, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 30, "cashaddr_prefix": null, "coin_label": "Denarius", "coin_name": "Denarius", "coin_shortcut": "DNR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/carsenk/denarius", "hash_genesis_block": "00000d5dbbda01621cfc16bbc1f9bf3264d641a5dbf0de89fd0182c2c4828fcd", "key": "bitcoin:DNR", "maintainer": "carsenk ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 10000, "name": "Denarius", "segwit": false, "shortcut": "DNR", "signed_message_header": "Denarius Signed Message:\n", "slip44": 116, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "denarius", "website": "https://denarius.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 30, "address_type_p2sh": 63, "bech32_prefix": "dgb", "bip115": false, "bitcore": [], "blockbook": ["https://dgb1.trezor.io", "https://dgb2.trezor.io"], "blocktime_seconds": 15, "cashaddr_prefix": null, "coin_label": "DigiByte", "coin_name": "DigiByte", "coin_shortcut": "DGB", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/digibyte/digibyte", "hash_genesis_block": "7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496", "key": "bitcoin:DGB", "maintainer": "DigiByte ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "DigiByte", "segwit": true, "shortcut": "DGB", "signed_message_header": "DigiByte Signed Message:\n", "slip44": 20, "support": {"connect": true, "trezor1": "1.6.3", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "digibyte", "website": "https://digibyte.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 30, "address_type_p2sh": 22, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://doge1.trezor.io", "https://doge2.trezor.io", "https://doge3.trezor.io", "https://doge4.trezor.io", "https://doge5.trezor.io"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Dogecoin", "coin_name": "Dogecoin", "coin_shortcut": "DOGE", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 100000}, "dust_limit": 10000000, "force_bip143": false, "fork_id": null, "github": "https://github.com/dogecoin/dogecoin", "hash_genesis_block": "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691", "key": "bitcoin:DOGE", "maintainer": "Karel Bilek ", "max_address_length": 34, "maxfee_kb": 1000000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Dogecoin", "segwit": false, "shortcut": "DOGE", "signed_message_header": "Dogecoin Signed Message:\n", "slip44": 3, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "dogecoin", "website": "https://dogecoin.com", "xprv_magic": 49988504, "xpub_magic": 49990397, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 95, "address_type_p2sh": 36, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 180, "cashaddr_prefix": null, "coin_label": "FairCoin", "coin_name": "FairCoin", "coin_shortcut": "FAIR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 800000}, "dust_limit": 500000, "force_bip143": false, "fork_id": 0, "github": "https://github.com/faircoin/faircoin", "hash_genesis_block": "beed44fa5e96150d95d56ebd5d2625781825a9407a5215dd7eda723373a0a1d7", "key": "bitcoin:FAIR", "maintainer": "Santi Nore\u00f1a ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "FairCoin", "segwit": false, "shortcut": "FAIR", "signed_message_header": "FairCoin Signed Message:\n", "slip44": 298, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "faircoin", "website": "https://www.faircoin.world", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 14, "address_type_p2sh": 5, "bech32_prefix": "fc", "bip115": false, "bitcore": ["https://bitcore.feathercoin.com"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Feathercoin", "coin_name": "Feathercoin", "coin_shortcut": "FTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "duplicate": true, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/FeatherCoin/Feathercoin", "hash_genesis_block": "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2", "key": "bitcoin:FTC", "maintainer": "Lucas Betschart ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Feathercoin", "segwit": true, "shortcut": "FTC", "signed_message_header": "Feathercoin Signed Message:\n", "slip44": 8, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "feathercoin", "website": "https://feathercoin.com", "xprv_magic": 76077806, "xpub_magic": 76069926, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 35, "address_type_p2sh": 94, "bech32_prefix": "flo", "bip115": false, "bitcore": ["https://livenet.flocha.in"], "blockbook": [], "blocktime_seconds": 40, "cashaddr_prefix": null, "coin_label": "Flo", "coin_name": "Florincoin", "coin_shortcut": "FLO", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/floblockchain/flo", "hash_genesis_block": "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea", "key": "bitcoin:FLO", "maintainer": "Robert English ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Flo", "segwit": true, "shortcut": "FLO", "signed_message_header": "Florincoin Signed Message:\n", "slip44": 216, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "florincoin", "website": "https://flo.cash", "xprv_magic": 15264107, "xpub_magic": 1526049, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 28471030}, {"address_type": 36, "address_type_p2sh": 16, "bech32_prefix": "fc", "bip115": false, "bitcore": [], "blockbook": ["https://explorer.fujicoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Fujicoin", "coin_name": "Fujicoin", "coin_shortcut": "FJC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 20000, "High": 100000, "Low": 10000, "Normal": 50000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/fujicoin/fujicoin", "hash_genesis_block": "adb6d9cfd74075e7f91608add4bd2a2ea636f70856183086842667a1597714a0", "key": "bitcoin:FJC", "maintainer": "motty ", "max_address_length": 34, "maxfee_kb": 1000000000, "min_address_length": 27, "minfee_kb": 10000000, "name": "Fujicoin", "segwit": true, "shortcut": "FJC", "signed_message_header": "FujiCoin Signed Message:\n", "slip44": 75, "support": {"connect": true, "trezor1": "1.6.1", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "fujicoin", "website": "https://fujicoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 38, "address_type_p2sh": 10, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.gincoin.io"], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "GIN", "coin_name": "Gincoin", "coin_shortcut": "GIN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/gincoin-dev/gincoin-core", "hash_genesis_block": "00000cd6bde619b2c3b23ad2e384328a450a37fa28731debf748c3b17f91f97d", "key": "bitcoin:GIN", "maintainer": "Dragos Badea ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "GIN", "segwit": false, "shortcut": "GIN", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 2000, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "gincoin", "website": "https://gincoin.io", "xprv_magic": 50221816, "xpub_magic": 50221772, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 38, "address_type_p2sh": 62, "bech32_prefix": "game", "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.gamecredits.network"], "blocktime_seconds": 90, "cashaddr_prefix": null, "coin_label": "GameCredits", "coin_name": "GameCredits", "coin_shortcut": "GAME", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/gamecredits-project/gamecredits", "hash_genesis_block": "91ec5f25ee9a0ffa1af7d4da4db9a552228dd2dc77cdb15b738be4e1f55f30ee", "key": "bitcoin:GAME", "maintainer": "Samad Sajanlal ", "max_address_length": 34, "maxfee_kb": 5000000, "min_address_length": 27, "minfee_kb": 100000, "name": "GameCredits", "segwit": true, "shortcut": "GAME", "signed_message_header": "GameCredits Signed Message:\n", "slip44": 101, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "gamecredits", "website": "https://gamecredits.org", "xprv_magic": 27108450, "xpub_magic": 27106558, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 28471030}, {"address_type": 36, "address_type_p2sh": 5, "bech32_prefix": "grs", "bip115": false, "bitcore": ["https://groestlsight.groestlcoin.org", "https://grsblocks.com"], "blockbook": ["https://blockbook.groestlcoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Groestlcoin", "coin_name": "Groestlcoin", "coin_shortcut": "GRS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_groestl", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Groestlcoin/groestlcoin", "hash_genesis_block": "00000ac5927c594d49cc0bdb81759d0da8297eb614683d3acb62f0703b639023", "key": "bitcoin:GRS", "maintainer": "Yura Pakhuchiy ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Groestlcoin", "segwit": true, "shortcut": "GRS", "signed_message_header": "GroestlCoin Signed Message:\n", "slip44": 17, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "groestlcoin", "website": "https://www.groestlcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "tgrs", "bip115": false, "bitcore": ["https://groestlsight-test.groestlcoin.org"], "blockbook": ["https://blockbook-test.groestlcoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Groestlcoin Testnet", "coin_name": "Groestlcoin Testnet", "coin_shortcut": "tGRS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_groestl", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Groestlcoin/groestlcoin", "hash_genesis_block": "000000ffbb50fc9898cdd36ec163e6ba23230164c0052a28876255b7dcf2cd36", "key": "bitcoin:tGRS", "maintainer": "Yura Pakhuchiy ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Groestlcoin Testnet", "segwit": true, "shortcut": "tGRS", "signed_message_header": "GroestlCoin Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "groestlcoin", "website": "https://www.groestlcoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 8329, "address_type_p2sh": 8342, "bech32_prefix": null, "bip115": true, "bitcore": ["https://explorer.horizen.global"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Horizen", "coin_name": "Horizen", "coin_shortcut": "ZEN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/ZencashOfficial/zen", "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602", "key": "bitcoin:ZEN", "maintainer": "Power_VANO ", "max_address_length": 95, "maxfee_kb": 2000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Horizen", "segwit": false, "shortcut": "ZEN", "signed_message_header": "Zcash Signed Message:\n", "slip44": 121, "support": {"connect": true, "trezor1": false, "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "horizen", "website": "https://www.horizen.global", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 60, "address_type_p2sh": 85, "bech32_prefix": null, "bip115": false, "bitcore": ["https://api.kmd.dev"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Komodo", "coin_name": "Komodo", "coin_shortcut": "KMD", "consensus_branch_id": {"1": 0, "2": 0, "3": 1537743641, "4": 1991772603}, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/komodoplatform/komodo", "hash_genesis_block": "027e3758c3a65b12aa1046462b486d0a63bfa1beae327897f56c5cfb7daaae71", "key": "bitcoin:KMD", "maintainer": "Kadan Stadelmann ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Komodo", "segwit": false, "shortcut": "KMD", "signed_message_header": "Komodo Signed Message:\n", "slip44": 141, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "komodo", "website": "https://komodoplatform.com", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 6198, "address_type_p2sh": 6203, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.kotocoin.info"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Koto", "coin_name": "Koto", "coin_shortcut": "KOTO", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/KotoDevelopers/koto", "hash_genesis_block": "6d424c350729ae633275d51dc3496e16cd1b1d195c164da00f39c499a2e9959e", "key": "bitcoin:KOTO", "maintainer": "WO ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Koto", "segwit": false, "shortcut": "KOTO", "signed_message_header": "Koto Signed Message:\n", "slip44": 510, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "koto", "website": "https://ko-to.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 48, "address_type_p2sh": 50, "bech32_prefix": "ltc", "bip115": false, "bitcore": [], "blockbook": ["https://ltc1.trezor.io", "https://ltc2.trezor.io", "https://ltc3.trezor.io", "https://ltc4.trezor.io", "https://ltc5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Litecoin", "coin_name": "Litecoin", "coin_shortcut": "LTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/litecoin-project/litecoin", "hash_genesis_block": "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2", "key": "bitcoin:LTC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Litecoin", "segwit": true, "shortcut": "LTC", "signed_message_header": "Litecoin Signed Message:\n", "slip44": 2, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "litecoin", "website": "https://litecoin.org", "xprv_magic": 27106558, "xpub_magic": 27108450, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 28471030}, {"address_type": 111, "address_type_p2sh": 58, "bech32_prefix": "tltc", "bip115": false, "bitcore": ["https://testnet.litecore.io"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Litecoin Testnet", "coin_name": "Litecoin Testnet", "coin_shortcut": "tLTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/litecoin-project/litecoin", "hash_genesis_block": "4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0", "key": "bitcoin:tLTC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Litecoin Testnet", "segwit": true, "shortcut": "tLTC", "signed_message_header": "Litecoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "litecoin", "website": "https://litecoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 50, "address_type_p2sh": 5, "bech32_prefix": "mec", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Megacoin", "coin_name": "Megacoin", "coin_shortcut": "MEC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/Megacoin", "hash_genesis_block": "7520788e2d99eec7cf6cf7315577e1268e177fff94cb0a7caf6a458ceeea9ac2", "key": "bitcoin:MEC", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Megacoin", "segwit": true, "shortcut": "MEC", "signed_message_header": "MegaCoin Signed Message:\n", "slip44": 217, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "megacoin", "website": "https://www.megacoin.eu", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 50, "address_type_p2sh": 55, "bech32_prefix": "mona", "bip115": false, "bitcore": ["https://mona.chainsight.info", "https://insight.electrum-mona.org"], "blockbook": ["https://blockbook.electrum-mona.org"], "blocktime_seconds": 90, "cashaddr_prefix": null, "coin_label": "Monacoin", "coin_name": "Monacoin", "coin_shortcut": "MONA", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/monacoinproject/monacoin", "hash_genesis_block": "ff9f1c0116d19de7c9963845e129f9ed1bfc0b376eb54fd7afa42e0d418c8bb6", "key": "bitcoin:MONA", "maintainer": "cryptcoin-junkey ", "max_address_length": 34, "maxfee_kb": 5000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Monacoin", "segwit": true, "shortcut": "MONA", "signed_message_header": "Monacoin Signed Message:\n", "slip44": 22, "support": {"connect": true, "trezor1": "1.6.0", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "monacoin", "website": "https://monacoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 16, "address_type_p2sh": 76, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.monetaryunit.org"], "blocktime_seconds": 40, "cashaddr_prefix": null, "coin_label": "MonetaryUnit", "coin_name": "MonetaryUnit", "coin_shortcut": "MUE", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/muecoin/MUE", "hash_genesis_block": "0b58ed450b3819ca54ab0054c4d220ca4f887d21c9e55d2a333173adf76d987f", "key": "bitcoin:MUE", "maintainer": "Sotiris Blad ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "MonetaryUnit", "segwit": false, "shortcut": "MUE", "signed_message_header": "MonetaryUnit Signed Message:\n", "slip44": 31, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "monetaryunit", "website": "https://www.monetaryunit.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 50, "address_type_p2sh": 9, "bech32_prefix": "my", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Myriad", "coin_name": "Myriad", "coin_shortcut": "XMY", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/myriadteam/myriadcoin", "hash_genesis_block": "00000ffde4c020b5938441a0ea3d314bf619eff0b38f32f78f7583cffa1ea485", "key": "bitcoin:XMY", "maintainer": "Adam Hickerson ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Myriad", "segwit": true, "shortcut": "XMY", "signed_message_header": "Myriadcoin Signed Message:\n", "slip44": 90, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "myriadcoin", "website": "https://www.myriadcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 38, "address_type_p2sh": 53, "bech32_prefix": "nix", "bip115": false, "bitcore": ["https://blockchain.nixplatform.io"], "blockbook": [], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "NIX", "coin_name": "NIX", "coin_shortcut": "NIX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/nixplatform/nixcore", "hash_genesis_block": "dd28ad86def767c3cfc34267a950d871fc7462bc57ea4a929fc3596d9b598e41", "key": "bitcoin:NIX", "maintainer": "mattt21 ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 0, "name": "NIX", "segwit": true, "shortcut": "NIX", "signed_message_header": "NIX Signed Message:\n", "slip44": 400, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "nix", "website": "https://nixplatform.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 52, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://nmc1.trezor.io", "https://nmc2.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Namecoin", "coin_name": "Namecoin", "coin_shortcut": "NMC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 2940, "force_bip143": false, "fork_id": null, "github": "https://github.com/namecoin/namecoin-core", "hash_genesis_block": "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770", "key": "bitcoin:NMC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Namecoin", "segwit": false, "shortcut": "NMC", "signed_message_header": "Namecoin Signed Message:\n", "slip44": 7, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "namecoin", "website": "https://namecoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 30, "address_type_p2sh": 13, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.pivx.link"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "PIVX", "coin_name": "PIVX", "coin_shortcut": "PIVX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/PIVX-Project/PIVX", "hash_genesis_block": "0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818", "key": "bitcoin:PIVX", "maintainer": "Random Zebra ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 100, "name": "PIVX", "segwit": false, "shortcut": "PIVX", "signed_message_header": "DarkNet Signed Message:\n", "slip44": 119, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "pivx", "website": "https://pivx.org", "xprv_magic": 35729707, "xpub_magic": 36513075, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 139, "address_type_p2sh": 19, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook-testnet.pivx.link"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "PIVX Testnet", "coin_name": "PIVX Testnet", "coin_shortcut": "tPIVX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/PIVX-Project/PIVX", "hash_genesis_block": "0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818", "key": "bitcoin:tPIVX", "maintainer": "Random Zebra ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 100, "name": "PIVX Testnet", "segwit": false, "shortcut": "tPIVX", "signed_message_header": "DarkNet Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "pivx", "website": "https://pivx.org", "xprv_magic": 981489719, "xpub_magic": 981492128, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 47, "address_type_p2sh": 22, "bech32_prefix": null, "bip115": false, "bitcore": ["https://live.pesetacoin.info"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Pesetacoin", "coin_name": "Pesetacoin", "coin_shortcut": "PTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "duplicate": true, "dust_limit": 10000000, "force_bip143": false, "fork_id": null, "github": "https://github.com/FundacionPesetacoin/PesetacoinCore", "hash_genesis_block": "edfe5830b53251bfff733600b1cd5c192e761c011b055f07924634818c906438", "key": "bitcoin:PTC", "maintainer": "Rw ", "max_address_length": 34, "maxfee_kb": 1000000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Pesetacoin", "segwit": false, "shortcut": "PTC", "signed_message_header": "Pesetacoin Signed Message:\n", "slip44": 109, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "pesetacoin", "website": "https://pesetacoin.info", "xprv_magic": 76079604, "xpub_magic": 76071982, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 55, "address_type_p2sh": 56, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.polispay.org"], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "Polis", "coin_name": "Polis", "coin_shortcut": "POLIS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/polispay/polis", "hash_genesis_block": "000009701eb781a8113b1af1d814e2f060f6408a2c990db291bc5108a1345c1e", "key": "bitcoin:POLIS", "maintainer": "Cronos ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Polis", "segwit": false, "shortcut": "POLIS", "signed_message_header": "Polis Signed Message:\n", "slip44": 1997, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "polis", "website": "https://www.polispay.org", "xprv_magic": 65165637, "xpub_magic": 65166718, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 23, "address_type_p2sh": 83, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Primecoin", "coin_name": "Primecoin", "coin_shortcut": "XPM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/primecoin/primecoin", "hash_genesis_block": "963d17ba4dc753138078a2f56afb3af9674e2546822badff26837db9a0152106", "key": "bitcoin:XPM", "maintainer": "James Skrowvedeht ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 26, "minfee_kb": 1000, "name": "Primecoin", "segwit": false, "shortcut": "XPM", "signed_message_header": "Primecoin Signed Message:\n", "slip44": 24, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "primecoin", "website": "https://primecoin.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 58, "address_type_p2sh": 50, "bech32_prefix": "qc", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 128, "cashaddr_prefix": null, "coin_label": "Qtum", "coin_name": "Qtum", "coin_shortcut": "QTUM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 500, "High": 2000, "Low": 410, "Normal": 600}, "duplicate": true, "dust_limit": 218400, "force_bip143": false, "fork_id": null, "github": "https://github.com/qtumproject/qtum", "hash_genesis_block": "000075aef83cf2853580f8ae8ce6f8c3096cfa21d98334d6e3f95e5582ed986c", "key": "bitcoin:QTUM", "maintainer": "CodeFace ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 400000, "name": "Qtum", "segwit": true, "shortcut": "QTUM", "signed_message_header": "Qtum Signed Message:\n", "slip44": 2301, "support": {"connect": false, "trezor1": "1.8.1", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "qtum", "website": "https://qtum.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 120, "address_type_p2sh": 110, "bech32_prefix": "tq", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 128, "cashaddr_prefix": null, "coin_label": "Qtum Testnet", "coin_name": "Qtum Testnet", "coin_shortcut": "tQTUM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 500, "High": 2000, "Low": 410, "Normal": 600}, "dust_limit": 218400, "force_bip143": false, "fork_id": null, "github": "https://github.com/qtumproject/qtum", "hash_genesis_block": "0000e803ee215c0684ca0d2f9220594d3f828617972aad66feb2ba51f5e14222", "key": "bitcoin:tQTUM", "maintainer": "CodeFace ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 400000, "name": "Qtum Testnet", "segwit": true, "shortcut": "tQTUM", "signed_message_header": "Qtum Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.8.1", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "qtum", "website": "https://qtum.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 60, "address_type_p2sh": 122, "bech32_prefix": null, "bip115": false, "bitcore": ["https://ravencoin.network"], "blockbook": ["https://blockbook.ravencoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Ravencoin", "coin_name": "Ravencoin", "coin_shortcut": "RVN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/RavenProject/Ravencoin", "hash_genesis_block": "0000006b444bc2f2ffe627be9d9e7e7a0730000870ef6eb6da46c8eae389df90", "key": "bitcoin:RVN", "maintainer": "Scotty ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Ravencoin", "segwit": false, "shortcut": "RVN", "signed_message_header": "Raven Signed Message:\n", "slip44": 175, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": true}, "uri_prefix": "raven", "website": "https://ravencoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "bcrt", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Regtest", "coin_name": "Regtest", "coin_shortcut": "REGTEST", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bitcoin/bitcoin", "hash_genesis_block": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206", "key": "bitcoin:REGTEST", "maintainer": "Thomas Kerin ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Regtest", "segwit": true, "shortcut": "REGTEST", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bitcoin", "website": "https://bitcoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 25, "address_type_p2sh": 105, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.ritocoin.org"], "blockbook": ["https://blockbook.ritocoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Ritocoin", "coin_name": "Ritocoin", "coin_shortcut": "RITO", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/RitoProject", "hash_genesis_block": "00000075e344bdf1c0e433f453764b1830a7aa19b2a5213e707502a22b779c1b", "key": "bitcoin:RITO", "maintainer": "Scotty ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Ritocoin", "segwit": false, "shortcut": "RITO", "signed_message_header": "Rito Signed Message:\n", "slip44": 19169, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "rito", "website": "https://ritocoin.org", "xprv_magic": 87326380, "xpub_magic": 87353290, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 63, "address_type_p2sh": 18, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.smartcash.cc"], "blockbook": [], "blocktime_seconds": 55, "cashaddr_prefix": null, "coin_label": "SmartCash", "coin_name": "SmartCash", "coin_shortcut": "SMART", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_smart", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "duplicate": true, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/SmartCash/Core-Smart", "hash_genesis_block": "000007acc6970b812948d14ea5a0a13db0fdd07d5047c7e69101fa8b361e05a4", "key": "bitcoin:SMART", "maintainer": "Leandro Reinaux ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "SmartCash", "segwit": false, "shortcut": "SMART", "signed_message_header": "SmartCash Signed Message:\n", "slip44": 224, "support": {"connect": false, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "smart", "website": "https://smartcash.cc", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 65, "address_type_p2sh": 21, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 55, "cashaddr_prefix": null, "coin_label": "SmartCash Testnet", "coin_name": "SmartCash Testnet", "coin_shortcut": "tSMART", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_smart", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/SmartCash/Core-Smart", "hash_genesis_block": "0000027235b5679bcd28c90d03d4bf1a9ba4c07c4efcc1c87d6c68cce25e6e5d", "key": "bitcoin:tSMART", "maintainer": "Leandro Reinaux ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "SmartCash Testnet", "segwit": false, "shortcut": "tSMART", "signed_message_header": "SmartCash Signed Message:\n", "slip44": 224, "support": {"connect": false, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "testsmart", "website": "https://smartcash.cc", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 76, "address_type_p2sh": 16, "bech32_prefix": "xc", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Stakenet", "coin_name": "Stakenet", "coin_shortcut": "XSN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 1000, "force_bip143": false, "fork_id": null, "github": "https://github.com/X9Developers/XSN", "hash_genesis_block": "00000c822abdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34", "key": "bitcoin:XSN", "maintainer": "Alexis Hernandez ", "max_address_length": 47, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Stakenet", "segwit": true, "shortcut": "XSN", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 199, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "stakenet", "website": "https://stakenet.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 0, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.terracoin.io"], "blockbook": [], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "Terracoin", "coin_name": "Terracoin", "coin_shortcut": "TRC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "duplicate": true, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/terracoin/terracoin", "hash_genesis_block": "00000000804bbc6a621a9dbb564ce469f492e1ccf2d70f8a6b241e26a277afa2", "key": "bitcoin:TRC", "maintainer": "The Terracoin Foundation ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 10000, "name": "Terracoin", "segwit": false, "shortcut": "TRC", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 83, "support": {"connect": false, "trezor1": false, "trezor2": false, "webwallet": false}, "uri_prefix": "terracoin", "website": "https://terracoin.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 70, "address_type_p2sh": 50, "bech32_prefix": "vips", "bip115": false, "bitcore": ["https://insight.vipstarco.in"], "blockbook": ["https://vips.blockbook.japanesecoin-pool.work"], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "VIPSTARCOIN", "coin_name": "VIPSTARCOIN", "coin_shortcut": "VIPS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 500, "High": 2000, "Low": 410, "Normal": 600}, "dust_limit": 218400, "force_bip143": false, "fork_id": null, "github": "https://github.com/VIPSTARCOIN/VIPSTARCOIN", "hash_genesis_block": "0000d068e1d30f79fb64446137106be9c6ee69a6a722295c131506b1ee09b77c", "key": "bitcoin:VIPS", "maintainer": "y-chan ", "max_address_length": 36, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 400000, "name": "VIPSTARCOIN", "segwit": true, "shortcut": "VIPS", "signed_message_header": "VIPSTARCOIN Signed Message:\n", "slip44": 1919, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "vipstarcoin", "website": "https://vipstarcoin.jp", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 71, "address_type_p2sh": 5, "bech32_prefix": "vtc", "bip115": false, "bitcore": [], "blockbook": ["https://vtc1.trezor.io", "https://vtc2.trezor.io", "https://vtc3.trezor.io", "https://vtc4.trezor.io", "https://vtc5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Vertcoin", "coin_name": "Vertcoin", "coin_shortcut": "VTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/vertcoin-project/vertcoin-core", "hash_genesis_block": "4d96a915f49d40b1e5c2844d1ee2dccb90013a990ccea12c492d22110489f0c4", "key": "bitcoin:VTC", "maintainer": "Jochen Hoenicke ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Vertcoin", "segwit": true, "shortcut": "VTC", "signed_message_header": "Vertcoin Signed Message:\n", "slip44": 28, "support": {"connect": true, "trezor1": "1.6.1", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "vertcoin", "website": "https://vertcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 71, "address_type_p2sh": 33, "bech32_prefix": "via", "bip115": false, "bitcore": ["https://explorer.viacoin.org"], "blockbook": [], "blocktime_seconds": 24, "cashaddr_prefix": null, "coin_label": "Viacoin", "coin_name": "Viacoin", "coin_shortcut": "VIA", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 7000, "High": 20000, "Low": 1000, "Normal": 14000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/viacoin", "hash_genesis_block": "4e9b54001f9976049830128ec0331515eaabe35a70970d79971da1539a400ba1", "key": "bitcoin:VIA", "maintainer": "romanornr ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Viacoin", "segwit": true, "shortcut": "VIA", "signed_message_header": "Viacoin Signed Message:\n", "slip44": 14, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "viacoin", "website": "https://viacoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 7352, "address_type_p2sh": 7357, "bech32_prefix": null, "bip115": false, "bitcore": ["https://explorer.zcl.zeltrez.io"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "ZClassic", "coin_name": "ZClassic", "coin_shortcut": "ZCL", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/ZclassicCommunity", "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602", "key": "bitcoin:ZCL", "maintainer": "James Skrowvedeht ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "ZClassic", "segwit": false, "shortcut": "ZCL", "signed_message_header": "Zcash Signed Message:\n", "slip44": 147, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "zclassic", "website": "https://zclassic.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 7352, "address_type_p2sh": 7357, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://zec1.trezor.io", "https://zec2.trezor.io", "https://zec3.trezor.io", "https://zec4.trezor.io", "https://zec5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Zcash", "coin_name": "Zcash", "coin_shortcut": "ZEC", "consensus_branch_id": {"1": 0, "2": 0, "3": 1537743641, "4": 1991772603}, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcash/zcash", "hash_genesis_block": "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08", "key": "bitcoin:ZEC", "maintainer": "Pavol Rusnak ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Zcash", "segwit": false, "shortcut": "ZEC", "signed_message_header": "Zcash Signed Message:\n", "slip44": 133, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "zcash", "website": "https://z.cash", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 7461, "address_type_p2sh": 7354, "bech32_prefix": null, "bip115": false, "bitcore": ["https://explorer.testnet.z.cash"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Zcash Testnet", "coin_name": "Zcash Testnet", "coin_shortcut": "TAZ", "consensus_branch_id": {"1": 0, "2": 0, "3": 1537743641, "4": 1991772603}, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcash/zcash", "hash_genesis_block": "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38", "key": "bitcoin:TAZ", "maintainer": "Pavol Rusnak ", "max_address_length": 95, "maxfee_kb": 10000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Zcash Testnet", "segwit": false, "shortcut": "TAZ", "signed_message_header": "Zcash Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "zcash", "website": "https://z.cash", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 82, "address_type_p2sh": 7, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.zcoin.io"], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Zcoin", "coin_name": "Zcoin", "coin_shortcut": "XZC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcoinofficial/zcoin", "hash_genesis_block": "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233", "key": "bitcoin:XZC", "maintainer": "Yura Pakhuchiy ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "Zcoin", "segwit": false, "shortcut": "XZC", "signed_message_header": "Zcoin Signed Message:\n", "slip44": 136, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "zcoin", "website": "https://zcoin.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 65, "address_type_p2sh": 178, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Zcoin Testnet", "coin_name": "Zcoin Testnet", "coin_shortcut": "tXZC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcoinofficial/zcoin", "hash_genesis_block": "7ac038c193c2158c428c59f9ae0c02a07115141c6e9dc244ae96132e99b4e642", "key": "bitcoin:tXZC", "maintainer": "Yura Pakhuchiy ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "Zcoin Testnet", "segwit": false, "shortcut": "tXZC", "signed_message_header": "Zcoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": false}, "uri_prefix": "testzcoin", "website": "https://zcoin.io", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}] +[{"address_type": 0, "address_type_p2sh": 5, "bech32_prefix": "bc", "bip115": false, "bitcore": [], "blockbook": ["https://btc1.trezor.io", "https://btc2.trezor.io", "https://btc3.trezor.io", "https://btc4.trezor.io", "https://btc5.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin", "coin_name": "Bitcoin", "coin_shortcut": "BTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bitcoin/bitcoin", "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "key": "bitcoin:BTC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin", "segwit": true, "shortcut": "BTC", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 0, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://bitcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "tb", "bip115": false, "bitcore": [], "blockbook": ["https://tbtc1.trezor.io", "https://tbtc2.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Testnet", "coin_name": "Testnet", "coin_shortcut": "TEST", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bitcoin/bitcoin", "hash_genesis_block": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", "key": "bitcoin:TEST", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Testnet", "segwit": true, "shortcut": "TEST", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://bitcoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 53, "address_type_p2sh": 55, "bech32_prefix": "acm", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Actinium", "coin_name": "Actinium", "coin_shortcut": "ACM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Actinium-project/Actinium", "hash_genesis_block": "28d77872e23714562f49a1be792c276623c1bbe3fdcf21b6035cfde78b00b824", "key": "bitcoin:ACM", "maintainer": "Harris Brakmic ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Actinium", "segwit": true, "shortcut": "ACM", "signed_message_header": "Actinium Signed Message:\n", "slip44": 228, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": true}, "uri_prefix": "actinium", "website": "https://actinium.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 55, "address_type_p2sh": 16, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Axe", "coin_name": "Axe", "coin_shortcut": "AXE", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/axerunners/axe", "hash_genesis_block": "00000c33631ca6f2f61368991ce2dc03306b5bb50bf7cede5cfbba6db38e52e6", "key": "bitcoin:AXE", "maintainer": "Kirill Orlov ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Axe", "segwit": false, "shortcut": "AXE", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 4242, "support": {"connect": true, "trezor1": "1.7.3", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "axe", "website": "https://axerunners.com", "xprv_magic": 50221816, "xpub_magic": 50221772, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 25, "address_type_p2sh": 85, "bech32_prefix": "bm", "bip115": false, "bitcore": [], "blockbook": ["https://bellcoin-blockbook.ilmango.work", "https://bell.blockbook.ovh"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Bellcoin", "coin_name": "Bellcoin", "coin_shortcut": "BELL", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 20}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bellcoin-org/bellcoin", "hash_genesis_block": "000008f3b6bd10c2d03b06674a006b8d9731f6cb58179ef1eee008cee2209603", "key": "bitcoin:BELL", "maintainer": "ilmango-doge ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bellcoin", "segwit": true, "shortcut": "BELL", "signed_message_header": "Bellcoin Signed Message:\n", "slip44": 25252, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bellcoin", "website": "https://bellcoin.web4u.jp", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 230, "address_type_p2sh": 235, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "BitCash", "coin_name": "BitCash", "coin_shortcut": "BITC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/WillyTheCat/BitCash", "hash_genesis_block": "7d57d87ff3c15a521530af60edee1887fba9c193eb518face926785c4cd8f4f1", "key": "bitcoin:BITC", "maintainer": "Christian Kassler ", "max_address_length": 53, "maxfee_kb": 30000000, "min_address_length": 52, "minfee_kb": 1000, "name": "BitCash", "segwit": false, "shortcut": "BITC", "signed_message_header": "Bitcash Signed Message:\n", "slip44": 230, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "bitcash", "website": "https://www.choosebitcash.com", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 81, "address_type_p2sh": 5, "bech32_prefix": "bz", "bip115": false, "bitcore": ["https://insight.bitzeny.jp", "https://zeny.insight.monaco-ex.org"], "blockbook": ["https://zny.blockbook.ovh"], "blocktime_seconds": 90, "cashaddr_prefix": null, "coin_label": "BitZeny", "coin_name": "BitZeny", "coin_shortcut": "ZNY", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 20}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/BitzenyCoreDevelopers/bitzeny", "hash_genesis_block": "000009f7e55e9e3b4781e22bd87a7cfa4acada9e4340d43ca738bf4e9fb8f5ce", "key": "bitcoin:ZNY", "maintainer": "y-chan ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "BitZeny", "segwit": true, "shortcut": "ZNY", "signed_message_header": "BitZeny Signed Message:\n", "slip44": 123, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bitzeny", "website": "https://bitzeny.tech", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 25, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 300, "cashaddr_prefix": null, "coin_label": "Bitcloud", "coin_name": "Bitcloud", "coin_shortcut": "BTDX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/Bitcloud", "hash_genesis_block": "000002d56463941c20eae5cb474cc805b646515d18bc7dc222a0885b206eadb0", "key": "bitcoin:BTDX", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 10000, "name": "Bitcloud", "segwit": false, "shortcut": "BTDX", "signed_message_header": "Diamond Signed Message:\n", "slip44": 218, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "bitcloud", "website": "https://bit-cloud.info", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 0, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://bch1.trezor.io", "https://bch2.trezor.io", "https://bch3.trezor.io", "https://bch4.trezor.io", "https://bch5.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": "bitcoincash", "coin_label": "Bitcoin Cash", "coin_name": "Bcash", "coin_shortcut": "BCH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": true, "fork_id": 0, "github": "https://github.com/Bitcoin-ABC/bitcoin-abc", "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "key": "bitcoin:BCH", "maintainer": "Jochen Hoenicke ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Cash", "segwit": false, "shortcut": "BCH", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 145, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "bitcoincash", "website": "https://www.bitcoincash.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": "bchtest", "coin_label": "Bitcoin Cash Testnet", "coin_name": "Bcash Testnet", "coin_shortcut": "TBCH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": true, "fork_id": 0, "github": "https://github.com/Bitcoin-ABC/bitcoin-abc", "hash_genesis_block": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", "key": "bitcoin:TBCH", "maintainer": "Jochen Hoenicke ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Cash Testnet", "segwit": false, "shortcut": "TBCH", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": false}, "uri_prefix": "bitcoincash", "website": "https://www.bitcoincash.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 38, "address_type_p2sh": 23, "bech32_prefix": "btg", "bip115": false, "bitcore": [], "blockbook": ["https://btg1.trezor.io", "https://btg2.trezor.io", "https://btg3.trezor.io", "https://btg4.trezor.io", "https://btg5.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin Gold", "coin_name": "Bgold", "coin_shortcut": "BTG", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": true, "fork_id": 79, "github": "https://github.com/BTCGPU/BTCGPU", "hash_genesis_block": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "key": "bitcoin:BTG", "maintainer": "Saleem Rashid ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Gold", "segwit": true, "shortcut": "BTG", "signed_message_header": "Bitcoin Gold Signed Message:\n", "slip44": 156, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "bitcoingold", "website": "https://bitcoingold.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "tbtg", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin Gold Testnet", "coin_name": "Bgold Testnet", "coin_shortcut": "TBTG", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": true, "fork_id": 79, "github": "https://github.com/BTCGPU/BTCGPU", "hash_genesis_block": "00000000e0781ebe24b91eedc293adfea2f557b53ec379e78959de3853e6f9f6", "key": "bitcoin:TBTG", "maintainer": "The Bitcoin Gold Developers ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Gold Testnet", "segwit": true, "shortcut": "TBTG", "signed_message_header": "Bitcoin Gold Signed Message:\n", "slip44": 156, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "bitcoingold", "website": "https://bitcoingold.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 4901, "address_type_p2sh": 5039, "bech32_prefix": null, "bip115": false, "bitcore": ["https://explorer.btcprivate.org"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Bitcoin Private", "coin_name": "Bprivate", "coin_shortcut": "BTCP", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": 42, "github": "https://github.com/BTCPrivate/BitcoinPrivate", "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602", "key": "bitcoin:BTCP", "maintainer": "Chris Sulmone ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Bitcoin Private", "segwit": false, "shortcut": "BTCP", "signed_message_header": "BitcoinPrivate Signed Message:\n", "slip44": 183, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "bitcoinprivate", "website": "https://btcprivate.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 61, "address_type_p2sh": 123, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook1.bitcoinrh.org", "https://blockbook2.bitcoinrh.org"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Bitcoin Rhodium", "coin_name": "Brhodium", "coin_shortcut": "XRC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://gitlab.com/bitcoinrh/BRhodiumNode", "hash_genesis_block": "baff5bfd9dc43fb672d003ec20fd21428f9282ca46bfa1730d73e1f2c75f5fdd", "key": "bitcoin:XRC", "maintainer": "baff5b ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcoin Rhodium", "segwit": false, "shortcut": "XRC", "signed_message_header": "BitCoin Rhodium Signed Message:\n", "slip44": 10291, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bitcoin-rhodium", "website": "https://www.bitcoinrh.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 3, "address_type_p2sh": 125, "bech32_prefix": "btx", "bip115": false, "bitcore": ["https://insight.bitcore.cc"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Bitcore", "coin_name": "Bitcore", "coin_shortcut": "BTX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/BitCore", "hash_genesis_block": "604148281e5c4b7f2487e5d03cd60d8e6f69411d613f6448034508cea52e9574", "key": "bitcoin:BTX", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Bitcore", "segwit": true, "shortcut": "BTX", "signed_message_header": "BitCore Signed Message:\n", "slip44": 160, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "bitcore", "website": "https://bitcore.cc", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 102, "address_type_p2sh": 5, "bech32_prefix": "bsd", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 200, "cashaddr_prefix": null, "coin_label": "Bitsend", "coin_name": "Bitsend", "coin_shortcut": "BSD", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/BitSend", "hash_genesis_block": "0000012e1b8843ac9ce8c18603658eaf8895f99d3f5e7e1b7b1686f35e3c087a", "key": "bitcoin:BSD", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 10000, "name": "Bitsend", "segwit": true, "shortcut": "BSD", "signed_message_header": "Bitsend Signed Message:\n", "slip44": 91, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "bitsend", "website": "https://bitsend.info", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 26, "address_type_p2sh": 5, "bech32_prefix": "bst", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "BlockStamp", "coin_name": "BlockStamp", "coin_shortcut": "BST", "consensus_branch_id": null, "cooldown": 1000, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "duplicate": true, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/BlockStamp/bst", "hash_genesis_block": "8000000049a2e26b0185be50b4b8ed58b707c8893762959f0b1673641cae1828", "key": "bitcoin:BST", "maintainer": "Krzysztof Kuchta ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "BlockStamp", "segwit": true, "shortcut": "BST", "signed_message_header": "BST Signed Message:\n", "slip44": 254, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "blockstamp", "website": "https://blockstamp.info", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 28, "address_type_p2sh": 35, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.capricoin.org", "https://blockbook2.capricoin.org", "https://blockbook3.capricoin.org", "https://blockbook4.capricoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Capricoin", "coin_name": "Capricoin", "coin_shortcut": "CPC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 7, "High": 20, "Low": 1, "Normal": 14}, "duplicate": true, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Capricoinofficial/Capricoin", "hash_genesis_block": "00000d23fa0fc52c90893adb1181c9ddffb6c797a3e41864b9a23aa2f2981fe3", "key": "bitcoin:CPC", "maintainer": "Tibor Arpas ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Capricoin", "segwit": false, "shortcut": "CPC", "signed_message_header": "Capricoin Signed Message:\n", "slip44": 289, "support": {"connect": true, "trezor1": false, "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "capricoin", "website": "https://capricoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 95495, "address_type_p2sh": 95473, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight-01.crown.tech", "https://insight-02.crown.tech", "https://insight-03.crown.tech"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Crown", "coin_name": "Crown", "coin_shortcut": "CRW", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Crowndev/crowncoin", "hash_genesis_block": "0000000085370d5e122f64f4ab19c68614ff3df78c8d13cb814fd7e69a1dc6da", "key": "bitcoin:CRW", "maintainer": "Ashot ", "max_address_length": 40, "maxfee_kb": 2000000, "min_address_length": 36, "minfee_kb": 1000, "name": "Crown", "segwit": false, "shortcut": "CRW", "signed_message_header": "Crown Signed Message:\n", "slip44": 72, "support": {"connect": false, "trezor1": false, "trezor2": false, "webwallet": false}, "uri_prefix": "crown", "website": "https://crown.tech", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 76, "address_type_p2sh": 16, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://dash1.trezor.io", "https://dash2.trezor.io", "https://dash3.trezor.io", "https://dash4.trezor.io", "https://dash5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Dash", "coin_name": "Dash", "coin_shortcut": "DASH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/dashpay/dash", "hash_genesis_block": "00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6", "key": "bitcoin:DASH", "maintainer": "Karel Bilek ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Dash", "segwit": false, "shortcut": "DASH", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 5, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "dash", "website": "https://www.dash.org", "xprv_magic": 50221816, "xpub_magic": 50221772, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 140, "address_type_p2sh": 19, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Dash Testnet", "coin_name": "Dash Testnet", "coin_shortcut": "tDASH", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/dashpay/dash", "hash_genesis_block": "00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c", "key": "bitcoin:tDASH", "maintainer": "Karel Bilek ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 10000, "name": "Dash Testnet", "segwit": false, "shortcut": "tDASH", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "dash", "website": "https://www.dash.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 1855, "address_type_p2sh": 1818, "bech32_prefix": null, "bip115": false, "bitcore": ["https://mainnet.decred.org"], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Decred", "coin_name": "Decred", "coin_shortcut": "DCR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_decred", "decred": true, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/decred/dcrd", "hash_genesis_block": "298e5cc3d985bfe7f81dc135f360abe089edd4396b86d2de66b0cef42b21d980", "key": "bitcoin:DCR", "maintainer": "Alex Yocom-Piatt ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 10000, "name": "Decred", "segwit": false, "shortcut": "DCR", "signed_message_header": "Decred Signed Message:\n", "slip44": 42, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://www.decred.org", "xprv_magic": 50177256, "xpub_magic": 50178342, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 3873, "address_type_p2sh": 3836, "bech32_prefix": null, "bip115": false, "bitcore": ["https://testnet.decred.org"], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Decred Testnet", "coin_name": "Decred Testnet", "coin_shortcut": "TDCR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_decred", "decred": true, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/decred/dcrd", "hash_genesis_block": "a649dce53918caf422e9c711c858837e08d626ecfcd198969b24f7b634a49bac", "key": "bitcoin:TDCR", "maintainer": "Saleem Rashid ", "max_address_length": 35, "maxfee_kb": 10000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Decred Testnet", "segwit": false, "shortcut": "TDCR", "signed_message_header": "Decred Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "bitcoin", "website": "https://www.decred.org", "xprv_magic": 70615959, "xpub_magic": 70617041, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 30, "address_type_p2sh": 90, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 30, "cashaddr_prefix": null, "coin_label": "Denarius", "coin_name": "Denarius", "coin_shortcut": "DNR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/carsenk/denarius", "hash_genesis_block": "00000d5dbbda01621cfc16bbc1f9bf3264d641a5dbf0de89fd0182c2c4828fcd", "key": "bitcoin:DNR", "maintainer": "carsenk ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 10000, "name": "Denarius", "segwit": false, "shortcut": "DNR", "signed_message_header": "Denarius Signed Message:\n", "slip44": 116, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "denarius", "website": "https://denarius.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 30, "address_type_p2sh": 63, "bech32_prefix": "dgb", "bip115": false, "bitcore": [], "blockbook": ["https://dgb1.trezor.io", "https://dgb2.trezor.io"], "blocktime_seconds": 15, "cashaddr_prefix": null, "coin_label": "DigiByte", "coin_name": "DigiByte", "coin_shortcut": "DGB", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/digibyte/digibyte", "hash_genesis_block": "7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496", "key": "bitcoin:DGB", "maintainer": "DigiByte ", "max_address_length": 34, "maxfee_kb": 500000, "min_address_length": 27, "minfee_kb": 1000, "name": "DigiByte", "segwit": true, "shortcut": "DGB", "signed_message_header": "DigiByte Signed Message:\n", "slip44": 20, "support": {"connect": true, "trezor1": "1.6.3", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "digibyte", "website": "https://digibyte.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 30, "address_type_p2sh": 22, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://doge1.trezor.io", "https://doge2.trezor.io", "https://doge3.trezor.io", "https://doge4.trezor.io", "https://doge5.trezor.io"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Dogecoin", "coin_name": "Dogecoin", "coin_shortcut": "DOGE", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 100000}, "dust_limit": 10000000, "force_bip143": false, "fork_id": null, "github": "https://github.com/dogecoin/dogecoin", "hash_genesis_block": "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691", "key": "bitcoin:DOGE", "maintainer": "Karel Bilek ", "max_address_length": 34, "maxfee_kb": 1000000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Dogecoin", "segwit": false, "shortcut": "DOGE", "signed_message_header": "Dogecoin Signed Message:\n", "slip44": 3, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "dogecoin", "website": "https://dogecoin.com", "xprv_magic": 49988504, "xpub_magic": 49990397, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 235, "address_type_p2sh": 75, "bech32_prefix": "ert", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Elements", "coin_name": "Elements", "coin_shortcut": "ELEMENTS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/ElementsProject/elements", "hash_genesis_block": "209577bda6bf4b5804bd46f8621580dd6d4e8bfa2d190e1c50e932492baca07d", "key": "bitcoin:ELEMENTS", "maintainer": "Roman Zeyde ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Elements", "segwit": true, "shortcut": "ELEMENTS", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": null, "trezor2": null, "webwallet": false}, "uri_prefix": "elements", "website": "https://elementsproject.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 95, "address_type_p2sh": 36, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 180, "cashaddr_prefix": null, "coin_label": "FairCoin", "coin_name": "FairCoin", "coin_shortcut": "FAIR", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 800000}, "dust_limit": 500000, "force_bip143": false, "fork_id": 0, "github": "https://github.com/faircoin/faircoin", "hash_genesis_block": "beed44fa5e96150d95d56ebd5d2625781825a9407a5215dd7eda723373a0a1d7", "key": "bitcoin:FAIR", "maintainer": "Santi Nore\u00f1a ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "FairCoin", "segwit": false, "shortcut": "FAIR", "signed_message_header": "FairCoin Signed Message:\n", "slip44": 298, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "faircoin", "website": "https://www.faircoin.world", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 14, "address_type_p2sh": 5, "bech32_prefix": "fc", "bip115": false, "bitcore": ["https://bitcore.feathercoin.com"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Feathercoin", "coin_name": "Feathercoin", "coin_shortcut": "FTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "duplicate": true, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/FeatherCoin/Feathercoin", "hash_genesis_block": "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2", "key": "bitcoin:FTC", "maintainer": "Lucas Betschart ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Feathercoin", "segwit": true, "shortcut": "FTC", "signed_message_header": "Feathercoin Signed Message:\n", "slip44": 8, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "feathercoin", "website": "https://feathercoin.com", "xprv_magic": 76077806, "xpub_magic": 76069926, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 35, "address_type_p2sh": 94, "bech32_prefix": "flo", "bip115": false, "bitcore": ["https://livenet.flocha.in"], "blockbook": [], "blocktime_seconds": 40, "cashaddr_prefix": null, "coin_label": "Flo", "coin_name": "Florincoin", "coin_shortcut": "FLO", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/floblockchain/flo", "hash_genesis_block": "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea", "key": "bitcoin:FLO", "maintainer": "Robert English ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Flo", "segwit": true, "shortcut": "FLO", "signed_message_header": "Florincoin Signed Message:\n", "slip44": 216, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "florincoin", "website": "https://flo.cash", "xprv_magic": 15264107, "xpub_magic": 1526049, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 28471030}, {"address_type": 36, "address_type_p2sh": 16, "bech32_prefix": "fc", "bip115": false, "bitcore": [], "blockbook": ["https://explorer.fujicoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Fujicoin", "coin_name": "Fujicoin", "coin_shortcut": "FJC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 20000, "High": 100000, "Low": 10000, "Normal": 50000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/fujicoin/fujicoin", "hash_genesis_block": "adb6d9cfd74075e7f91608add4bd2a2ea636f70856183086842667a1597714a0", "key": "bitcoin:FJC", "maintainer": "motty ", "max_address_length": 34, "maxfee_kb": 1000000000, "min_address_length": 27, "minfee_kb": 10000000, "name": "Fujicoin", "segwit": true, "shortcut": "FJC", "signed_message_header": "FujiCoin Signed Message:\n", "slip44": 75, "support": {"connect": true, "trezor1": "1.6.1", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "fujicoin", "website": "https://fujicoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 38, "address_type_p2sh": 10, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.gincoin.io"], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "GIN", "coin_name": "Gincoin", "coin_shortcut": "GIN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/gincoin-dev/gincoin-core", "hash_genesis_block": "00000cd6bde619b2c3b23ad2e384328a450a37fa28731debf748c3b17f91f97d", "key": "bitcoin:GIN", "maintainer": "Dragos Badea ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "GIN", "segwit": false, "shortcut": "GIN", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 2000, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "gincoin", "website": "https://gincoin.io", "xprv_magic": 50221816, "xpub_magic": 50221772, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 38, "address_type_p2sh": 62, "bech32_prefix": "game", "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.gamecredits.network"], "blocktime_seconds": 90, "cashaddr_prefix": null, "coin_label": "GameCredits", "coin_name": "GameCredits", "coin_shortcut": "GAME", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/gamecredits-project/gamecredits", "hash_genesis_block": "91ec5f25ee9a0ffa1af7d4da4db9a552228dd2dc77cdb15b738be4e1f55f30ee", "key": "bitcoin:GAME", "maintainer": "Samad Sajanlal ", "max_address_length": 34, "maxfee_kb": 5000000, "min_address_length": 27, "minfee_kb": 100000, "name": "GameCredits", "segwit": true, "shortcut": "GAME", "signed_message_header": "GameCredits Signed Message:\n", "slip44": 101, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "gamecredits", "website": "https://gamecredits.org", "xprv_magic": 27108450, "xpub_magic": 27106558, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 28471030}, {"address_type": 36, "address_type_p2sh": 5, "bech32_prefix": "grs", "bip115": false, "bitcore": ["https://groestlsight.groestlcoin.org", "https://grsblocks.com"], "blockbook": ["https://blockbook.groestlcoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Groestlcoin", "coin_name": "Groestlcoin", "coin_shortcut": "GRS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_groestl", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Groestlcoin/groestlcoin", "hash_genesis_block": "00000ac5927c594d49cc0bdb81759d0da8297eb614683d3acb62f0703b639023", "key": "bitcoin:GRS", "maintainer": "Yura Pakhuchiy ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Groestlcoin", "segwit": true, "shortcut": "GRS", "signed_message_header": "GroestlCoin Signed Message:\n", "slip44": 17, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "groestlcoin", "website": "https://www.groestlcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "tgrs", "bip115": false, "bitcore": ["https://groestlsight-test.groestlcoin.org"], "blockbook": ["https://blockbook-test.groestlcoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Groestlcoin Testnet", "coin_name": "Groestlcoin Testnet", "coin_shortcut": "tGRS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_groestl", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/Groestlcoin/groestlcoin", "hash_genesis_block": "000000ffbb50fc9898cdd36ec163e6ba23230164c0052a28876255b7dcf2cd36", "key": "bitcoin:tGRS", "maintainer": "Yura Pakhuchiy ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Groestlcoin Testnet", "segwit": true, "shortcut": "tGRS", "signed_message_header": "GroestlCoin Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.6.2", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "groestlcoin", "website": "https://www.groestlcoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 8329, "address_type_p2sh": 8342, "bech32_prefix": null, "bip115": true, "bitcore": ["https://explorer.horizen.global"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Horizen", "coin_name": "Horizen", "coin_shortcut": "ZEN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/ZencashOfficial/zen", "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602", "key": "bitcoin:ZEN", "maintainer": "Power_VANO ", "max_address_length": 95, "maxfee_kb": 2000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Horizen", "segwit": false, "shortcut": "ZEN", "signed_message_header": "Zcash Signed Message:\n", "slip44": 121, "support": {"connect": true, "trezor1": false, "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "horizen", "website": "https://www.horizen.global", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 60, "address_type_p2sh": 85, "bech32_prefix": null, "bip115": false, "bitcore": ["https://api.kmd.dev"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Komodo", "coin_name": "Komodo", "coin_shortcut": "KMD", "consensus_branch_id": {"1": 0, "2": 0, "3": 1537743641, "4": 1991772603}, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/komodoplatform/komodo", "hash_genesis_block": "027e3758c3a65b12aa1046462b486d0a63bfa1beae327897f56c5cfb7daaae71", "key": "bitcoin:KMD", "maintainer": "Kadan Stadelmann ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Komodo", "segwit": false, "shortcut": "KMD", "signed_message_header": "Komodo Signed Message:\n", "slip44": 141, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "komodo", "website": "https://komodoplatform.com", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 6198, "address_type_p2sh": 6203, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.kotocoin.info"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Koto", "coin_name": "Koto", "coin_shortcut": "KOTO", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/KotoDevelopers/koto", "hash_genesis_block": "6d424c350729ae633275d51dc3496e16cd1b1d195c164da00f39c499a2e9959e", "key": "bitcoin:KOTO", "maintainer": "WO ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Koto", "segwit": false, "shortcut": "KOTO", "signed_message_header": "Koto Signed Message:\n", "slip44": 510, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "koto", "website": "https://ko-to.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 48, "address_type_p2sh": 50, "bech32_prefix": "ltc", "bip115": false, "bitcore": [], "blockbook": ["https://ltc1.trezor.io", "https://ltc2.trezor.io", "https://ltc3.trezor.io", "https://ltc4.trezor.io", "https://ltc5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Litecoin", "coin_name": "Litecoin", "coin_shortcut": "LTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/litecoin-project/litecoin", "hash_genesis_block": "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2", "key": "bitcoin:LTC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Litecoin", "segwit": true, "shortcut": "LTC", "signed_message_header": "Litecoin Signed Message:\n", "slip44": 2, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "litecoin", "website": "https://litecoin.org", "xprv_magic": 27106558, "xpub_magic": 27108450, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 28471030}, {"address_type": 111, "address_type_p2sh": 58, "bech32_prefix": "tltc", "bip115": false, "bitcore": ["https://testnet.litecore.io"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Litecoin Testnet", "coin_name": "Litecoin Testnet", "coin_shortcut": "tLTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/litecoin-project/litecoin", "hash_genesis_block": "4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0", "key": "bitcoin:tLTC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Litecoin Testnet", "segwit": true, "shortcut": "tLTC", "signed_message_header": "Litecoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "litecoin", "website": "https://litecoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 50, "address_type_p2sh": 5, "bech32_prefix": "mec", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Megacoin", "coin_name": "Megacoin", "coin_shortcut": "MEC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/LIMXTEC/Megacoin", "hash_genesis_block": "7520788e2d99eec7cf6cf7315577e1268e177fff94cb0a7caf6a458ceeea9ac2", "key": "bitcoin:MEC", "maintainer": "limxdev ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Megacoin", "segwit": true, "shortcut": "MEC", "signed_message_header": "MegaCoin Signed Message:\n", "slip44": 217, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": false}, "uri_prefix": "megacoin", "website": "https://www.megacoin.eu", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 50, "address_type_p2sh": 55, "bech32_prefix": "mona", "bip115": false, "bitcore": ["https://mona.chainsight.info", "https://insight.electrum-mona.org"], "blockbook": ["https://blockbook.electrum-mona.org"], "blocktime_seconds": 90, "cashaddr_prefix": null, "coin_label": "Monacoin", "coin_name": "Monacoin", "coin_shortcut": "MONA", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/monacoinproject/monacoin", "hash_genesis_block": "ff9f1c0116d19de7c9963845e129f9ed1bfc0b376eb54fd7afa42e0d418c8bb6", "key": "bitcoin:MONA", "maintainer": "cryptcoin-junkey ", "max_address_length": 34, "maxfee_kb": 5000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Monacoin", "segwit": true, "shortcut": "MONA", "signed_message_header": "Monacoin Signed Message:\n", "slip44": 22, "support": {"connect": true, "trezor1": "1.6.0", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "monacoin", "website": "https://monacoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 16, "address_type_p2sh": 76, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.monetaryunit.org"], "blocktime_seconds": 40, "cashaddr_prefix": null, "coin_label": "MonetaryUnit", "coin_name": "MonetaryUnit", "coin_shortcut": "MUE", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/muecoin/MUE", "hash_genesis_block": "0b58ed450b3819ca54ab0054c4d220ca4f887d21c9e55d2a333173adf76d987f", "key": "bitcoin:MUE", "maintainer": "Sotiris Blad ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "MonetaryUnit", "segwit": false, "shortcut": "MUE", "signed_message_header": "MonetaryUnit Signed Message:\n", "slip44": 31, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "monetaryunit", "website": "https://www.monetaryunit.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 50, "address_type_p2sh": 9, "bech32_prefix": "my", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Myriad", "coin_name": "Myriad", "coin_shortcut": "XMY", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/myriadteam/myriadcoin", "hash_genesis_block": "00000ffde4c020b5938441a0ea3d314bf619eff0b38f32f78f7583cffa1ea485", "key": "bitcoin:XMY", "maintainer": "Adam Hickerson ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Myriad", "segwit": true, "shortcut": "XMY", "signed_message_header": "Myriadcoin Signed Message:\n", "slip44": 90, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "myriadcoin", "website": "https://www.myriadcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 38, "address_type_p2sh": 53, "bech32_prefix": "nix", "bip115": false, "bitcore": ["https://blockchain.nixplatform.io"], "blockbook": [], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "NIX", "coin_name": "NIX", "coin_shortcut": "NIX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/nixplatform/nixcore", "hash_genesis_block": "dd28ad86def767c3cfc34267a950d871fc7462bc57ea4a929fc3596d9b598e41", "key": "bitcoin:NIX", "maintainer": "mattt21 ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 0, "name": "NIX", "segwit": true, "shortcut": "NIX", "signed_message_header": "NIX Signed Message:\n", "slip44": 400, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "nix", "website": "https://nixplatform.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 52, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://nmc1.trezor.io", "https://nmc2.trezor.io"], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Namecoin", "coin_name": "Namecoin", "coin_shortcut": "NMC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 2940, "force_bip143": false, "fork_id": null, "github": "https://github.com/namecoin/namecoin-core", "hash_genesis_block": "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770", "key": "bitcoin:NMC", "maintainer": "Pavol Rusnak ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Namecoin", "segwit": false, "shortcut": "NMC", "signed_message_header": "Namecoin Signed Message:\n", "slip44": 7, "support": {"connect": true, "trezor1": "1.5.2", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "namecoin", "website": "https://namecoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 30, "address_type_p2sh": 13, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.pivx.link"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "PIVX", "coin_name": "PIVX", "coin_shortcut": "PIVX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/PIVX-Project/PIVX", "hash_genesis_block": "0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818", "key": "bitcoin:PIVX", "maintainer": "Random Zebra ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 100, "name": "PIVX", "segwit": false, "shortcut": "PIVX", "signed_message_header": "DarkNet Signed Message:\n", "slip44": 119, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "pivx", "website": "https://pivx.org", "xprv_magic": 35729707, "xpub_magic": 36513075, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 139, "address_type_p2sh": 19, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook-testnet.pivx.link"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "PIVX Testnet", "coin_name": "PIVX Testnet", "coin_shortcut": "tPIVX", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/PIVX-Project/PIVX", "hash_genesis_block": "0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818", "key": "bitcoin:tPIVX", "maintainer": "Random Zebra ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 100, "name": "PIVX Testnet", "segwit": false, "shortcut": "tPIVX", "signed_message_header": "DarkNet Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "pivx", "website": "https://pivx.org", "xprv_magic": 981489719, "xpub_magic": 981492128, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 47, "address_type_p2sh": 22, "bech32_prefix": null, "bip115": false, "bitcore": ["https://live.pesetacoin.info"], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Pesetacoin", "coin_name": "Pesetacoin", "coin_shortcut": "PTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "duplicate": true, "dust_limit": 10000000, "force_bip143": false, "fork_id": null, "github": "https://github.com/FundacionPesetacoin/PesetacoinCore", "hash_genesis_block": "edfe5830b53251bfff733600b1cd5c192e761c011b055f07924634818c906438", "key": "bitcoin:PTC", "maintainer": "Rw ", "max_address_length": 34, "maxfee_kb": 1000000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Pesetacoin", "segwit": false, "shortcut": "PTC", "signed_message_header": "Pesetacoin Signed Message:\n", "slip44": 109, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "pesetacoin", "website": "https://pesetacoin.info", "xprv_magic": 76079604, "xpub_magic": 76071982, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 55, "address_type_p2sh": 56, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.polispay.org"], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "Polis", "coin_name": "Polis", "coin_shortcut": "POLIS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/polispay/polis", "hash_genesis_block": "000009701eb781a8113b1af1d814e2f060f6408a2c990db291bc5108a1345c1e", "key": "bitcoin:POLIS", "maintainer": "Cronos ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 1000, "name": "Polis", "segwit": false, "shortcut": "POLIS", "signed_message_header": "Polis Signed Message:\n", "slip44": 1997, "support": {"connect": false, "trezor1": "soon", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "polis", "website": "https://www.polispay.org", "xprv_magic": 65165637, "xpub_magic": 65166718, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 23, "address_type_p2sh": 83, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Primecoin", "coin_name": "Primecoin", "coin_shortcut": "XPM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/primecoin/primecoin", "hash_genesis_block": "963d17ba4dc753138078a2f56afb3af9674e2546822badff26837db9a0152106", "key": "bitcoin:XPM", "maintainer": "James Skrowvedeht ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 26, "minfee_kb": 1000, "name": "Primecoin", "segwit": false, "shortcut": "XPM", "signed_message_header": "Primecoin Signed Message:\n", "slip44": 24, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "primecoin", "website": "https://primecoin.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 58, "address_type_p2sh": 50, "bech32_prefix": "qc", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 128, "cashaddr_prefix": null, "coin_label": "Qtum", "coin_name": "Qtum", "coin_shortcut": "QTUM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 500, "High": 2000, "Low": 410, "Normal": 600}, "duplicate": true, "dust_limit": 218400, "force_bip143": false, "fork_id": null, "github": "https://github.com/qtumproject/qtum", "hash_genesis_block": "000075aef83cf2853580f8ae8ce6f8c3096cfa21d98334d6e3f95e5582ed986c", "key": "bitcoin:QTUM", "maintainer": "CodeFace ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 400000, "name": "Qtum", "segwit": true, "shortcut": "QTUM", "signed_message_header": "Qtum Signed Message:\n", "slip44": 2301, "support": {"connect": false, "trezor1": "1.8.1", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "qtum", "website": "https://qtum.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 120, "address_type_p2sh": 110, "bech32_prefix": "tq", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 128, "cashaddr_prefix": null, "coin_label": "Qtum Testnet", "coin_name": "Qtum Testnet", "coin_shortcut": "tQTUM", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 500, "High": 2000, "Low": 410, "Normal": 600}, "dust_limit": 218400, "force_bip143": false, "fork_id": null, "github": "https://github.com/qtumproject/qtum", "hash_genesis_block": "0000e803ee215c0684ca0d2f9220594d3f828617972aad66feb2ba51f5e14222", "key": "bitcoin:tQTUM", "maintainer": "CodeFace ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 400000, "name": "Qtum Testnet", "segwit": true, "shortcut": "tQTUM", "signed_message_header": "Qtum Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.8.1", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "qtum", "website": "https://qtum.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 60, "address_type_p2sh": 122, "bech32_prefix": null, "bip115": false, "bitcore": ["https://ravencoin.network"], "blockbook": ["https://blockbook.ravencoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Ravencoin", "coin_name": "Ravencoin", "coin_shortcut": "RVN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/RavenProject/Ravencoin", "hash_genesis_block": "0000006b444bc2f2ffe627be9d9e7e7a0730000870ef6eb6da46c8eae389df90", "key": "bitcoin:RVN", "maintainer": "Scotty ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Ravencoin", "segwit": false, "shortcut": "RVN", "signed_message_header": "Raven Signed Message:\n", "slip44": 175, "support": {"connect": true, "trezor1": "1.7.2", "trezor2": "2.0.10", "webwallet": true}, "uri_prefix": "raven", "website": "https://ravencoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 111, "address_type_p2sh": 196, "bech32_prefix": "bcrt", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Regtest", "coin_name": "Regtest", "coin_shortcut": "REGTEST", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/bitcoin/bitcoin", "hash_genesis_block": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206", "key": "bitcoin:REGTEST", "maintainer": "Thomas Kerin ", "max_address_length": 34, "maxfee_kb": 10000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Regtest", "segwit": true, "shortcut": "REGTEST", "signed_message_header": "Bitcoin Signed Message:\n", "slip44": 1, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "bitcoin", "website": "https://bitcoin.org", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": 73342198, "xpub_magic_segwit_p2sh": 71979618}, {"address_type": 25, "address_type_p2sh": 105, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.ritocoin.org"], "blockbook": ["https://blockbook.ritocoin.org"], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Ritocoin", "coin_name": "Ritocoin", "coin_shortcut": "RITO", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Low": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/RitoProject", "hash_genesis_block": "00000075e344bdf1c0e433f453764b1830a7aa19b2a5213e707502a22b779c1b", "key": "bitcoin:RITO", "maintainer": "Scotty ", "max_address_length": 34, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Ritocoin", "segwit": false, "shortcut": "RITO", "signed_message_header": "Rito Signed Message:\n", "slip44": 19169, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "rito", "website": "https://ritocoin.org", "xprv_magic": 87326380, "xpub_magic": 87353290, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 63, "address_type_p2sh": 18, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.smartcash.cc"], "blockbook": [], "blocktime_seconds": 55, "cashaddr_prefix": null, "coin_label": "SmartCash", "coin_name": "SmartCash", "coin_shortcut": "SMART", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_smart", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "duplicate": true, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/SmartCash/Core-Smart", "hash_genesis_block": "000007acc6970b812948d14ea5a0a13db0fdd07d5047c7e69101fa8b361e05a4", "key": "bitcoin:SMART", "maintainer": "Leandro Reinaux ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "SmartCash", "segwit": false, "shortcut": "SMART", "signed_message_header": "SmartCash Signed Message:\n", "slip44": 224, "support": {"connect": false, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "smart", "website": "https://smartcash.cc", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 65, "address_type_p2sh": 21, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 55, "cashaddr_prefix": null, "coin_label": "SmartCash Testnet", "coin_name": "SmartCash Testnet", "coin_shortcut": "tSMART", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1_smart", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/SmartCash/Core-Smart", "hash_genesis_block": "0000027235b5679bcd28c90d03d4bf1a9ba4c07c4efcc1c87d6c68cce25e6e5d", "key": "bitcoin:tSMART", "maintainer": "Leandro Reinaux ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "SmartCash Testnet", "segwit": false, "shortcut": "tSMART", "signed_message_header": "SmartCash Signed Message:\n", "slip44": 224, "support": {"connect": false, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": false}, "uri_prefix": "testsmart", "website": "https://smartcash.cc", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 76, "address_type_p2sh": 16, "bech32_prefix": "xc", "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 60, "cashaddr_prefix": null, "coin_label": "Stakenet", "coin_name": "Stakenet", "coin_shortcut": "XSN", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 70, "High": 200, "Low": 10, "Normal": 140}, "dust_limit": 1000, "force_bip143": false, "fork_id": null, "github": "https://github.com/X9Developers/XSN", "hash_genesis_block": "00000c822abdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34", "key": "bitcoin:XSN", "maintainer": "Alexis Hernandez ", "max_address_length": 47, "maxfee_kb": 2000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Stakenet", "segwit": true, "shortcut": "XSN", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 199, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "stakenet", "website": "https://stakenet.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 0, "address_type_p2sh": 5, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.terracoin.io"], "blockbook": [], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "Terracoin", "coin_name": "Terracoin", "coin_shortcut": "TRC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "duplicate": true, "dust_limit": 5460, "force_bip143": false, "fork_id": null, "github": "https://github.com/terracoin/terracoin", "hash_genesis_block": "00000000804bbc6a621a9dbb564ce469f492e1ccf2d70f8a6b241e26a277afa2", "key": "bitcoin:TRC", "maintainer": "The Terracoin Foundation ", "max_address_length": 34, "maxfee_kb": 100000, "min_address_length": 27, "minfee_kb": 10000, "name": "Terracoin", "segwit": false, "shortcut": "TRC", "signed_message_header": "DarkCoin Signed Message:\n", "slip44": 83, "support": {"connect": false, "trezor1": false, "trezor2": false, "webwallet": false}, "uri_prefix": "terracoin", "website": "https://terracoin.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 70, "address_type_p2sh": 50, "bech32_prefix": "vips", "bip115": false, "bitcore": ["https://insight.vipstarco.in"], "blockbook": ["https://vips.blockbook.japanesecoin-pool.work"], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "VIPSTARCOIN", "coin_name": "VIPSTARCOIN", "coin_shortcut": "VIPS", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 500, "High": 2000, "Low": 410, "Normal": 600}, "dust_limit": 218400, "force_bip143": false, "fork_id": null, "github": "https://github.com/VIPSTARCOIN/VIPSTARCOIN", "hash_genesis_block": "0000d068e1d30f79fb64446137106be9c6ee69a6a722295c131506b1ee09b77c", "key": "bitcoin:VIPS", "maintainer": "y-chan ", "max_address_length": 36, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 400000, "name": "VIPSTARCOIN", "segwit": true, "shortcut": "VIPS", "signed_message_header": "VIPSTARCOIN Signed Message:\n", "slip44": 1919, "support": {"connect": false, "trezor1": "1.8.2", "trezor2": "2.1.1", "webwallet": false}, "uri_prefix": "vipstarcoin", "website": "https://vipstarcoin.jp", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 71, "address_type_p2sh": 5, "bech32_prefix": "vtc", "bip115": false, "bitcore": [], "blockbook": ["https://vtc1.trezor.io", "https://vtc2.trezor.io", "https://vtc3.trezor.io", "https://vtc4.trezor.io", "https://vtc5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Vertcoin", "coin_name": "Vertcoin", "coin_shortcut": "VTC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 1000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/vertcoin-project/vertcoin-core", "hash_genesis_block": "4d96a915f49d40b1e5c2844d1ee2dccb90013a990ccea12c492d22110489f0c4", "key": "bitcoin:VTC", "maintainer": "Jochen Hoenicke ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 100000, "name": "Vertcoin", "segwit": true, "shortcut": "VTC", "signed_message_header": "Vertcoin Signed Message:\n", "slip44": 28, "support": {"connect": true, "trezor1": "1.6.1", "trezor2": "2.0.5", "webwallet": true}, "uri_prefix": "vertcoin", "website": "https://vertcoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 71, "address_type_p2sh": 33, "bech32_prefix": "via", "bip115": false, "bitcore": ["https://explorer.viacoin.org"], "blockbook": [], "blocktime_seconds": 24, "cashaddr_prefix": null, "coin_label": "Viacoin", "coin_name": "Viacoin", "coin_shortcut": "VIA", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 7000, "High": 20000, "Low": 1000, "Normal": 14000}, "dust_limit": 54600, "force_bip143": false, "fork_id": null, "github": "https://github.com/viacoin", "hash_genesis_block": "4e9b54001f9976049830128ec0331515eaabe35a70970d79971da1539a400ba1", "key": "bitcoin:VIA", "maintainer": "romanornr ", "max_address_length": 34, "maxfee_kb": 40000000, "min_address_length": 27, "minfee_kb": 1000, "name": "Viacoin", "segwit": true, "shortcut": "VIA", "signed_message_header": "Viacoin Signed Message:\n", "slip44": 14, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "viacoin", "website": "https://viacoin.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": 78792518, "xpub_magic_segwit_p2sh": 77429938}, {"address_type": 7352, "address_type_p2sh": 7357, "bech32_prefix": null, "bip115": false, "bitcore": ["https://explorer.zcl.zeltrez.io"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "ZClassic", "coin_name": "ZClassic", "coin_shortcut": "ZCL", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/ZclassicCommunity", "hash_genesis_block": "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602", "key": "bitcoin:ZCL", "maintainer": "James Skrowvedeht ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "ZClassic", "segwit": false, "shortcut": "ZCL", "signed_message_header": "Zcash Signed Message:\n", "slip44": 147, "support": {"connect": true, "trezor1": "1.8.0", "trezor2": "2.0.11", "webwallet": false}, "uri_prefix": "zclassic", "website": "https://zclassic.org", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 7352, "address_type_p2sh": 7357, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://zec1.trezor.io", "https://zec2.trezor.io", "https://zec3.trezor.io", "https://zec4.trezor.io", "https://zec5.trezor.io"], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Zcash", "coin_name": "Zcash", "coin_shortcut": "ZEC", "consensus_branch_id": {"1": 0, "2": 0, "3": 1537743641, "4": 1991772603}, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcash/zcash", "hash_genesis_block": "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08", "key": "bitcoin:ZEC", "maintainer": "Pavol Rusnak ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Zcash", "segwit": false, "shortcut": "ZEC", "signed_message_header": "Zcash Signed Message:\n", "slip44": 133, "support": {"connect": true, "trezor1": "1.7.1", "trezor2": "2.0.8", "webwallet": true}, "uri_prefix": "zcash", "website": "https://z.cash", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 7461, "address_type_p2sh": 7354, "bech32_prefix": null, "bip115": false, "bitcore": ["https://explorer.testnet.z.cash"], "blockbook": [], "blocktime_seconds": 150, "cashaddr_prefix": null, "coin_label": "Zcash Testnet", "coin_name": "Zcash Testnet", "coin_shortcut": "TAZ", "consensus_branch_id": {"1": 0, "2": 0, "3": 1537743641, "4": 1991772603}, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcash/zcash", "hash_genesis_block": "05a60a92d99d85997cce3b87616c089f6124d7342af37106edc76126334a2c38", "key": "bitcoin:TAZ", "maintainer": "Pavol Rusnak ", "max_address_length": 95, "maxfee_kb": 10000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Zcash Testnet", "segwit": false, "shortcut": "TAZ", "signed_message_header": "Zcash Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "zcash", "website": "https://z.cash", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 82, "address_type_p2sh": 7, "bech32_prefix": null, "bip115": false, "bitcore": ["https://insight.zcoin.io"], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Zcoin", "coin_name": "Zcoin", "coin_shortcut": "XZC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcoinofficial/zcoin", "hash_genesis_block": "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233", "key": "bitcoin:XZC", "maintainer": "Yura Pakhuchiy ", "max_address_length": 34, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "Zcoin", "segwit": false, "shortcut": "XZC", "signed_message_header": "Zcoin Signed Message:\n", "slip44": 136, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": true}, "uri_prefix": "zcoin", "website": "https://zcoin.io", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 65, "address_type_p2sh": 178, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": [], "blocktime_seconds": 600, "cashaddr_prefix": null, "coin_label": "Zcoin Testnet", "coin_name": "Zcoin Testnet", "coin_shortcut": "tXZC", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Economy": 10, "High": 200, "Low": 1, "Normal": 100}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zcoinofficial/zcoin", "hash_genesis_block": "7ac038c193c2158c428c59f9ae0c02a07115141c6e9dc244ae96132e99b4e642", "key": "bitcoin:tXZC", "maintainer": "Yura Pakhuchiy ", "max_address_length": 35, "maxfee_kb": 1000000, "min_address_length": 27, "minfee_kb": 0, "name": "Zcoin Testnet", "segwit": false, "shortcut": "tXZC", "signed_message_header": "Zcoin Signed Message:\n", "slip44": 1, "support": {"connect": true, "trezor1": "1.6.2", "trezor2": "2.0.7", "webwallet": false}, "uri_prefix": "testzcoin", "website": "https://zcoin.io", "xprv_magic": 70615956, "xpub_magic": 70617039, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}, {"address_type": 7352, "address_type_p2sh": 7357, "bech32_prefix": null, "bip115": false, "bitcore": [], "blockbook": ["https://blockbook.zel.network"], "blocktime_seconds": 120, "cashaddr_prefix": null, "coin_label": "Zel", "coin_name": "ZelCash", "coin_shortcut": "ZEL", "consensus_branch_id": null, "cooldown": 100, "curve_name": "secp256k1", "decred": false, "default_fee_b": {"Normal": 10}, "dust_limit": 546, "force_bip143": false, "fork_id": null, "github": "https://github.com/zelcash", "hash_genesis_block": "00052461a5006c2e3b74ce48992a08695607912d5604c3eb8da25749b0900444", "key": "bitcoin:ZEL", "maintainer": "Cabecinha84 ", "max_address_length": 95, "maxfee_kb": 1000000, "min_address_length": 35, "minfee_kb": 1000, "name": "Zel", "segwit": false, "shortcut": "ZEL", "signed_message_header": "Zcash Signed Message:\n", "slip44": 19167, "support": {"connect": false, "trezor1": null, "trezor2": null, "webwallet": false}, "uri_prefix": "zelcash", "website": "https://zel.network", "xprv_magic": 76066276, "xpub_magic": 76067358, "xpub_magic_segwit_native": null, "xpub_magic_segwit_p2sh": null}] diff --git a/python/trezorlib/messages/ResetDevice.py b/python/trezorlib/messages/ResetDevice.py index 719b8106c..87bea0c27 100644 --- a/python/trezorlib/messages/ResetDevice.py +++ b/python/trezorlib/messages/ResetDevice.py @@ -17,6 +17,7 @@ class ResetDevice(p.MessageType): u2f_counter: int = None, skip_backup: bool = None, no_backup: bool = None, + slip39: bool = None, ) -> None: self.display_random = display_random self.strength = strength @@ -27,6 +28,7 @@ class ResetDevice(p.MessageType): self.u2f_counter = u2f_counter self.skip_backup = skip_backup self.no_backup = no_backup + self.slip39 = slip39 @classmethod def get_fields(cls): @@ -40,4 +42,5 @@ class ResetDevice(p.MessageType): 7: ('u2f_counter', p.UVarintType, 0), 8: ('skip_backup', p.BoolType, 0), 9: ('no_backup', p.BoolType, 0), + 10: ('slip39', p.BoolType, 0), } From b65896b24e9fca6626dc5f6ff24bb22d6056f2bb Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Sun, 23 Jun 2019 12:20:36 +0200 Subject: [PATCH 5/5] python: add device test for SLIP-39 recovery device; fix reset device --- .../test_msg_recoverydevice_shamir.py | 193 ++++++++++++++++++ .../device_tests/test_msg_resetdevice_t2.py | 28 ++- 2 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 python/trezorlib/tests/device_tests/test_msg_recoverydevice_shamir.py diff --git a/python/trezorlib/tests/device_tests/test_msg_recoverydevice_shamir.py b/python/trezorlib/tests/device_tests/test_msg_recoverydevice_shamir.py new file mode 100644 index 000000000..815e8906c --- /dev/null +++ b/python/trezorlib/tests/device_tests/test_msg_recoverydevice_shamir.py @@ -0,0 +1,193 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2019 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import time + +import pytest + +from trezorlib import btc, device, messages as proto +from trezorlib.messages.PassphraseSourceType import HOST as PASSPHRASE_ON_HOST + +from .common import TrezorTest + + +@pytest.mark.skip_t1 +class TestMsgRecoveryDeviceShamir(TrezorTest): + def test_3of6_nopin_nopassphrase(self): + # 128 bits security, 3 of 6 + mnemonics = [ + "extra extend academic bishop cricket bundle tofu goat apart victim enlarge program behavior permit course armed jerky faint language modern", + "extra extend academic acne away best indicate impact square oasis prospect painting voting guest either argue username racism enemy eclipse", + "extra extend academic arcade born dive legal hush gross briefing talent drug much home firefly toxic analysis idea umbrella slice", + ] + word_count = len(mnemonics[0].split(" ")) + + ret = self.client.call_raw( + proto.RecoveryDevice( + passphrase_protection=False, pin_protection=False, label="label" + ) + ) + + # Confirm Recovery + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter word count + assert ret == proto.ButtonRequest( + code=proto.ButtonRequestType.MnemonicWordCount + ) + self.client.debug.input(str(word_count)) + ret = self.client.call_raw(proto.ButtonAck()) + + # Confirm T9 keyboard + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter shares + for mnemonic in mnemonics: + # Enter mnemonic words + assert ret == proto.ButtonRequest( + code=proto.ButtonRequestType.MnemonicInput + ) + self.client.transport.write(proto.ButtonAck()) + for word in mnemonic.split(" "): + time.sleep(1) + self.client.debug.input(word) + ret = self.client.transport.read() + + if mnemonic != mnemonics[-1]: + # Confirm status + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + # Workflow succesfully ended + assert ret == proto.Success(message="Device recovered") + + assert self.client.features.pin_protection is False + assert self.client.features.passphrase_protection is False + + # Check mnemonic + assert ( + self.client.debug.read_mnemonic_secret().hex() + == "491b795b80fc21ccdf466c0fbc98c8fc" + ) + + # BIP32 Root Key for empty passphrase + # provided by Andrew, address calculated using T1 + # xprv9s21ZrQH143K3reExTJbGTHPu6mGuUx6yN1H1KkqoiAcw6j1t6hBiwwnXYxNQXxU8T7pANSv1rJYQPXn1LMQk1gbWes5BjSz3rJ5ZfH1cc3 + address = btc.get_address(self.client, "Bitcoin", []) + assert address == "1G1MwH5sLVxKQ7yKYasfE5pxWaABLo7VK7" + + # TODO: enable passphrase and test + # ask @jpochyla how to do this - see below + # # BIP32 Root Key for passphrase TREZOR + # # provided by Andrew, address calculated using T1 + # # xprv9s21ZrQH143K2pMWi8jrTawHaj16uKk4CSbvo4Zt61tcrmuUDMx2o1Byzcr3saXNGNvHP8zZgXVdJHsXVdzYFPavxvCyaGyGr1WkAYG83ce + # self.client.set_passphrase("TREZOR") + # address = btc.get_address(self.client, "Bitcoin", []) + # assert address == "18oZEMRWurCZW1FeK8sWYyXuWx2bFqEKyX" + + def test_2of5_pin_passphrase(self): + # 256 bits security, 2 of 5 + mnemonics = [ + "hobo romp academic axis august founder knife legal recover alien expect emphasis loan kitchen involve teacher capture rebuild trial numb spider forward ladle lying voter typical security quantity hawk legs idle leaves gasoline", + "hobo romp academic agency ancestor industry argue sister scene midst graduate profile numb paid headset airport daisy flame express scene usual welcome quick silent downtown oral critical step remove says rhythm venture aunt", + ] + word_count = len(mnemonics[0].split(" ")) + + ret = self.client.call_raw( + proto.RecoveryDevice( + passphrase_protection=True, pin_protection=True, label="label" + ) + ) + + # Confirm Recovery + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter word count + assert ret == proto.ButtonRequest( + code=proto.ButtonRequestType.MnemonicWordCount + ) + self.client.debug.input(str(word_count)) + ret = self.client.call_raw(proto.ButtonAck()) + + # Confirm T9 keyboard + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter shares + for mnemonic in mnemonics: + # Enter mnemonic words + assert ret == proto.ButtonRequest( + code=proto.ButtonRequestType.MnemonicInput + ) + self.client.transport.write(proto.ButtonAck()) + for word in mnemonic.split(" "): + time.sleep(1) + self.client.debug.input(word) + ret = self.client.transport.read() + + if mnemonic != mnemonics[-1]: + # Confirm status + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter PIN for first time + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.Other) + self.client.debug.input("654") + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter PIN for second time + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.Other) + self.client.debug.input("654") + ret = self.client.call_raw(proto.ButtonAck()) + + # Workflow succesfully ended + assert ret == proto.Success(message="Device recovered") + + # Check mnemonic + self.client.init_device() + assert ( + self.client.debug.read_mnemonic_secret().hex() + == "b770e0da1363247652de97a39bdbf2463be087848d709ecbf28e84508e31202a" + ) + + assert self.client.features.pin_protection is True + assert self.client.features.passphrase_protection is True + + device.apply_settings(self.client, passphrase_source=PASSPHRASE_ON_HOST) + + # BIP32 Root Key for empty passphrase + # provided by Andrew, address calculated using T1 + # xprv9s21ZrQH143K2kP9RYJE5AFggTHLs8PbDaaTYtvh238THxDyXqyqQV6H1QpFr3aaQ7CFusFMYyGZ6VcK7aLADyCaCJrszovxtzVZmnRfca4 + address = btc.get_address(self.client, "Bitcoin", []) + assert address == "1BmqXKM8M1gWA4bgkbPeCtJruRnrY2qYKP" + + # BIP32 Root Key for empty passphrase + # provided by Andrew, address calculated using T1 + # xprv9s21ZrQH143K2o6EXEHpVy8TCYoMmkBnDCCESLdR2ieKwmcNG48ck2XJQY4waS7RUQcXqR9N7HnQbUVEDMWYyREdF1idQqxFHuCfK7fqFni + # self.client.set_passphrase("TREZOR") + # TODO: @jpochyla this does not get send to Trezor :( How should I enter passphrase? + + # address = btc.get_address(self.client, "Bitcoin", []) + # assert address == "19Fjs9AvT13Y2Nx8GtoVfADmFWnccsPinQ" diff --git a/python/trezorlib/tests/device_tests/test_msg_resetdevice_t2.py b/python/trezorlib/tests/device_tests/test_msg_resetdevice_t2.py index 1af2f1720..488e1ca93 100644 --- a/python/trezorlib/tests/device_tests/test_msg_resetdevice_t2.py +++ b/python/trezorlib/tests/device_tests/test_msg_resetdevice_t2.py @@ -45,6 +45,11 @@ class TestMsgResetDeviceT2(TrezorTest): assert btn_code == B.ResetDevice self.client.debug.press_yes() + # safety warning + btn_code = yield + assert btn_code == B.ResetDevice + self.client.debug.press_yes() + # mnemonic phrases btn_code = yield assert btn_code == B.ResetDevice @@ -64,7 +69,12 @@ class TestMsgResetDeviceT2(TrezorTest): index = self.client.debug.state().reset_word_pos self.client.debug.input(words[index]) - # safety warning + # confirm recovery seed check + btn_code = yield + assert btn_code == B.ResetDevice + self.client.debug.press_yes() + + # confirm success btn_code = yield assert btn_code == B.ResetDevice self.client.debug.press_yes() @@ -78,6 +88,8 @@ class TestMsgResetDeviceT2(TrezorTest): proto.ButtonRequest(code=B.ResetDevice), proto.ButtonRequest(code=B.ResetDevice), proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), proto.Success(), proto.Features(), ] @@ -138,6 +150,11 @@ class TestMsgResetDeviceT2(TrezorTest): assert btn_code == B.ResetDevice self.client.debug.press_yes() + # safety warning + btn_code = yield + assert btn_code == B.ResetDevice + self.client.debug.press_yes() + # mnemonic phrases btn_code = yield assert btn_code == B.ResetDevice @@ -157,7 +174,12 @@ class TestMsgResetDeviceT2(TrezorTest): index = self.client.debug.state().reset_word_pos self.client.debug.input(words[index]) - # safety warning + # confirm recovery seed check + btn_code = yield + assert btn_code == B.ResetDevice + self.client.debug.press_yes() + + # confirm success btn_code = yield assert btn_code == B.ResetDevice self.client.debug.press_yes() @@ -174,6 +196,8 @@ class TestMsgResetDeviceT2(TrezorTest): proto.ButtonRequest(code=B.ResetDevice), proto.ButtonRequest(code=B.ResetDevice), proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), + proto.ButtonRequest(code=B.ResetDevice), proto.Success(), proto.Features(), ]