Beispiel #1
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from main import dis, pa

    if not stash.bip39_passphrase:
        if not await ux_confirm(
                '''You do not have a BIP39 passphrase set right now, so this command does little except forget the seed words. It does not enhance security.'''
        ):
            return

    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)

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #2
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from common import dis, pa

    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
    stash.bip39_passphrase = ''

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #3
0
def save_wallet_seed(seed_bits):
    from common import dis, pa, settings

    print('save_wallet_seed 1')
    # encode it for our limited secret space
    nv = SecretStash.encode(seed_bits=seed_bits)
    print('save_wallet_seed 2: nv={}'.format(b2a_hex(nv)))

    dis.fullscreen('Saving Wallet...')
    pa.change(new_secret=nv)
    print('save_wallet_seed 3')

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)
    print('save_wallet_seed 4')

    # check and reload secret
    pa.reset()
    print('save_wallet_seed 5')
    pa.login()
    print('save_wallet_seed 6')
Beispiel #4
0
def set_seed_value(words=None, encoded=None):
    # Save the seed words into secure element, and reboot. BIP-39 password
    # is not set at this point (empty string)
    if words:
        bip39.a2b_words(words)      # checksum check

        # map words to bip39 wordlist indices
        data = [bip39.wordlist_en.index(w) for w in words]

        # map to packed binary representation.
        val = 0
        for v in data:
            val <<= 11
            val |= v

        # remove the checksum part
        vlen = (len(words) * 4) // 3
        val >>= (len(words) // 3)

        # convert to bytes
        seed = val.to_bytes(vlen, 'big')
        assert len(seed) == vlen

        # encode it for our limited secret space
        nv = SecretStash.encode(seed_phrase=seed)
    else:
        nv = encoded

    from glob import dis
    dis.fullscreen('Applying...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #5
0
def set_seed_value(words):
    # Save the seed words into secure element, and reboot. BIP39 password
    # is not set at this point (empty string)
    ok = tcc.bip39.check(' '.join(words))
    assert ok, "seed check: %r" % words

    # map words to bip39 wordlist indices
    data = [tcc.bip39.lookup_word(w) for w in words]

    # map to packed binary representation.
    val = 0
    for v in data:
        val <<= 11
        val |= v

    # remove the checksum part
    vlen = (len(words) * 4) // 3
    val >>= (len(words) // 3)

    # convert to bytes
    seed = val.to_bytes(vlen, 'big')
    assert len(seed) == vlen
    
    from main import dis, pa, settings

    # encode it for our limited secret space
    nv = SecretStash.encode(seed_phrase=seed)

    dis.fullscreen('Applying...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #6
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from main import dis, pa

    dis.fullscreen('Check...')

    with stash.SensitiveValues() as sv:
        nv = SecretStash.encode(xprv=sv.node)

    # Important: won't write new XFP to nvram if pw still set
    stash.bip39_passphrase = ''

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #7
0
async def import_xprv(*A):
    # read an XPRV from a text file and use it.
    import tcc, chains, ure
    from main import pa
    from stash import SecretStash
    from ubinascii import hexlify as b2a_hex
    from backups import restore_from_dict

    assert pa.is_secret_blank()  # "must not have secret"

    def contains_xprv(fname):
        # just check if likely to be valid; not full check
        try:
            with open(fname, 'rt') as fd:
                for ln in fd:
                    # match tprv and xprv, plus y/zprv etc
                    if 'prv' in ln: return True
                return False
        except OSError:
            # directories?
            return False

    # pick a likely-looking file.
    fn = await file_picker('Select file containing the XPRV to be imported.',
                           min_size=50,
                           max_size=2000,
                           taster=contains_xprv)

    if not fn: return

    node, chain, addr_fmt = None, None, None

    # open file and do it
    pat = ure.compile(r'.prv[A-Za-z0-9]+')
    with CardSlot() as card:
        with open(fn, 'rt') as fd:
            for ln in fd.readlines():
                if 'prv' not in ln: continue

                found = pat.search(ln)
                if not found: continue

                found = found.group(0)

                for ch in chains.AllChains:
                    for kk in ch.slip132:
                        if found[0] == ch.slip132[kk].hint:
                            try:
                                node = tcc.bip32.deserialize(
                                    found, ch.slip132[kk].pub,
                                    ch.slip132[kk].priv)
                                chain = ch
                                addr_fmt = kk
                                break
                            except ValueError:
                                pass
                if node:
                    break

    if not node:
        # unable
        await ux_show_story('''\
Sorry, wasn't able to find an extended private key to import. It should be at \
the start of a line, and probably starts with "xprv".''',
                            title="FAILED")
        return

    # encode it in our style
    d = dict(chain=chain.ctype,
             raw_secret=b2a_hex(SecretStash.encode(xprv=node)))
    node.blank()

    # TODO: capture the address format implied by SLIP32 version bytes
    #addr_fmt =

    # restore as if it was a backup (code reuse)
    await restore_from_dict(d)
Beispiel #8
0
async def pin_changer(_1, _2, item):
    # Help them to change pins with appropriate warnings.
    # - forcing them to drill-down to get warning about secondary is on purpose
    # - the bootloader maybe lying to us about weather we are main vs. duress
    # - there is a duress wallet for both main/sec pins, and you need to know main pin for that
    # - what may look like just policy here, is in fact enforced by the bootrom code
    #
    from main import pa, dis
    from login import LoginUX
    from pincodes import BootloaderError, EPIN_OLD_AUTH_FAIL

    mode = item.arg

    warn = {
        'main':
        ('Main PIN',
         'You will be changing the main PIN used to unlock your Coldcard. '
         "It's the one you just used a moment ago to get in here."),
        'duress': ('Duress PIN',
                   'This PIN leads to a bogus wallet. Funds are recoverable '
                   'from main seed backup, but not as easily.'),
        'secondary':
        ('Second PIN',
         'This PIN protects the "secondary" wallet that can be used to '
         'segregate funds or other banking purposes. This other wallet is '
         'completely independant of the primary.'),
        'brickme':
        ('Brickme PIN',
         'Use of this special PIN code at any prompt will destroy the '
         'Coldcard completely. It cannot be reused or salvaged, and '
         'the secrets it held are destroyed forever.\n\nDO NOT TEST THIS!'),
    }

    if pa.is_secondary:
        # secondary wallet user can only change their own password, and the secondary
        # duress pin...
        # NOTE: now excluded from menu, but keep
        if mode == 'main' or mode == 'brickme':
            await needs_primary()
            return

    if mode == 'duress' and pa.is_secret_blank():
        await ux_show_story(
            "Please set wallet seed before creating duress wallet.")
        return

    # are we changing the pin used to login?
    is_login_pin = (mode == 'main') or (mode == 'secondary'
                                        and pa.is_secondary)

    lll = LoginUX()
    lll.offer_second = False
    title, msg = warn[mode]

    async def incorrect_pin():
        await ux_show_story(
            'You provided an incorrect value for the existing %s.' % title,
            title='Wrong PIN')
        return

    # standard threats for all PIN's
    msg += '''\n\n\
THERE IS ABSOLUTELY NO WAY TO RECOVER A FORGOTTEN PIN! Write it down.

We strongly recommend all PIN codes used be unique between each other.
'''
    if not is_login_pin:
        msg += '''\nUse 999999-999999 to clear existing PIN.'''

    ch = await ux_show_story(msg, title=title)
    if ch != 'y': return

    args = {}

    need_old_pin = True

    if is_login_pin:
        # Challenge them for old password; certainly had it, because
        # we wouldn't be here otherwise.
        need_old_pin = True
    else:
        # There may be no existing PIN, and we need to learn that
        if mode == 'secondary':
            args['is_secondary'] = True
        elif mode == 'duress':
            args['is_duress'] = True
        elif mode == 'brickme':
            args['is_brickme'] = True

        old_pin = None
        try:
            dis.fullscreen("Check...")
            pa.change(old_pin=b'', new_pin=b'', **args)
            need_old_pin = False
            old_pin = ''  # converts to bytes below
        except BootloaderError as exc:
            #print("(not-error) old pin was NOT blank: %s" % exc)
            need_old_pin = True

    was_blank = not need_old_pin

    if need_old_pin:
        # We need the existing pin, so prompt for that.
        lll.subtitle = 'Old ' + title

        old_pin = await lll.prompt_pin()
        if old_pin is None:
            return await ux_aborted()

    args['old_pin'] = old_pin.encode()

    # we can verify the main pin right away here. Be nice.
    if is_login_pin and args['old_pin'] != pa.pin:
        return await incorrect_pin()

    while 1:
        lll.reset()
        lll.subtitle = "New " + title
        pin = await lll.get_new_pin(title, allow_clear=True)

        if pin is None:
            return await ux_aborted()

        is_clear = (pin == '999999-999999')

        args['new_pin'] = pin.encode() if not is_clear else b''

        if args['new_pin'] == pa.pin and not is_login_pin:
            await ux_show_story(
                "Your new PIN matches the existing PIN used to get here. "
                "It would be a bad idea to use it for another purpose.",
                title="Try Again")
            continue

        break

    # install it.
    dis.fullscreen("Saving...")

    try:
        pa.change(**args)
    except Exception as exc:
        code = exc.args[1]

        if code == EPIN_OLD_AUTH_FAIL:
            # likely: wrong old pin, on anything but main PIN
            return await incorrect_pin()
        else:
            return await ux_show_story("Unexpected low-level error: %s" %
                                       exc.args[0],
                                       title='Error')

    # Main pin is changed, and we use it lots, so update pa
    # - also to get pa.has_duress_pin() and has_brickme_pin() to be correct, need this
    dis.fullscreen("Verify...")
    pa.setup(args['new_pin'] if is_login_pin else pa.pin, pa.is_secondary)

    if not pa.is_successful():
        # typical: do need login, but if we just cleared the main PIN,
        # we cannot/need not login again
        pa.login()

    if mode == 'duress':
        # program the duress secret now... it's derived from real wallet
        from stash import SensitiveValues, SecretStash, AE_SECRET_LEN

        if is_clear:
            # clear secret, using the new pin, which is empty string
            pa.change(is_duress=True,
                      new_secret=b'\0' * AE_SECRET_LEN,
                      old_pin=b'',
                      new_pin=b'')
        else:
            with SensitiveValues() as sv:
                # derive required key
                node = sv.duress_root()
                d_secret = SecretStash.encode(xprv=node)
                sv.register(d_secret)

                # write it out.
                pa.change(is_duress=True,
                          new_secret=d_secret,
                          old_pin=args['new_pin'])
Beispiel #9
0
# load up the simulator w/ indicated test master key
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

tn = chains.BitcoinTestnet

node = tcc.bip32.deserialize(main.TPRV, tn.b32_version_pub,
                             tn.b32_version_priv)
assert node

if settings.get('xfp') == node.my_fingerprint():
    print("right xfp already")

else:
    settings.current = sim_defaults
    settings.set('chain', 'XTN')

    raw = SecretStash.encode(xprv=node)
    pa.change(new_secret=raw)
    pa.new_main_secret(raw)

    print("New key in effect: %s" % settings.get('xpub', 'MISSING'))
    print("Fingerprint: 0x%08x" % settings.get('xfp', 0))

    assert settings.get('xfp', 0) == node.my_fingerprint()