예제 #1
0
async def view_seed_words(*a):
    import stash, tcc

    if not await ux_confirm(
            '''The next screen will show the seed words (and if defined, your BIP39 passphrase).\n\nAnyone with knowledge of those words can control all funds in this wallet.'''
    ):
        return

    with stash.SensitiveValues() as sv:
        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':
            from ubinascii import hexlify as b2a_hex

            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)

        stash.blank_object(msg)
    def make_msg(start):
        msg = ''
        if start == 0:
            msg = "Press 1 to save to MicroSD."
            msg += '\n\n'
        msg += "Addresses %d..%d:\n\n" % (start, start + n - 1)

        addrs = []
        chain = chains.current_chain()

        dis.fullscreen('Loading...')

        with stash.SensitiveValues() as sv:

            for idx in range(start, start + n):
                subpath = path.format(account=0, change=0, idx=idx)
                node = sv.derive_path(subpath, register=False)
                addr = chain.address(node, addr_fmt)
                addr1 = addr[:16]
                addr2 = addr[16:]
                addrs.append(addr)

                msg += "%s =>\n  %s\n  %s\n\n" % (subpath, addr1, addr2)

                dis.progress_bar_show(idx / n)

            stash.blank_object(node)

        msg += "Press 9 to see next group.\nPress 7 to see prev. group."

        return msg, addrs
예제 #3
0
    def make_msg(start):
        msg = ''
        if start == 0:
            msg = "Press 1 to save to MicroSD."
            if version.has_fatram:
                msg += " 4 to view QR Codes."
            msg += '\n\n'
        msg += "Addresses %d..%d:\n\n" % (start, start + n - 1)

        addrs = []
        chain = chains.current_chain()

        dis.fullscreen('Wait...')

        with stash.SensitiveValues() as sv:

            for idx in range(start, start + n):
                subpath = path.format(account=0, change=0, idx=idx)
                node = sv.derive_path(subpath, register=False)
                addr = chain.address(node, addr_fmt)
                addrs.append(addr)

                msg += "%s =>\n%s\n\n" % (subpath, addr)

                dis.progress_bar_show(idx / n)

            stash.blank_object(node)

        msg += "Press 9 to see next group, 7 to go back. X to quit."

        return msg, addrs
예제 #4
0
def generate_address_csv(path, addr_fmt, ms_wallet, account_num, n, start=0):
    # Produce CSV file contents as a generator

    if ms_wallet:
        from ubinascii import hexlify as b2a_hex

        # For multisig, include redeem script and derivation for each signer
        yield '"' + '","'.join(['Index', 'Payment Address',
                                    'Redeem Script (%d of %d)' % (ms_wallet.M, ms_wallet.N)] 
                                    + (['Derivation'] * ms_wallet.N)) + '"\n'

        for (idx, derivs, addr, script) in ms_wallet.yield_addresses(start, n):
            ln = '%d,"%s","%s","' % (idx, addr, b2a_hex(script).decode())
            ln += '","'.join(derivs)
            ln += '"\n'

            yield ln

        return

    yield '"Index","Payment Address","Derivation"\n'
    ch = chains.current_chain()

    with stash.SensitiveValues() as sv:
        for idx in range(start, start+n):
            deriv = path.format(account=account_num, change=0, idx=idx)
            node = sv.derive_path(deriv, register=False)

            yield '%d,"%s","%s"\n' % (idx, ch.address(node, addr_fmt), deriv)

        stash.blank_object(node)
예제 #5
0
        def make_msg():
            msg = ''
            if n > 1:
                if start == 0:
                    msg = "Press 1 to save to MicroSD."
                    if version.has_fatram and not ms_wallet:
                        msg += " 4 to view QR Codes."
                    msg += '\n\n'
                msg += "Addresses %d..%d:\n\n" % (start, start + n - 1)
            else:
                # single address, from deep path given by user
                msg += "Showing single address."
                if version.has_fatram:
                    msg += " Press 4 to view QR Codes."
                msg += '\n\n'

            addrs = []
            chain = chains.current_chain()

            dis.fullscreen('Wait...')

            if ms_wallet:
                # IMPORTANT safety feature: never show complete address
                # but show enough they can verify addrs shown elsewhere.
                # - makes a redeem script
                # - converts into addr
                # - assumes 0/0 is first address.
                for (i, paths, addr, script) in ms_wallet.yield_addresses(start, n):
                    if i == 0 and ms_wallet.N <= 4:
                        msg += '\n'.join(paths) + '\n =>\n'
                    else:
                        msg += '.../0/%d =>\n' % i

                    addrs.append(addr)
                    msg += truncate_address(addr) + '\n\n'
                    dis.progress_bar_show(i/n)

            else:
                # single-singer wallets

                with stash.SensitiveValues() as sv:

                    for idx in range(start, start + n):
                        deriv = path.format(account=self.account_num, change=0, idx=idx)
                        node = sv.derive_path(deriv, register=False)
                        addr = chain.address(node, addr_fmt)
                        addrs.append(addr)

                        msg += "%s =>\n%s\n\n" % (deriv, addr)

                        dis.progress_bar_show(idx/n)

                    stash.blank_object(node)

            if n > 1:
                msg += "Press 9 to see next group, 7 to go back. X to quit."

            return msg, addrs
