From e6b264098714605225690540480d44924439f6b9 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:13:24 -0400 Subject: [PATCH 01/16] Generalize 'view seed' to support xprv/master secrets --- shared/actions.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/shared/actions.py b/shared/actions.py index 6b0cb284..2ac235b6 100644 --- a/shared/actions.py +++ b/shared/actions.py @@ -561,16 +561,24 @@ async def view_seed_words(*a): return with stash.SensitiveValues() as sv: - assert sv.mode == 'words' # protected by menu item predicate - - words = tcc.bip39.from_data(sv.raw).split(' ') - - msg = 'Seed words (%d):\n' % len(words) - msg += '\n'.join('%2d: %s' % (i+1, w) for i,w in enumerate(words)) - - pw = stash.bip39_passphrase - if pw: - msg += '\n\nBIP39 Passphrase:\n%s' % stash.bip39_passphrase + if sv.mode == 'words': + words = tcc.bip39.from_data(sv.raw).split(' ') + + msg = 'Seed words (%d):\n' % len(words) + msg += '\n'.join('%2d: %s' % (i+1, w) for i,w in enumerate(words)) + + pw = stash.bip39_passphrase + if pw: + msg += '\n\nBIP39 Passphrase:\n%s' % stash.bip39_passphrase + elif sv.mode == 'xprv': + import chains + msg = chains.current_chain().serialize_private(sv.node) + + elif sv.mode == 'master': + msg = '%d bytes:\n\n' % len(sv.raw) + msg += str(b2a_hex(sv.raw), 'ascii') + else: + raise ValueError(sv.mode) await ux_show_story(msg, sensitive=True) From 575aed2e834cf71068c43b646c7eefb8d389ec62 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:16:39 -0400 Subject: [PATCH 02/16] Bugfix: do not blank passed-in secret --- shared/stash.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shared/stash.py b/shared/stash.py index ff93ddad..be46a4a2 100644 --- a/shared/stash.py +++ b/shared/stash.py @@ -124,10 +124,12 @@ def __init__(self, secret=None, for_backup=False): raise ValueError('no secrets yet') self.secret = pa.fetch() + self.spots = [ self.secret ] else: # sometimes we already know it - assert set(secret) != {0} + #assert set(secret) != {0} self.secret = secret + self.spots = [] # backup during volatile bip39 encryption: do not use passphrase self._bip39pw = '' if for_backup else str(bip39_passphrase) @@ -137,9 +139,10 @@ def __enter__(self): self.mode, self.raw, self.node = SecretStash.decode(self.secret, self._bip39pw) - self.chain = chains.current_chain() + self.spots.append(self.node) + self.spots.append(self.raw) - self.spots = [ self.secret, self.node, self.raw ] + self.chain = chains.current_chain() return self @@ -151,7 +154,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): if hasattr(self, 'secret'): # will be blanked from above - assert self.secret == bytes(AE_SECRET_LEN) del self.secret if hasattr(self, 'node'): From 7e97d93153aee1a6878702145410ff9a6106119a Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:18:34 -0400 Subject: [PATCH 03/16] Remove warning, because not applicable to all cases, and we're deep in the dangerzone anyway --- shared/seed.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/shared/seed.py b/shared/seed.py index 2f92245b..b43e6ca4 100644 --- a/shared/seed.py +++ b/shared/seed.py @@ -467,11 +467,6 @@ async def remember_bip39_passphrase(): dis.fullscreen('Check...') with stash.SensitiveValues() as sv: - if sv.mode != 'words': - # not a BIP39 derived secret, so cannot work. - await ux_show_story('''The wallet secret was not based on a seed phrase, so we cannot add a BIP39 passphrase at this time.''', title='Failed') - return - nv = SecretStash.encode(xprv=sv.node) # Important: won't write new XFP to nvram if pw still set From ac939cfc7a45c9ef2453eeaabca322937094ddd5 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:25:45 -0400 Subject: [PATCH 04/16] Correct comment --- unix/frozen-modules/sim_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/frozen-modules/sim_settings.py b/unix/frozen-modules/sim_settings.py index 0cdd1754..449221a6 100644 --- a/unix/frozen-modules/sim_settings.py +++ b/unix/frozen-modules/sim_settings.py @@ -81,7 +81,7 @@ print("Override XFP: " + xfp2str(sim_defaults['xfp'])) if '--seed' in sys.argv: - # --xfp aabbccdd => pretend we know that key (won't be able to sign) + # --seed "word1 word2 ... word24" => import that seed phrase at start from ustruct import unpack from utils import xfp2str from seed import set_seed_value From 0c756c851fdd627ff88b5cd49aa89a84ce4c1641 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:31:00 -0400 Subject: [PATCH 05/16] Bugfix: show master secrets as hex --- shared/actions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/actions.py b/shared/actions.py index 2ac235b6..91e90b1d 100644 --- a/shared/actions.py +++ b/shared/actions.py @@ -575,6 +575,8 @@ async def view_seed_words(*a): msg = chains.current_chain().serialize_private(sv.node) elif sv.mode == 'master': + from ubinascii import hexlify as b2a_hex + msg = '%d bytes:\n\n' % len(sv.raw) msg += str(b2a_hex(sv.raw), 'ascii') else: From c62a98c6916fc36f62441ff526d92d92b03b73d0 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:31:56 -0400 Subject: [PATCH 06/16] Add Derive Entropy item, remove need for bip39 words for lock-down/view-seed commands --- shared/flow.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/shared/flow.py b/shared/flow.py index 38c2077a..048fa0cd 100644 --- a/shared/flow.py +++ b/shared/flow.py @@ -16,8 +16,10 @@ if version.has_fatram: from hsm import hsm_policy_available + from drv_entro import drv_entro_start else: hsm_policy_available = lambda: False + drv_entro_start = None # # NOTE: "Always In Title Case" @@ -130,10 +132,8 @@ def has_secrets(): DangerZoneMenu = [ # xxxxxxxxxxxxxxxx MenuItem("Debug Functions", menu=DebugFunctionsMenu), # actually harmless - MenuItem('Lock Down Seed', f=convert_bip39_to_bip32, - predicate=lambda: settings.get('words', True)), - MenuItem('View Seed Words', f=view_seed_words, - predicate=lambda: settings.get('words', True)), + MenuItem('Lock Down Seed', f=convert_bip39_to_bip32), + MenuItem('View Seed Words', f=view_seed_words), # text is a little wrong sometimes, rare MenuItem("Destroy Seed", f=clear_seed), MenuItem("I Am Developer.", menu=maybe_dev_menu), MenuItem("Wipe Patch Area", f=wipe_filesystem), # needs better label @@ -159,6 +159,7 @@ def has_secrets(): MenuItem('Paper Wallets', f=make_paper_wallet), MenuItem("Address Explorer", f=address_explore), MenuItem('User Management', menu=make_users_menu, predicate=lambda: version.has_fatram), + MenuItem('Derive Entropy', f=drv_entro_start, predicate=lambda: version.has_fatram), MenuItem("Danger Zone", menu=DangerZoneMenu), ] From a66ae3c7e1d8b8d27a1b17cb1b633d6508c3d9f4 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:36:39 -0400 Subject: [PATCH 07/16] Some cleanup --- shared/drv_entro.py | 195 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 shared/drv_entro.py diff --git a/shared/drv_entro.py b/shared/drv_entro.py new file mode 100644 index 00000000..c016e891 --- /dev/null +++ b/shared/drv_entro.py @@ -0,0 +1,195 @@ +# (c) Copyright 2020 by Coinkite Inc. This file is part of Coldcard +# and is covered by GPLv3 license found in COPYING. +# +# BIP-(tbd): Deterministic Entropy From BIP32 Keychains +# +# Using the system's BIP32 master key, safely derive seeds phrases/entropy for other systems. +# +# see +# idea from: Ethan Kosakovsky +# +# +import stash, tcc, hmac, chains +from ux import ux_show_story, ux_enter_number, the_ux, ux_confirm +from menu import MenuItem, MenuSystem +from ubinascii import hexlify as b2a_hex +from serializations import hash160 + +def drv_entro_start(*a): + + # UX entry + ch = await ux_show_story('''\ +Create Entropy for Other Wallets + +This feature derives "entropy" based mathematically from this wallet's seed value. +This will be displayed as a 12 or 24 word seed phrase, +or formatted in other ways to make it easy to import into +other wallet systems. + +You will be able to recreate this value later, based +only the seed-phrase or backup of this Coldcard. + +There is no way to reverse the process, should the other wallet system be compromised, +so the other wallet is effectively segregated from the Coldcard and yet +also backed-up from it.''') + if ch != 'y': return + + if stash.bip39_passphrase: + if not await ux_confirm('''You have a BIP39 passphrase set right now and so that will become wrapped into the new secret.'''): + return + + choices = [ '12 words', '18 words', '24 words', 'Core HDSEED', + 'XPRV', '32-bytes Hex', '64-bytes Hex'] + + m = MenuSystem([MenuItem(c, f=drv_entro_step2) for c in choices]) + the_ux.push(m) + +def drv_entro_step2(_1, picked, _2): + from main import dis + from files import CardSlot, CardMissingError + + the_ux.pop() + + index = await ux_enter_number("Index Number?", 9999) + + if picked in (0,1,2): + # BIP39 seed phrases (we only support English) + num_words = (12, 18, 24)[picked] + width = (16, 24, 32)[picked] # of bytes + path = "m/83696968'/39'/0'/{num_words}'/{index}'".format(num_words=num_words, index=index) + s_mode = 'words' + elif picked == 3: + # HDSeed for Bitcoin Core + s_mode = 'core' + path = "m/83696968'/2'/{index}'".format(index=index) + width = 32 + elif picked == 4: + # Straight XPRV + path = "m/83696968'/32'/{index}'".format(index=index) + s_mode = 'xprv' + width = 64 + elif picked in (5, 6): + width = 32 if picked == 5 else 63 + path = "m/83696968'/128169'/{width}'/{index}'".format(width=width, index=index) + s_mode = 'hex' + else: + raise ValueError(picked) + + dis.fullscreen("Working...") + + hmac_sha512 = lambda key, msg=None: hmac.HMAC(key, msg, tcc.sha512) + + with stash.SensitiveValues() as sv: + node = sv.derive_path(path) + entropy = hmac_sha512(node.private_key(), b"bip-entropy-from-bip32").digest() + sv.register(entropy) + + # truncate for this application + new_secret = entropy[0:width] + + # only "new_secret" is interesting past here (node already blanked here) + del node + + # Reveal to user! + chain = chains.current_chain() + + if s_mode == 'words': + # BIP39 seed phrase, various lengths + words = tcc.bip39.from_data(new_secret).split(' ') + + msg = 'Seed words (%d):\n' % len(words) + msg += '\n'.join('%2d: %s' % (i+1, w) for i,w in enumerate(words)) + + encoded = stash.SecretStash.encode(seed_phrase=new_secret) + + elif s_mode == 'core': + # for Bitcoin Core: it's 32-bytes of secret exponent, base58 w/ prefix 0x80?? + # XXX zero confidence this is right + + msg = 'HDSeed:\n' + tcc.codecs.b58_encode(chain.b58_privkey + new_secret) + + # "The Hash160 of the HD seed" .. useful as confirmation? + msg += '\n\nHDSeedId:\n' + str(b2a_hex(hash160(new_secret)), 'ascii') + + msg += '\n\nRaw secret:\n' + str(b2a_hex(new_secret), 'ascii') + + encoded = stash.SecretStash.encode(master_secret=new_secret) + + elif s_mode == 'xprv': + # Raw XPRV value. + ch, pk = new_secret[0:32], new_secret[32:64] + + master_node = tcc.bip32.HDNode(chain_code=ch, private_key=pk, + child_num=0, depth=0, fingerprint=0) + + msg = 'Derived XPRV:\n' + chain.serialize_private(master_node) + + encoded = stash.SecretStash.encode(xprv=master_node) + + elif s_mode == 'hex': + # Random hex number for whatever purpose + msg = ('Hex (%d bytes):\n' % width) + str(b2a_hex(new_secret), 'ascii').upper() + + encoded = stash.SecretStash.encode(master_secret=new_secret) + else: + raise ValueError(s_mode) + + msg += '\n\nPath Used:\n ' + path + + if s_mode != 'hex': + msg += '\n\nRaw Entropy:\n' + str(b2a_hex(new_secret), 'ascii') + + prompt = '\n\nPress 1 to save to MicroSD card, 2 to switch to derived seed.' + + while 1: + ch = await ux_show_story(msg+prompt, sensitive=True, escape='12') + + if ch == '1': + # write to SD card: simple text file + try: + with CardSlot() as card: + fname, out_fn = card.pick_filename('derived.txt') + + with open(fname, 'wt') as fp: + fp.write(msg) + fp.write('\n') + except CardMissingError: + await needs_microsd() + continue + except Exception as e: + await ux_show_story('Failed to write!\n\n\n'+str(e)) + continue + + await ux_show_story("Filename is:\n\n%s" % out_fn, title='Saved') + + pass + else: + break + + stash.blank_object(new_secret) + stash.blank_object(msg) + + if ch == '2': + from main import pa, settings, dis + from pincodes import AE_SECRET_LEN + + # switch over to new secret! + dis.fullscreen("Applying...") + + stash.bip39_passphrase = '' + tmp_secret = encoded + bytes(AE_SECRET_LEN - len(encoded)) + + # monkey-patch to block SE access, and just use new secret + pa.fetch = lambda *a: bytearray(tmp_secret) + pa.change = lambda *a: None + pa.ls_fetch = pa.change + + # copies system settings to new encrypted-key value, calculates + # XFP, XPUB and saves into that, and starts using them. + pa.new_main_secret(pa.fetch()) + + await ux_show_story("New master key in effect until next power down.") + + stash.blank_object(encoded) + +# EOF From a142463edd5f6910756fba39390537f7b6a622b1 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:37:30 -0400 Subject: [PATCH 08/16] Add shortcut for testing Derive Entropy --- unix/README.md | 1 + unix/frozen-modules/sim_quickstart.py | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/unix/README.md b/unix/README.md index a54af592..41ad1b34 100644 --- a/unix/README.md +++ b/unix/README.md @@ -38,6 +38,7 @@ wallet (on testnet, always with the same seed). But there are other options: - `--users` => preset a few users: "totp", "hotp" and "pw" - `--user-mgmt` => go to the User Management menu inside settings - `--pin 123456-123456` => set PIN code to indicated value +- `--deriv` => go to the Derive Entropy menu inside settings, also loads XPRV from BIP See `frozen-modules/sim-settings.py` for the details of settings-related options. diff --git a/unix/frozen-modules/sim_quickstart.py b/unix/frozen-modules/sim_quickstart.py index b8ba6a4b..5cd9ab08 100644 --- a/unix/frozen-modules/sim_quickstart.py +++ b/unix/frozen-modules/sim_quickstart.py @@ -92,13 +92,33 @@ from main import numpad numpad.inject('x') # no HSM, thanks numpad.inject('9') - numpad.inject('9') - numpad.inject('y') # settings + numpad.inject('5') + numpad.inject('y') # advanced numpad.inject('9') numpad.inject('9') numpad.inject('5') + numpad.inject('5') numpad.inject('y') # User management +if '--deriv' in sys.argv: + # Advanced > Derive Entropy + + from sim_secel import SECRETS + from sim_settings import sim_defaults + + # XPRV from spec: xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb + SECRETS['_pin1_secret'] = '011b67969d1ec69bdfeeae43213da8460ba34b92d0788c8f7bfcfa44906e8a589c3f15e5d852dc2e9ba5e9fe189a8dd2e1547badef5b563bbe6579fc6807d80ed900000000000000' + sim_defaults['chain'] = 'BTC' + + from main import numpad + numpad.inject('9') + numpad.inject('5') + numpad.inject('y') # advanced + numpad.inject('9') + numpad.inject('9') + numpad.inject('5') # up one from bottom + numpad.inject('y') # Derive Entropy + # not best place for this import hsm hsm.POLICY_FNAME = hsm.POLICY_FNAME.replace('/flash/', '') From d90f3ba02e60f66a5ba1fffe44e7ffd4a35e6e14 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 16 Apr 2020 15:43:40 -0400 Subject: [PATCH 09/16] Fix line breaks, unwanted --- shared/drv_entro.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/shared/drv_entro.py b/shared/drv_entro.py index c016e891..2b620f94 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -21,17 +21,17 @@ def drv_entro_start(*a): ch = await ux_show_story('''\ Create Entropy for Other Wallets -This feature derives "entropy" based mathematically from this wallet's seed value. -This will be displayed as a 12 or 24 word seed phrase, -or formatted in other ways to make it easy to import into +This feature derives "entropy" based mathematically from this wallet's seed value. \ +This will be displayed as a 12 or 24 word seed phrase, \ +or formatted in other ways to make it easy to import into \ other wallet systems. -You will be able to recreate this value later, based +You will be able to recreate this value later, based \ only the seed-phrase or backup of this Coldcard. -There is no way to reverse the process, should the other wallet system be compromised, -so the other wallet is effectively segregated from the Coldcard and yet -also backed-up from it.''') +There is no way to reverse the process, should the other wallet system be compromised, \ +so the other wallet is effectively segregated from the Coldcard and yet \ +still backed-up.''') if ch != 'y': return if stash.bip39_passphrase: From 71fa2af057c57ee1f22c0ffb0bba7dc89d03af41 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Fri, 17 Apr 2020 12:35:29 -0400 Subject: [PATCH 10/16] Switch to XPRV->XPRV w/o HMAC512; bugfixes --- shared/drv_entro.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/shared/drv_entro.py b/shared/drv_entro.py index 2b620f94..c95cc434 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -69,7 +69,7 @@ def drv_entro_step2(_1, picked, _2): s_mode = 'xprv' width = 64 elif picked in (5, 6): - width = 32 if picked == 5 else 63 + width = 32 if picked == 5 else 64 path = "m/83696968'/128169'/{width}'/{index}'".format(width=width, index=index) s_mode = 'hex' else: @@ -81,11 +81,17 @@ def drv_entro_step2(_1, picked, _2): with stash.SensitiveValues() as sv: node = sv.derive_path(path) - entropy = hmac_sha512(node.private_key(), b"bip-entropy-from-bip32").digest() - sv.register(entropy) + if s_mode == 'xprv': + # strip parent-node data via encode/decode and skip HMAC512 step + encoded = stash.SecretStash.encode(xprv=node) + new_secret = None + else: + entropy = hmac_sha512(node.private_key(), b'bip-entropy-from-k').digest() + sv.register(entropy) - # truncate for this application - new_secret = entropy[0:width] + # truncate for this application + new_secret = entropy[0:width] + # only "new_secret" is interesting past here (node already blanked here) del node @@ -117,28 +123,32 @@ def drv_entro_step2(_1, picked, _2): elif s_mode == 'xprv': # Raw XPRV value. - ch, pk = new_secret[0:32], new_secret[32:64] + #ch, pk = new_secret[0:32], new_secret[32:64] + #master_node = tcc.bip32.HDNode(chain_code=ch, private_key=pk, + # child_num=0, depth=0, fingerprint=0) - master_node = tcc.bip32.HDNode(chain_code=ch, private_key=pk, - child_num=0, depth=0, fingerprint=0) + _, _, master_node = stash.SecretStash.decode(encoded) msg = 'Derived XPRV:\n' + chain.serialize_private(master_node) - encoded = stash.SecretStash.encode(xprv=master_node) - elif s_mode == 'hex': # Random hex number for whatever purpose - msg = ('Hex (%d bytes):\n' % width) + str(b2a_hex(new_secret), 'ascii').upper() + msg = ('Hex (%d bytes):\n' % width) + str(b2a_hex(new_secret), 'ascii') encoded = stash.SecretStash.encode(master_secret=new_secret) + + stash.blank_object(new_secret) + new_secret = None # no need to print it again else: raise ValueError(s_mode) - msg += '\n\nPath Used:\n ' + path + msg += '\n\nPath Used (index=%d):\n %s' % (index, path) - if s_mode != 'hex': + if new_secret: msg += '\n\nRaw Entropy:\n' + str(b2a_hex(new_secret), 'ascii') + print(msg) # XXX debug + prompt = '\n\nPress 1 to save to MicroSD card, 2 to switch to derived seed.' while 1: @@ -166,7 +176,8 @@ def drv_entro_step2(_1, picked, _2): else: break - stash.blank_object(new_secret) + if new_secret is not None: + stash.blank_object(new_secret) stash.blank_object(msg) if ch == '2': From 027635d2ef804a5ece9d49caa2337235285c8338 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Mon, 20 Apr 2020 11:06:53 -0400 Subject: [PATCH 11/16] Correct key vs. message in HMAC512 step --- shared/drv_entro.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/drv_entro.py b/shared/drv_entro.py index c95cc434..76a3660e 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -77,8 +77,6 @@ def drv_entro_step2(_1, picked, _2): dis.fullscreen("Working...") - hmac_sha512 = lambda key, msg=None: hmac.HMAC(key, msg, tcc.sha512) - with stash.SensitiveValues() as sv: node = sv.derive_path(path) if s_mode == 'xprv': @@ -86,7 +84,7 @@ def drv_entro_step2(_1, picked, _2): encoded = stash.SecretStash.encode(xprv=node) new_secret = None else: - entropy = hmac_sha512(node.private_key(), b'bip-entropy-from-k').digest() + entropy = hmac.HMAC(b'bip-entropy-from-k', node.private_key(), tcc.sha512).digest() sv.register(entropy) # truncate for this application From 76ae68101d4c90394069a26f0ca0c394679ab0a5 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Mon, 20 Apr 2020 11:46:19 -0400 Subject: [PATCH 12/16] Rename HDSEED to WIF, text cleanups, etc --- shared/drv_entro.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/shared/drv_entro.py b/shared/drv_entro.py index 76a3660e..a82ee4f3 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -21,12 +21,12 @@ def drv_entro_start(*a): ch = await ux_show_story('''\ Create Entropy for Other Wallets -This feature derives "entropy" based mathematically from this wallet's seed value. \ +This feature derives "entropy" based mathematically on this wallet's seed value. \ This will be displayed as a 12 or 24 word seed phrase, \ or formatted in other ways to make it easy to import into \ other wallet systems. -You will be able to recreate this value later, based \ +You can recreate this value later, based \ only the seed-phrase or backup of this Coldcard. There is no way to reverse the process, should the other wallet system be compromised, \ @@ -38,8 +38,8 @@ def drv_entro_start(*a): if not await ux_confirm('''You have a BIP39 passphrase set right now and so that will become wrapped into the new secret.'''): return - choices = [ '12 words', '18 words', '24 words', 'Core HDSEED', - 'XPRV', '32-bytes Hex', '64-bytes Hex'] + choices = [ '12 words', '18 words', '24 words', 'WIF (privkey)', + 'XPRV', '32-bytes hex', '64-bytes hex'] m = MenuSystem([MenuItem(c, f=drv_entro_step2) for c in choices]) the_ux.push(m) @@ -59,8 +59,8 @@ def drv_entro_step2(_1, picked, _2): path = "m/83696968'/39'/0'/{num_words}'/{index}'".format(num_words=num_words, index=index) s_mode = 'words' elif picked == 3: - # HDSeed for Bitcoin Core - s_mode = 'core' + # HDSeed for Bitcoin Core: but really a WIF of a private key, can be used anywhere + s_mode = 'wif' path = "m/83696968'/2'/{index}'".format(index=index) width = 32 elif picked == 4: @@ -76,6 +76,7 @@ def drv_entro_step2(_1, picked, _2): raise ValueError(picked) dis.fullscreen("Working...") + encoded = None with stash.SensitiveValues() as sv: node = sv.derive_path(path) @@ -106,19 +107,19 @@ def drv_entro_step2(_1, picked, _2): encoded = stash.SecretStash.encode(seed_phrase=new_secret) - elif s_mode == 'core': - # for Bitcoin Core: it's 32-bytes of secret exponent, base58 w/ prefix 0x80?? - # XXX zero confidence this is right + elif s_mode == 'wif': + # for Bitcoin Core: a 32-byte of secret exponent, base58 w/ prefix 0x80 + # - always "compressed", so has suffix of 0x01 (inside base58) + # - we're not checking it's on curve + # - we have no way to represent this internally, since we rely on bip32 - msg = 'HDSeed:\n' + tcc.codecs.b58_encode(chain.b58_privkey + new_secret) + # append 0x01 to indicate it's a compressed private key + pk = new_secret + b'\x01' - # "The Hash160 of the HD seed" .. useful as confirmation? - msg += '\n\nHDSeedId:\n' + str(b2a_hex(hash160(new_secret)), 'ascii') + msg = 'WIF (privkey):\n' + tcc.codecs.b58_encode(chain.b58_privkey + pk) msg += '\n\nRaw secret:\n' + str(b2a_hex(new_secret), 'ascii') - encoded = stash.SecretStash.encode(master_secret=new_secret) - elif s_mode == 'xprv': # Raw XPRV value. #ch, pk = new_secret[0:32], new_secret[32:64] @@ -133,8 +134,6 @@ def drv_entro_step2(_1, picked, _2): # Random hex number for whatever purpose msg = ('Hex (%d bytes):\n' % width) + str(b2a_hex(new_secret), 'ascii') - encoded = stash.SecretStash.encode(master_secret=new_secret) - stash.blank_object(new_secret) new_secret = None # no need to print it again else: @@ -147,7 +146,9 @@ def drv_entro_step2(_1, picked, _2): print(msg) # XXX debug - prompt = '\n\nPress 1 to save to MicroSD card, 2 to switch to derived seed.' + prompt = '\n\nPress 1 to save to MicroSD card' + if encoded is not None: + prompt += ', 2 to switch to derived secret.' while 1: ch = await ux_show_story(msg+prompt, sensitive=True, escape='12') @@ -156,7 +157,7 @@ def drv_entro_step2(_1, picked, _2): # write to SD card: simple text file try: with CardSlot() as card: - fname, out_fn = card.pick_filename('derived.txt') + fname, out_fn = card.pick_filename('drv-%s-idx%d.txt' % (s_mode, index)) with open(fname, 'wt') as fp: fp.write(msg) @@ -169,8 +170,6 @@ def drv_entro_step2(_1, picked, _2): continue await ux_show_story("Filename is:\n\n%s" % out_fn, title='Saved') - - pass else: break @@ -178,7 +177,7 @@ def drv_entro_step2(_1, picked, _2): stash.blank_object(new_secret) stash.blank_object(msg) - if ch == '2': + if ch == '2' and (encoded is not None): from main import pa, settings, dis from pincodes import AE_SECRET_LEN @@ -199,6 +198,7 @@ def drv_entro_step2(_1, picked, _2): await ux_show_story("New master key in effect until next power down.") - stash.blank_object(encoded) + if encoded is not None: + stash.blank_object(encoded) # EOF From ea7c31d524dfdcc0ac9e41d07bcae4167a8a4468 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Mon, 20 Apr 2020 15:05:42 -0400 Subject: [PATCH 13/16] Derived seed test code --- shared/drv_entro.py | 5 +- testing/conftest.py | 27 +++- testing/devtest/set_encoded_secret.py | 19 +++ testing/test_drv_entro.py | 174 ++++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 testing/devtest/set_encoded_secret.py create mode 100644 testing/test_drv_entro.py diff --git a/shared/drv_entro.py b/shared/drv_entro.py index a82ee4f3..636072d8 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -188,9 +188,10 @@ def drv_entro_step2(_1, picked, _2): tmp_secret = encoded + bytes(AE_SECRET_LEN - len(encoded)) # monkey-patch to block SE access, and just use new secret - pa.fetch = lambda *a: bytearray(tmp_secret) - pa.change = lambda *a: None + pa.fetch = lambda *a, **k: bytearray(tmp_secret) + pa.change = lambda *a, **k: None pa.ls_fetch = pa.change + pa.ls_change = pa.change # copies system settings to new encrypted-key value, calculates # XFP, XPUB and saves into that, and starts using them. diff --git a/testing/conftest.py b/testing/conftest.py index f8d8be75..31b1abb2 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -464,6 +464,32 @@ def doit(prv): # - actually need seed words for all tests reset_seed_words() +@pytest.fixture(scope="function") +def set_encoded_secret(sim_exec, sim_execfile, simulator, reset_seed_words): + # load simulator w/ a specific secret + + def doit(encoded): + assert 17 <= len(encoded) <= 72 + + encoded += bytes(72- len(encoded)) + + sim_exec('import main; main.ENCODED_SECRET = %r; ' % encoded) + rv = sim_execfile('devtest/set_encoded_secret.py') + if rv: pytest.fail(rv) + + simulator.start_encryption() + simulator.check_mitm() + + #print("sim xfp: 0x%08x" % simulator.master_fingerprint) + + return simulator.master_fingerprint + + yield doit + + # Important cleanup: restore normal key, because other tests assume that + # - actually need seed words for all tests + reset_seed_words() + @pytest.fixture(scope="function") def set_seed_words(sim_exec, sim_execfile, simulator, reset_seed_words): # load simulator w/ a specific bip32 master key @@ -508,7 +534,6 @@ def doit(): - @pytest.fixture() def settings_set(sim_exec): diff --git a/testing/devtest/set_encoded_secret.py b/testing/devtest/set_encoded_secret.py new file mode 100644 index 00000000..4d09b668 --- /dev/null +++ b/testing/devtest/set_encoded_secret.py @@ -0,0 +1,19 @@ +# load up the simulator w/ indicated encoded secret. could be xprv/words/etc. +import tcc, main +from sim_settings import sim_defaults +import stash, chains +from h import b2a_hex +from main import settings, pa +from stash import SecretStash, SensitiveValues +from utils import xfp2str + +settings.current = sim_defaults +settings.overrides.clear() + +raw = main.ENCODED_SECRET +pa.change(new_secret=raw) +pa.new_main_secret(raw) + +print("New key in effect: %s" % settings.get('xpub', 'MISSING')) +print("Fingerprint: %s" % xfp2str(settings.get('xfp', 0))) + diff --git a/testing/test_drv_entro.py b/testing/test_drv_entro.py new file mode 100644 index 00000000..f9c3f3e6 --- /dev/null +++ b/testing/test_drv_entro.py @@ -0,0 +1,174 @@ +# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard +# and is covered by GPLv3 license found in COPYING. +# +# test drv_entro.py features +# +import pytest, time, os +from binascii import a2b_hex +from helpers import B2A +from pycoin.key.BIP32Node import BIP32Node + +# XPRV from spec: xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb +EXAMPLE_XPRV = '011b67969d1ec69bdfeeae43213da8460ba34b92d0788c8f7bfcfa44906e8a589c3f15e5d852dc2e9ba5e9fe189a8dd2e1547badef5b563bbe6579fc6807d80ed900000000000000' + + +@pytest.mark.parametrize('mode,index,entropy,expect', [ + ('12 words', 0, + '6250b68daf746d12a24d58b4787a714b', + 'girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose'), + ('18 words', 0, + '938033ed8b12698449d4bbca3c853c66b293ea1b1ce9d9dc', + 'near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token'), + ('24 words', 0, + 'ae131e2312cdc61331542efe0d1077bac5ea803adf24b313a4f0e48e9c51f37f', + 'puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano'), + + ('WIF (privkey)', 0, + '7040bb53104f27367f317558e78a994ada7296c6fde36a364e5baf206e502bb1', + 'Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp'), + ('XPRV', 0, + None, + 'xprv9s21ZrQH143K3KJoGoKpsDsWdDNDBKs1wqFymBpCGJtrYXrfKzykGDBadZq5SrNde22F83X9qhFZr4uyV9TptTgLqCBc6XFN9tssphdxVeg'), # XXX no second-source on this one + ('32-bytes hex', 0, + None, + 'ea3ceb0b02ee8e587779c63f4b7b3a21e950a213f1ec53cab608d13e8796e6dc'), + ('64-bytes hex', 0, + None, + '492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c'), +]) +def test_bip_vectors(mode, index, entropy, expect, + set_encoded_secret, dev, cap_menu, pick_menu_item, + goto_home, cap_story, need_keypress, microsd_path, settings_set, sim_eval, sim_exec +): + + set_encoded_secret(a2b_hex(EXAMPLE_XPRV)) + settings_set('chain', 'BTC') + + goto_home() + pick_menu_item('Advanced') + pick_menu_item('Derive Entropy') + + time.sleep(0.1) + title, story = cap_story() + + assert 'seed value' in story + assert 'other wallet systems' in story + + need_keypress('y') + time.sleep(0.1) + + pick_menu_item(mode) + + if index is not None: + time.sleep(0.1) + for n in str(index): + need_keypress(n) + + need_keypress('y') + + time.sleep(0.1) + title, story = cap_story() + + assert f'Path Used (index={index}):' in story + assert "m/83696968'/" in story + assert f"/{index}'" in story + + if entropy is not None: + assert f"Raw Entropy:\n{entropy}" in story + + do_import = False + + if 'words' in mode: + num_words = int(mode.split()[0]) + assert f'Seed words ({num_words}):' in story + assert f"m/83696968'/39'/0'/{num_words}'/{index}'" in story + assert '\n 1: ' in story + assert f'\n{num_words}: ' in story + got = [ln[4:] for ln in story.split('\n') if len(ln)>5 and ln[2] == ':'] + assert ' '.join(got) == expect + do_import = 'words' + + elif mode == 'XPRV': + assert 'Derived XPRV:' in story + assert f"m/83696968'/32'/{index}'" in story + assert expect in story + do_import = 'xprv' + + elif 'WIF' in mode: + assert 'WIF (privkey)' in story + assert f"m/83696968'/2'/{index}'" in story + assert expect in story + + elif 'bytes hex' in mode: + width = int(mode.split('-')[0]) + assert width in { 32, 64} + assert f'Hex ({width} bytes):' in story + assert f"m/83696968'/128169'/{width}'/{index}'" in story + assert expect in story + + else: + raise ValueError(mode) + + # write to SD + msg = story.split('Press', 1)[0] + if 1: + assert 'Press 1 to save' in story + need_keypress('1') + + time.sleep(0.1) + title, story = cap_story() + + assert title == 'Saved' + fname = story.split('\n')[-1] + need_keypress('y') + + time.sleep(0.1) + title, story = cap_story() + + assert story.startswith(msg) + + path = microsd_path(fname) + assert path.endswith('.txt') + txt = open(path, 'rt').read() + + assert txt.strip() == msg.strip() + + + if do_import: + assert '2 to switch to derived secret' in story + + try: + time.sleep(0.1) + need_keypress('2') + + time.sleep(0.1) + title, story = cap_story() + assert 'New master key in effect' in story + + encoded = sim_eval('main.pa.fetch()') + print(encoded) + assert encoded.startswith('bytearray(b') + encoded = eval(encoded) + assert len(encoded) == 72 + + marker = encoded[0] + if do_import == 'words': + assert marker & 0x80 == 0x80 + width = ((marker & 0x3) + 2) * 8 + assert width in {16, 24, 32} + assert encoded[1:1+width] == a2b_hex(entropy) + elif do_import == 'xprv': + assert marker == 0x01 + node = BIP32Node.from_hwif(expect) + ch, pk = encoded[1:33], encoded[33:65] + assert node.chain_code() == ch + assert node.secret_exponent() == int(B2A(pk), 16) + finally: + # required cleanup + sim_exec('import main; from pincodes import PinAttempt; ' + 'main.pa = PinAttempt(); main.pa.setup("12-12"); main.pa.login();') + + + need_keypress('x') + +# EOF From cbe8374b748d9d9a0bcecb0c0394c860b88d4167 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Mon, 27 Apr 2020 10:13:15 -0400 Subject: [PATCH 14/16] XPRV created from entropy, not BIP32 process; more tests --- shared/drv_entro.py | 29 ++++++-------- testing/test_drv_entro.py | 79 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 23 deletions(-) diff --git a/shared/drv_entro.py b/shared/drv_entro.py index 636072d8..436c007a 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -39,7 +39,7 @@ def drv_entro_start(*a): return choices = [ '12 words', '18 words', '24 words', 'WIF (privkey)', - 'XPRV', '32-bytes hex', '64-bytes hex'] + 'XPRV (BIP32)', '32-bytes hex', '64-bytes hex'] m = MenuSystem([MenuItem(c, f=drv_entro_step2) for c in choices]) the_ux.push(m) @@ -64,7 +64,7 @@ def drv_entro_step2(_1, picked, _2): path = "m/83696968'/2'/{index}'".format(index=index) width = 32 elif picked == 4: - # Straight XPRV + # New XPRV path = "m/83696968'/32'/{index}'".format(index=index) s_mode = 'xprv' width = 64 @@ -80,19 +80,14 @@ def drv_entro_step2(_1, picked, _2): with stash.SensitiveValues() as sv: node = sv.derive_path(path) - if s_mode == 'xprv': - # strip parent-node data via encode/decode and skip HMAC512 step - encoded = stash.SecretStash.encode(xprv=node) - new_secret = None - else: - entropy = hmac.HMAC(b'bip-entropy-from-k', node.private_key(), tcc.sha512).digest() - sv.register(entropy) + entropy = hmac.HMAC(b'bip-entropy-from-k', node.private_key(), tcc.sha512).digest() + sv.register(entropy) - # truncate for this application - new_secret = entropy[0:width] + # truncate for this application + new_secret = entropy[0:width] - # only "new_secret" is interesting past here (node already blanked here) + # only "new_secret" is interesting past here (node already blanked at this point) del node # Reveal to user! @@ -118,15 +113,13 @@ def drv_entro_step2(_1, picked, _2): msg = 'WIF (privkey):\n' + tcc.codecs.b58_encode(chain.b58_privkey + pk) - msg += '\n\nRaw secret:\n' + str(b2a_hex(new_secret), 'ascii') - elif s_mode == 'xprv': # Raw XPRV value. - #ch, pk = new_secret[0:32], new_secret[32:64] - #master_node = tcc.bip32.HDNode(chain_code=ch, private_key=pk, - # child_num=0, depth=0, fingerprint=0) + ch, pk = new_secret[0:32], new_secret[32:64] + master_node = tcc.bip32.HDNode(chain_code=ch, private_key=pk, + child_num=0, depth=0, fingerprint=0) - _, _, master_node = stash.SecretStash.decode(encoded) + encoded = stash.SecretStash.encode(xprv=master_node) msg = 'Derived XPRV:\n' + chain.serialize_private(master_node) diff --git a/testing/test_drv_entro.py b/testing/test_drv_entro.py index f9c3f3e6..4aa2e2a9 100644 --- a/testing/test_drv_entro.py +++ b/testing/test_drv_entro.py @@ -3,10 +3,12 @@ # # test drv_entro.py features # -import pytest, time, os -from binascii import a2b_hex +import pytest, time, os, re +from binascii import a2b_hex, b2a_hex from helpers import B2A from pycoin.key.BIP32Node import BIP32Node +from pycoin.key.Key import Key +from mnemonic import Mnemonic # XPRV from spec: xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb EXAMPLE_XPRV = '011b67969d1ec69bdfeeae43213da8460ba34b92d0788c8f7bfcfa44906e8a589c3f15e5d852dc2e9ba5e9fe189a8dd2e1547badef5b563bbe6579fc6807d80ed900000000000000' @@ -26,9 +28,9 @@ ('WIF (privkey)', 0, '7040bb53104f27367f317558e78a994ada7296c6fde36a364e5baf206e502bb1', 'Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp'), - ('XPRV', 0, + ('XPRV (BIP32)', 0, None, - 'xprv9s21ZrQH143K3KJoGoKpsDsWdDNDBKs1wqFymBpCGJtrYXrfKzykGDBadZq5SrNde22F83X9qhFZr4uyV9TptTgLqCBc6XFN9tssphdxVeg'), # XXX no second-source on this one + 'xprv9s21ZrQH143K2srSbCSg4m4kLvPMzcWydgmKEnMmoZUurYuBuYG46c6P71UGXMzmriLzCCBvKQWBUv3vPB3m1SATMhp3uEjXHJ42jFg7myX'), ('32-bytes hex', 0, None, 'ea3ceb0b02ee8e587779c63f4b7b3a21e950a213f1ec53cab608d13e8796e6dc'), @@ -88,7 +90,7 @@ def test_bip_vectors(mode, index, entropy, expect, assert ' '.join(got) == expect do_import = 'words' - elif mode == 'XPRV': + elif 'XPRV' in mode: assert 'Derived XPRV:' in story assert f"m/83696968'/32'/{index}'" in story assert expect in story @@ -171,4 +173,71 @@ def test_bip_vectors(mode, index, entropy, expect, need_keypress('x') +HISTORY = set() + +@pytest.mark.parametrize('mode,pattern', [ + ('WIF (privkey)', r'[1-9A-HJ-NP-Za-km-z]{51,52}' ), + ('XPRV (BIP32)', r'[tx]prv[1-9A-HJ-NP-Za-km-z]{107}'), + ('32-bytes hex', r'[a-f0-9]{32}'), + ('64-bytes hex', r'[a-f0-9]{64}'), + ('12 words', r'[a-f0-9]{32}'), + ('18 words', r'[a-f0-9]{48}'), + ('24 words', r'[a-f0-9]{64}'), +]) +@pytest.mark.parametrize('index', [0, 1, 10, 100, 1000, 9999]) +def test_path_index(mode, pattern, index, + set_encoded_secret, dev, cap_menu, pick_menu_item, + goto_home, cap_story, need_keypress +): + # Uses any key on Simulator; just checking for operation + entropy level + + goto_home() + pick_menu_item('Advanced') + pick_menu_item('Derive Entropy') + + time.sleep(0.1) + title, story = cap_story() + + assert 'seed value' in story + assert 'other wallet systems' in story + + need_keypress('y') + time.sleep(0.1) + + pick_menu_item(mode) + + if index is not None: + time.sleep(0.1) + for n in str(index): + need_keypress(n) + + need_keypress('y') + + time.sleep(0.1) + title, story = cap_story() + + assert f'Path Used (index={index}):' in story + assert "m/83696968'/" in story + assert f"/{index}'" in story + + got = re.findall(pattern, story)[0] + + assert len(set(got)) >= 12 + + global HISTORY + assert got not in HISTORY + HISTORY.add(got) + + if 'words' in mode: + exp = Mnemonic('english').to_mnemonic(a2b_hex(got)).split() + assert '\n'.join(f'{n+1:2d}: {w}' for n, w in enumerate(exp)) in story + elif 'XPRV' in mode: + node = BIP32Node.from_hwif(got) + assert str(b2a_hex(node.chain_code()), 'ascii') in story + assert hex(node.secret_exponent())[2:] in story + elif 'WIF' in mode: + key = Key.from_text(got) + assert hex(key.secret_exponent())[2:] in story + + # EOF From e3a41d6dbbf871938cb1b24938ff648f54f72450 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Tue, 9 Jun 2020 08:26:25 -0400 Subject: [PATCH 15/16] Comments/docs updates --- releases/ChangeLog.md | 11 ++++++++++- shared/drv_entro.py | 10 ++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/releases/ChangeLog.md b/releases/ChangeLog.md index 21dc98a9..9f665f2a 100644 --- a/releases/ChangeLog.md +++ b/releases/ChangeLog.md @@ -1,3 +1,13 @@ + +- Enhancement: adds BIP-85 support: "One seed to rule them all". Takes the + secret of this Coldcard, and (safely) constructs a secret/seed phrase you + can import into another wallet system. Supports BIP-39 seeds of 12,18 or 24 words, + "HDSeed" (WIF private key) for Bitcoin Core, a fresh XPRV for BIP-32 systems, + or even 32/64 bytes of hex for other applications. The point of this is your + Coldcard backup also backs up the new wallet, since it's root secret is + deterministically derived. Advanced > DangerZone > Derive Entropy. + + ## 3.1.3 - April 30, 2020 - Enhancement: Save your BIP39 passphrases, encrypted, onto a specific SDCard, if desired. @@ -49,7 +59,6 @@ That text can be signed (always with root key) to prove authenticity. - Enhancement: Sending large PSBT files, and firmware upgrades over USB should be a little faster. - IMPORTANT: This release is NOT COMPATIBLE with Mk1 hardware. It will brick Mk1 Coldcards. ->>>>>>> a0ffa2344d659dbe196484fc75e73240c811ba6a ## 3.0.6 - Dec 19, 2019 diff --git a/shared/drv_entro.py b/shared/drv_entro.py index 436c007a..41c2e6ca 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -1,13 +1,11 @@ # (c) Copyright 2020 by Coinkite Inc. This file is part of Coldcard # and is covered by GPLv3 license found in COPYING. # -# BIP-(tbd): Deterministic Entropy From BIP32 Keychains -# -# Using the system's BIP32 master key, safely derive seeds phrases/entropy for other systems. -# -# see -# idea from: Ethan Kosakovsky +# BIP-85: Deterministic Entropy From BIP32 Keychains, by +# Ethan Kosakovsky # +# Using the system's BIP32 master key, safely derive seeds phrases/entropy for other +# wallet systems, which may expect seed phrases, XPRV, or other entropy. # import stash, tcc, hmac, chains from ux import ux_show_story, ux_enter_number, the_ux, ux_confirm From 4f83de924ecc4556d64828f16bb9f16deeced675 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Tue, 9 Jun 2020 09:13:13 -0400 Subject: [PATCH 16/16] Add bip number --- shared/drv_entro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/drv_entro.py b/shared/drv_entro.py index 41c2e6ca..a5d2c2b9 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -17,7 +17,7 @@ def drv_entro_start(*a): # UX entry ch = await ux_show_story('''\ -Create Entropy for Other Wallets +Create Entropy for Other Wallets (BIP-85) This feature derives "entropy" based mathematically on this wallet's seed value. \ This will be displayed as a 12 or 24 word seed phrase, \