예제 #6
0
async def choose_first_address(*a):
    # Choose from a truncated list of index 0 common addresses, remember
    # the last address the user selected and use it as the default
    from main import settings, dis
    chain = chains.current_chain()

    dis.fullscreen('Wait...')

    with stash.SensitiveValues() as sv:

        def truncate_address(addr):
            # Truncates address to width of screen, replacing middle chars
            middle = "-"
            leftover = SCREEN_CHAR_WIDTH - len(middle)
            start = addr[0:(leftover + 1) // 2]
            end = addr[len(addr) - (leftover // 2):]
            return start + middle + end

        # Create list of choices (address_index_0, path, addr_fmt)
        choices = []
        for name, path, addr_fmt in chains.CommonDerivations:
            if '{coin_type}' in path:
                path = path.replace('{coin_type}', str(chain.b44_cointype))
            subpath = path.format(account=0, change=0, idx=0)
            node = sv.derive_path(subpath, register=False)
            address = chain.address(node, addr_fmt)
            choices.append((truncate_address(address), path, addr_fmt))

            dis.progress_bar_show(len(choices) / len(chains.CommonDerivations))

        stash.blank_object(node)

    picked = None

    async def clicked(_1, _2, item):
        if picked is None:
            picked = item.arg
        the_ux.pop()

    items = [
        MenuItem(address, f=clicked, arg=i)
        for i, (address, path, addr_fmt) in enumerate(choices)
    ]
    menu = MenuSystem(items)
    menu.goto_idx(settings.get('axi', 0))
    the_ux.push(menu)

    await menu.interact()

    if picked is None:
        return None

    # update last clicked address
    settings.put('axi', picked)
    address, path, addr_fmt = choices[picked]

    return (path, addr_fmt)
예제 #7
0
def generate_address_csv(path, addr_fmt, n):
    # Produce CSV file contents as a generator

    yield '"Index","Payment Address","Derivation"\n'

    ch = chains.current_chain()

    with stash.SensitiveValues() as sv:
        for idx in range(n):
            subpath = path.format(account=0, change=0, idx=idx)
            node = sv.derive_path(subpath, register=False)

            yield '%d,"%s","%s"\n' % (idx, ch.address(node, addr_fmt), subpath)

        stash.blank_object(node)
예제 #8
0
    async def render(self):
        # Choose from a truncated list of index 0 common addresses, remember
        # the last address the user selected and use it as the default
        from glob import dis
        chain = chains.current_chain()

        dis.fullscreen('Wait...')

        with stash.SensitiveValues() as sv:

            # Create list of choices (address_index_0, path, addr_fmt)
            choices = []
            for name, path, addr_fmt in chains.CommonDerivations:
                if '{coin_type}' in path:
                    path = path.replace('{coin_type}', str(chain.b44_cointype))

                if self.account_num != 0 and '{account}' not in path:
                    # skip derivations that are not affected by account number
                    continue

                deriv = path.format(account=self.account_num, change=0, idx=0)
                node = sv.derive_path(deriv, register=False)
                address = chain.address(node, addr_fmt)
                choices.append( (truncate_address(address), path, addr_fmt) )

                dis.progress_bar_show(len(choices) / len(chains.CommonDerivations))

            stash.blank_object(node)

        items = [MenuItem(address, f=self.pick_single, arg=(path, addr_fmt)) 
                        for i, (address, path, addr_fmt) in enumerate(choices)]

        # some other choices
        if self.account_num == 0:
            items.append(MenuItem("Account Number", f=self.change_account))
            items.append(MenuItem("Custom Path", menu=self.make_custom))

            # if they have MS wallets, add those next
            for ms in MultisigWallet.iter_wallets():
                if not ms.addr_fmt: continue
                items.append(MenuItem(ms.name, f=self.pick_multisig, arg=ms))
        else:
            items.append(MenuItem("Account: %d" % self.account_num, f=self.change_account))

        self.goto_idx(settings.get('axi', 0))      # weak

        self.replace_items(items)
예제 #9
0
async def view_seed_words(*a):
    import stash, tcc

    if not await ux_confirm('''The next screen will show the seed words (and if defined, your BIP39 passphrase).\n\nAnyone with knowledge of those words can control all funds in this wallet.''' ):
        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

        await ux_show_story(msg, sensitive=True)

        stash.blank_object(msg)
예제 #10
0
    def make_msg(start):
        msg = "Press 1 to save to MicroSD.\n\n"
        msg += "Addresses %d..%d:\n\n" % (start, start + n - 1)

        chain = chains.current_chain()

        dis.fullscreen('Wait...')

        with stash.SensitiveValues() as sv:

            for idx in range(start, start + n):
                subpath = path.format(account=0, change=0, idx=idx)
                node = sv.derive_path(subpath, register=False)
                msg += "%s =>\n%s\n\n" % (subpath, chain.address(
                    node, addr_fmt))

                dis.progress_bar_show(idx / n)

            stash.blank_object(node)

        msg += "Press 9 to see next group, 7 to go back. X to quit."

        return msg
예제 #11
0
    def set_key(self, new_secret=None):
        # System settings (not secrets) are stored in SPI Flash, encrypted with this
        # key that is derived from main wallet secret. Call this method when the secret
        # is first loaded, or changes for some reason.
        from pincodes import pa
        from stash import blank_object

        key = None
        mine = False

        if not new_secret:
            if not pa.is_successful() or pa.is_secret_blank():
                # simple fixed key allows us to store a few things when logged out
                key = b'\0' * 32
            else:
                # read secret and use it.
                new_secret = pa.fetch()
                mine = True

        if new_secret:
            # hash up the secret... without decoding it or similar
            assert len(new_secret) >= 32

            s = sha256(new_secret)

            for round in range(5):
                s.update('pad')

                s = sha256(s.digest())

            key = s.digest()

            if mine:
                blank_object(new_secret)

        # for restore from backup case, or when changing (created) the seed
        self.nvram_key = key
예제 #12
0
    def save_storage_locker(self):
        # save the "long secret" ... probably only happens first time HSM policy
        # is activated, because we don't store that original value except here
        # and in SE.
        from main import pa

        # add length half-word to start, and pad to max size
        tmp = bytearray(AE_LONG_SECRET_LEN)
        val = self.set_sl.encode('utf8')
        ustruct.pack_into('H', tmp, 0, len(val))
        tmp[2:2 + len(self.set_sl)] = val

        # write it
        pa.ls_change(tmp)

        # memory cleanup
        blank_object(tmp)
        blank_object(val)
        blank_object(self.set_sl)
        self.set_sl = None
예제 #13
0
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: 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:
        # New 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 64
        path = "m/83696968'/128169'/{width}'/{index}'".format(width=width, index=index)
        s_mode = 'hex'
    else:
        raise ValueError(picked)

    dis.fullscreen("Working...")
    encoded = None

    with stash.SensitiveValues() as sv:
        node = sv.derive_path(path)
        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]
            

    # only "new_secret" is interesting past here (node already blanked at this point)
    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 == '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

        # append 0x01 to indicate it's a compressed private key
        pk = new_secret + b'\x01'

        msg = 'WIF (privkey):\n' + tcc.codecs.b58_encode(chain.b58_privkey + pk)

    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)

        encoded = stash.SecretStash.encode(xprv=master_node)
        
        msg = 'Derived XPRV:\n' + chain.serialize_private(master_node)

    elif s_mode == 'hex':
        # Random hex number for whatever purpose
        msg = ('Hex (%d bytes):\n' % width) + str(b2a_hex(new_secret), 'ascii')

        stash.blank_object(new_secret)
        new_secret = None       # no need to print it again
    else:
        raise ValueError(s_mode)

    msg += '\n\nPath Used (index=%d):\n  %s' % (index, path)

    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'
    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')

        if ch == '1':
            # write to SD card: simple text file
            try:
                with CardSlot() as card:
                    fname, out_fn = card.pick_filename('drv-%s-idx%d.txt' % (s_mode, index))

                    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')
        else:
            break

    if new_secret is not None:
        stash.blank_object(new_secret)
    stash.blank_object(msg)

    if ch == '2' and (encoded is not None):
        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, **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.
        pa.new_main_secret(pa.fetch())

        await ux_show_story("New master key in effect until next power down.")

    if encoded is not None:
        stash.blank_object(encoded)
예제 #14
0
async def xor_split_start(*a):

    ch = await ux_show_story('''\
Seed XOR Split

This feature splits your BIP-39 seed phrase into multiple parts. \
Each part is 24 words and looks and functions as a normal BIP-39 wallet.

We recommend spliting into just two parts, but permit up to four.

If ANY ONE of the parts is lost, then ALL FUNDS are lost and the original \
seed phrase cannot be reconstructed.

Finding a single part does not help an attacker construct the original seed.

Press 2, 3 or 4 to select number of parts to split into. ''',
                             strict_escape=True,
                             escape='234x')
    if ch == 'x': return

    num_parts = int(ch)

    ch = await ux_show_story('''\
Split Into {n} Parts

On the following screen you will be shown {n} lists of 24-words. \
The new words, when reconstructed, will re-create the seed already \
in use on this Coldcard.

The new parts are generated deterministically from your seed, so if you \
repeat this process later, the same {t} words will be shown.

If you would prefer a random split using the TRNG, press (2). \
Otherwise, press OK to continue.'''.format(n=num_parts, t=num_parts * 24),
                             escape='2')

    use_rng = (ch == '2')
    if ch == 'x': return

    await ux_dramatic_pause('Generating...', 2)

    raw_secret = bytes(32)
    try:
        with stash.SensitiveValues() as sv:
            words = None
            if sv.mode == 'words':
                words = bip39.b2a_words(sv.raw).split(' ')

            if not words or len(words) != 24:
                await ux_show_story("Need 24-seed words for this feature.")
                return

            # checksum of target result is useful.
            chk_word = words[-1]
            del words

            # going to need the secret
            raw_secret = bytearray(sv.raw)
            assert len(raw_secret) == 32

        parts = []
        for i in range(num_parts - 1):
            if use_rng:
                here = random.bytes(32)
                assert len(set(here)) > 4  # TRNG failure?
                mask = ngu.hash.sha256d(here)
            else:
                mask = ngu.hash.sha256d(b'Batshitoshi ' + raw_secret +
                                        b'%d of %d parts' % (i, num_parts))
            parts.append(mask)

        parts.append(xor32(raw_secret, *parts))

        assert xor32(*parts) == raw_secret  # selftest

    finally:
        stash.blank_object(raw_secret)

    word_parts = [bip39.b2a_words(p).split(' ') for p in parts]

    while 1:
        ch = await show_n_parts(word_parts, chk_word)
        if ch == 'x':
            if not use_rng: return
            if await ux_confirm("Stop and forget those words?"):
                return
            continue

        for ws, part in enumerate(word_parts):
            ch = await word_quiz(part, title='Word %s%%d is?' % chr(65 + ws))
            if ch == 'x': break
        else:
            break

    await ux_show_story('''\
Quiz Passed!\n
You have confirmed the details of the new split.''')
예제 #15
0
    def sign_it(self):
        # txn is approved. sign all inputs we can sign. add signatures
        # - hash the txn first
        # - sign all inputs we have the key for
        # - inputs might be p2sh, p2pkh and/or segwit style
        # - save partial inputs somewhere (append?)
        # - update our state with new partial sigs
        from main import dis
        dis.fullscreen('Signing...')

        with stash.SensitiveValues() as sv:
            # Double check the change outputs are right. This is slow, but critical because
            # it detects bad actors, not bugs or mistakes.
            change_paths = [(n, o.is_change)
                            for n, o in enumerate(self.outputs)
                            if o and o.is_change]
            if change_paths:
                for out_idx, (pubkey, subpath) in change_paths:
                    skp = path_to_str(subpath)
                    node = sv.derive_path(skp)

                    # check the pubkey of this BIP32 node
                    pu = node.public_key()
                    if pu != pubkey:
                        raise FraudulentChangeOutput(
                            "Deception regarding change output #%d. "
                            "BIP32 path doesn't match actual address." %
                            out_idx)

            # Sign individual inputs
            sigs = 0
            success = set()
            for in_idx, txi in self.input_iter():
                dis.progress_bar_show(in_idx / self.num_inputs)

                inp = self.inputs[in_idx]

                if not inp.has_utxo():
                    # maybe they didn't provide the UTXO
                    continue

                if not inp.required_key:
                    # we don't know the key for this input
                    continue

                if inp.already_signed and not inp.is_multisig:
                    # for multisig, it's possible I need to add another sig
                    # but in other cases, no more signatures are possible
                    continue

                which_key = inp.required_key
                assert not inp.added_sig, "already done??"
                assert which_key in inp.subpaths, 'unk key'

                if inp.subpaths[which_key][0] != self.my_xfp:
                    # we don't have the key for this subkey
                    continue

                txi.scriptSig = inp.scriptSig
                assert txi.scriptSig, "no scriptsig?"

                if not inp.is_segwit:
                    # Hash by serializing/blanking various subparts of the transaction
                    digest = self.make_txn_sighash(in_idx, txi, inp.sighash)
                else:
                    # Hash the inputs and such in totally new ways, based on BIP-143
                    digest = self.make_txn_segwit_sighash(
                        in_idx, txi, inp.amount, inp.scriptCode, inp.sighash)

                # Do the ACTUAL signature ... finally!!!
                skp = path_to_str(inp.subpaths[which_key])
                node = sv.derive_path(skp, register=False)

                pk = node.private_key()

                # expensive test, but works... and important
                pu = node.public_key()
                assert pu == which_key, "Path (%s) led to wrong pubkey for input#%d" % (
                    skp, in_idx)

                #print("privkey %s" % b2a_hex(pk).decode('ascii'))
                #print(" pubkey %s" % b2a_hex(which_key).decode('ascii'))
                #print(" digest %s" % b2a_hex(digest).decode('ascii'))

                result = tcc.secp256k1.sign(pk, digest)

                # private key no longer required
                stash.blank_object(pk)
                stash.blank_object(node)
                del pk, node, pu, skp

                #print("result %s" % b2a_hex(result).decode('ascii'))

                # convert signature to DER format
                assert len(result) == 65
                r = result[1:33]
                s = result[33:65]

                inp.added_sig = (which_key, ser_sig_der(r, s, inp.sighash))

                success.add(in_idx)

                # memory cleanup
                del result, r, s

                gc.collect()

        if len(success) != self.num_inputs:
            print("Wasn't able to sign input(s): %s" %
                  ', '.join('#' + str(i)
                            for i in set(range(self.num_inputs)) - success))

        # done.
        dis.progress_bar_show(1)
예제 #16
0
    async def doit(self, *a, have_key=None):
        # make the wallet.
        from main import dis

        try:
            from chains import current_chain
            import tcc
            from serializations import hash160
            from stash import blank_object

            if not have_key:
                # get some random bytes
                await ux_dramatic_pause("Picking key...", 2)
                privkey = tcc.secp256k1.generate_secret()
            else:
                # caller must range check this already: 0 < privkey < order
                privkey = have_key

            # calculate corresponding public key value
            pubkey = tcc.secp256k1.publickey(privkey,
                                             True)  # always compressed style

            dis.fullscreen("Rendering...")

            # make payment address
            digest = hash160(pubkey)
            ch = current_chain()
            if self.is_segwit:
                addr = tcc.codecs.bech32_encode(ch.bech32_hrp, 0, digest)
            else:
                addr = tcc.codecs.b58_encode(ch.b58_addr + digest)

            wif = tcc.codecs.b58_encode(ch.b58_privkey + privkey + b'\x01')

            if self.can_do_qr():
                with imported('uqr') as uqr:
                    # make the QR's now, since it's slow
                    is_alnum = self.is_segwit
                    qr_addr = uqr.make(
                        addr if not is_alnum else addr.upper(),
                        min_version=4,
                        max_version=4,
                        encoding=(uqr.Mode_ALPHANUMERIC if is_alnum else 0))

                    qr_wif = uqr.make(wif,
                                      min_version=4,
                                      max_version=4,
                                      encoding=uqr.Mode_BYTE)
            else:
                qr_addr = None
                qr_wif = None

            # Use address as filename. clearly will be unique, but perhaps a bit
            # awkward to work with.
            basename = addr

            dis.fullscreen("Saving...")
            with CardSlot() as card:
                fname, nice_txt = card.pick_filename(
                    basename + ('-note.txt' if self.template_fn else '.txt'))

                with open(fname, 'wt') as fp:
                    self.make_txt(fp, addr, wif, privkey, qr_addr, qr_wif)

                if self.template_fn:
                    fname, nice_pdf = card.pick_filename(basename + '.pdf')

                    with open(fname, 'wb') as fp:
                        self.make_pdf(fp, addr, wif, qr_addr, qr_wif)
                else:
                    nice_pdf = ''

            # Half-hearted attempt to cleanup secrets-contaminated memory
            # - better would be force user to reboot
            # - and yet, we just output the WIF to SDCard anyway
            blank_object(privkey)
            blank_object(wif)
            del qr_wif

        except CardMissingError:
            await needs_microsd()
            return
        except Exception as e:
            await ux_show_story('Failed to write!\n\n\n' + str(e))
            return

        await ux_show_story('Done! Created file(s):\n\n%s\n\n%s' %
                            (nice_txt, nice_pdf))
예제 #17
0
def drv_entro_step2(_1, picked, _2):
    from glob import dis
    from files import CardSlot, CardMissingError

    the_ux.pop()

    index = await ux_enter_number("Index Number?", 9999)

    if picked in (0,1,2):
        # BIP-39 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: 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:
        # New 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 64
        path = "m/83696968'/128169'/{width}'/{index}'".format(width=width, index=index)
        s_mode = 'hex'
    else:
        raise ValueError(picked)

    dis.fullscreen("Working...")
    encoded = None

    with stash.SensitiveValues() as sv:
        node = sv.derive_path(path)
        entropy = ngu.hmac.hmac_sha512(b'bip-entropy-from-k', node.privkey())
    
        sv.register(entropy)

        # truncate for this application
        new_secret = entropy[0:width]
            

    # only "new_secret" is interesting past here (node already blanked at this point)
    del node

    # Reveal to user!
    chain = chains.current_chain()
    qr = None
    qr_alnum = False

    if s_mode == 'words':
        # BIP-39 seed phrase, various lengths
        words = bip39.b2a_words(new_secret).split(' ')

        # encode more tightly for QR
        qr = ' '.join(w[0:4] for w in words)
        qr_alnum = True

        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 == '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

        # append 0x01 to indicate it's a compressed private key
        pk = new_secret + b'\x01'
        qr = ngu.codecs.b58_encode(chain.b58_privkey + pk)

        msg = 'WIF (privkey):\n' + qr

    elif s_mode == 'xprv':
        # Raw XPRV value.
        ch, pk = new_secret[0:32], new_secret[32:64]
        master_node = ngu.hdnode.HDNode().from_chaincode_privkey(ch, pk)

        encoded = stash.SecretStash.encode(xprv=master_node)
        qr = chain.serialize_private(master_node)
        
        msg = 'Derived XPRV:\n' + qr

    elif s_mode == 'hex':
        # Random hex number for whatever purpose
        qr = str(b2a_hex(new_secret), 'ascii')
        msg = ('Hex (%d bytes):\n' % width) + qr

        qr_alnum = True

        stash.blank_object(new_secret)
        new_secret = None       # no need to print it again
    else:
        raise ValueError(s_mode)

    msg += '\n\nPath Used (index=%d):\n  %s' % (index, path)

    if new_secret:
        msg += '\n\nRaw Entropy:\n' + str(b2a_hex(new_secret), 'ascii')

    prompt = '\n\nPress 1 to save to MicroSD card'
    if encoded is not None:
        prompt += ', 2 to switch to derived secret'
    if (qr is not None) and version.has_fatram:
        prompt += ', 3 to view as QR code.'

    while 1:
        ch = await ux_show_story(msg+prompt, sensitive=True, escape='123')

        if ch == '1':
            # write to SD card: simple text file
            try:
                with CardSlot() as card:
                    fname, out_fn = card.pick_filename('drv-%s-idx%d.txt' % (s_mode, index))

                    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')
        elif ch == '3' and version.has_fatram:
            from ux import show_qr_code
            await show_qr_code(qr, qr_alnum)
            continue
        else:
            break

    if new_secret is not None:
        stash.blank_object(new_secret)
    stash.blank_object(msg)

    if ch == '2' and (encoded is not None):
        from glob import dis
        from pincodes import pa

        # switch over to new secret!
        dis.fullscreen("Applying...")

        pa.tmp_secret(encoded)

        await ux_show_story("New master key in effect until next power down.")

    if encoded is not None:
        stash.blank_object(encoded)