Exemple #1
0
def user_images(username, page):
    per_page = settings.get("images_per_page", 10)
    images = UserImages.get_gallery_images(page = page,\
                                           per_page = per_page,\
                                           username = username)
    show_pagination = images.wrapped_count() > per_page
    images_uploaded = bool(tuple(images))
    if not tuple(images) and page != 1:
        abort(404)
    thumbnail_size = settings.get("thumbnail_size", "l")    
    pagination = Pagination(page, per_page, images.wrapped_count() )
    return render_template("user_images.html",\
                           images_uploaded = images_uploaded,\
                           pagination = pagination,\
                           show_upload_btn = True,\
                           thumbnail_size = thumbnail_size,\
                           images = images,\
                           show_pagination = show_pagination)
Exemple #2
0
def gallery(page):
    if not get_config().gallery:
        return redirect(url_for("index"))
    per_page = settings.get("images_per_page", 10)
    images = UserImages.get_gallery_images(page = page,\
                                           per_page = per_page,\
                                           gallery = True)
    if not tuple(images) and page != 1:
        abort(404)
    count = images.wrapped_count()
    show_pagination = count > per_page
    thumbnail_size = settings.get("thumbnail_size", "l")    
    images_uploaded = count > 0
    pagination = Pagination(page, per_page, count)
    return render_template("gallery.html",\
                           images_uploaded = images_uploaded,\
                           pagination = pagination,\
                           thumbnail_size = thumbnail_size,\
                           images = images,\
                           show_pagination = show_pagination)
Exemple #3
0
def article_view(slug):
    article = Articles.get_article_by_slug(slug)
    if not article:
        abort(404)
    author = article.author
    next_article = article.get_next_article()
    previous_article = article.get_previous_article()
    user_picture = settings.get("portrait", False)
    related_articles = article.get_similar_articles()
    show_related_articles = related_articles.wrapped_count(False) > 0
    article_series = article.get_article_series()
    return render_template("article_view.html",\
                            article = article,\
                            author = author,\
                            user_picture = user_picture,\
                            show_related_articles = show_related_articles,\
                            related_articles = related_articles,\
                            next_article = next_article,\
                            article_series = article_series,\
                            previous_article = previous_article)
Exemple #4
0
def index(page):
    articles_per_page = settings.get("articles_per_page")
    articles = Articles.get_index_articles(page, articles_per_page)
    count = articles.wrapped_count()
    show_pagination = count > articles_per_page
    articles_written = count > 0
    if not articles_written and page != 1:
        abort(404)
    pagination = Pagination(page, articles_per_page, count)
    user = Users.get_user(1)
    images = dict()
    images['logo'] = settings['logo']
    images['portrait'] = settings['portrait']
    images['bg'] = settings['bg']
    if not user:
        return redirect(url_for('create_account'))
    return render_template("index.html",\
                           pagination = pagination,\
                           articles = articles,\
                           images = images,\
                           articles_written = articles_written,\
                           show_pagination = show_pagination,\
                           user = user\
                           )
Exemple #5
0
async def make_bitcoin_core_wallet(account_num=0, fname_pattern='bitcoin-core.txt'):
    from main import dis, settings
    import ustruct
    xfp = xfp2str(settings.get('xfp'))

    dis.fullscreen('Generating...')

    # make the data
    examples = []
    payload = ujson.dumps(list(generate_bitcoin_core_wallet(examples, account_num)))

    body = '''\
# Bitcoin Core Wallet Import File

https://github.com/Coldcard/firmware/blob/master/docs/bitcoin-core-usage.md

## For wallet with master key fingerprint: {xfp}

Wallet operates on blockchain: {nb}

## Bitcoin Core RPC

The following command can be entered after opening Window -> Console
in Bitcoin Core, or using bitcoin-cli:

importmulti '{payload}'

## Resulting Addresses (first 3)

'''.format(payload=payload, xfp=xfp, nb=chains.current_chain().name)

    body += '\n'.join('%s => %s' % t for t in examples)

    body += '\n'

    await write_text_file(fname_pattern, body, 'Bitcoin Core')
Exemple #6
0
    async def done_apply(self, *a):
        # apply the passphrase.
        # - important to work on empty string here too.
        from stash import bip39_passphrase
        old_pw = str(bip39_passphrase)

        err = set_bip39_passphrase(pp_sofar)
        if err:
            # kinda very late: but if not BIP39 based key, ends up here.
            return await ux_show_story(err, title="Fail")

        from main import settings
        xfp = settings.get('xfp')

        ch = await ux_show_story('''Above is the master key fingerprint of the new wallet.

Press X to abort and keep editing passphrase. OK to use the new wallet.''',
                                title="[%s]" % xfp2str(xfp))
        if ch == 'x':
            # go back!
            set_bip39_passphrase(old_pw)
            return

        goto_top_menu()
Exemple #7
0
    def handle_bag_number(self, bag_num):
        import version, callgate
        from main import dis, pa, is_devmode, settings

        if version.is_factory_mode() and bag_num:
            # check state first
            assert settings.get('tested', False)
            assert pa.is_blank()
            assert bag_num[0:2] == b'C0' and len(bag_num) == 8

            # do the change
            failed = callgate.set_bag_number(bag_num)
            assert not failed

            callgate.set_rdp_level(2 if not is_devmode else 0)
            pa.greenlight_firmware()
            dis.fullscreen(bytes(bag_num).decode())

            self.call_after(callgate.show_logout, 1)

        # always report the existing/new value
        val = callgate.get_bag_number() or b''

        return b'asci' + val
Exemple #8
0
def idle_timeout_chooser():
    from ux import DEFAULT_IDLE_TIMEOUT

    timeout = settings.get('idle_to', DEFAULT_IDLE_TIMEOUT)        # in seconds

    ch = [  ' 2 minutes',
            ' 5 minutes',
            '15 minutes',
            ' 1 hour',
            ' 4 hours',
            ' 8 hours',
            ' Never' ]
    va = [ 2*60, 5*60, 15*60,
              3600, 4*3600, 8*3600, 0 ]

    try:
        which = va.index(timeout)
    except ValueError:
        which = 0

    def set_idle_timeout(idx, text):
        settings.set('idle_to', va[idx])

    return which, ch, set_idle_timeout
Exemple #9
0
async def idle_logout():
    import main
    from main import numpad, settings

    while not main.hsm_active:
        await sleep_ms(250)

        # they may have changed setting recently
        timeout = settings.get('idle_to', DEFAULT_IDLE_TIMEOUT) * 1000  # ms
        if timeout == 0:
            continue

        now = utime.ticks_ms()

        if not numpad.last_event_time:
            continue

        if now > numpad.last_event_time + timeout:
            # do a logout now.
            print("Idle!")

            from actions import logout_now
            await logout_now()
            return  # not reached
Exemple #10
0
def generate_wasabi_wallet():
    # Generate the data for a JSON file which Wasabi can open directly as a new wallet.
    from main import settings
    import ustruct, version

    # bitcoin (xpub) is used, even for testnet case (ie. no tpub)
    # - altho, doesn't matter; the wallet operates based on it's own settings for test/mainnet
    #   regardless of the contents of the wallet file
    btc = chains.BitcoinMain

    with stash.SensitiveValues() as sv:
        xpub = btc.serialize_public(sv.derive_path("84'/0'/0'"))

    xfp = settings.get('xfp')
    txt_xfp = xfp2str(xfp)

    chain = chains.current_chain()
    assert chain.ctype in {'BTC', 'XTN'}, "Only Bitcoin supported"

    _, vers, _ = version.get_mpy_version()

    return dict(MasterFingerprint=txt_xfp,
                ColdCardFirmwareVersion=vers,
                ExtPubKey=xpub)
Exemple #11
0
async def idle_logout():
    from main import numpad, settings

    while 1:
        await sleep_ms(250)

        # they may have changed setting recently
        timeout = settings.get('idle_to', DEFAULT_IDLE_TIMEOUT) * 1000  # ms
        if timeout == 0:
            continue

        now = utime.ticks_ms()

        if not numpad.last_event_time:
            continue

        if now > numpad.last_event_time + timeout:
            # do a logout now.
            print("Idle timeout now!")

            import callgate
            callgate.show_logout()

            return
Exemple #12
0
async def ondevice_multisig_create(mode='p2wsh', addr_fmt=AF_P2WSH):
    # collect all xpub- exports on current SD card (must be > 1)
    # - ask for M value
    # - create wallet, save and also export
    # - also create electrum skel to go with that
    # - only expected to work with our ccxp-foo.json export files.
    from actions import file_picker
    import uos, ujson
    from utils import get_filesize
    from main import settings

    chain = chains.current_chain()
    my_xfp = settings.get('xfp')

    xpubs = []
    files = []
    has_mine = False
    deriv = None
    try:
        with CardSlot() as card:
            for path in card.get_paths():
                for fn, ftype, *var in uos.ilistdir(path):
                    if ftype == 0x4000:
                        # ignore subdirs
                        continue

                    if not fn.startswith('ccxp-') or not fn.endswith('.json'):
                        # wrong prefix/suffix: ignore
                        continue

                    full_fname = path + '/' + fn

                    # Conside file size
                    # sigh, OS/filesystem variations
                    file_size = var[1] if len(var) == 2 else get_filesize(
                        full_fname)

                    if not (0 <= file_size <= 1000):
                        # out of range size
                        continue

                    try:
                        with open(full_fname, 'rt') as fp:
                            vals = ujson.load(fp)

                        ln = vals.get(mode)

                        # value in file is BE32, but we want LE32 internally
                        xfp = str2xfp(vals['xfp'])
                        if not deriv:
                            deriv = vals[mode + '_deriv']
                        else:
                            assert deriv == vals[mode +
                                                 '_deriv'], "wrong derivation"

                        node, _, _ = import_xpub(ln)

                        if xfp == my_xfp:
                            has_mine = True

                        xpubs.append(
                            (xfp, chain.serialize_public(node, AF_P2SH)))
                        files.append(fn)

                    except CardMissingError:
                        raise

                    except Exception as exc:
                        # show something for coders, but no user feedback
                        sys.print_exception(exc)
                        continue

    except CardMissingError:
        await needs_microsd()
        return

    # remove dups; easy to happen if you double-tap the export
    delme = set()
    for i in range(len(xpubs)):
        for j in range(len(xpubs)):
            if j in delme: continue
            if i == j: continue
            if xpubs[i] == xpubs[j]:
                delme.add(j)
    if delme:
        xpubs = [x for idx, x in enumerate(xpubs) if idx not in delme]

    if not xpubs or len(xpubs) == 1 and has_mine:
        await ux_show_story(
            "Unable to find any Coldcard exported keys on this card. Must have filename: ccxp-....json"
        )
        return

    # add myself if not included already
    if not has_mine:
        with stash.SensitiveValues() as sv:
            node = sv.derive_path(deriv)
            xpubs.append((my_xfp, chain.serialize_public(node, AF_P2SH)))

    N = len(xpubs)

    if N > MAX_SIGNERS:
        await ux_show_story("Too many signers, max is %d." % MAX_SIGNERS)
        return

    # pick useful M value to start
    assert N >= 2
    M = (N - 1) if N < 4 else ((N // 2) + 1)

    while 1:
        msg = '''How many need to sign?\n      %d of %d

Press (7 or 9) to change M value, or OK \
to continue.

If you expected more or less keys (N=%d #files=%d), \
then check card and file contents.

Coldcard multisig setup file and an Electrum wallet file will be created automatically.\
''' % (M, N, N, len(files))

        ch = await ux_show_story(msg, escape='123479')

        if ch in '1234':
            M = min(N, int(ch))  # undocumented shortcut
        elif ch == '9':
            M = min(N, M + 1)
        elif ch == '7':
            M = max(1, M - 1)
        elif ch == 'x':
            await ux_dramatic_pause('Aborted.', 2)
            return
        elif ch == 'y':
            break

    # create appropriate object
    assert 1 <= M <= N <= MAX_SIGNERS

    name = 'CC-%d-of-%d' % (M, N)
    ms = MultisigWallet(name, (M, N),
                        xpubs,
                        chain_type=chain.ctype,
                        common_prefix=deriv[2:],
                        addr_fmt=addr_fmt)

    from auth import NewEnrollRequest, active_request

    active_request = NewEnrollRequest(ms, auto_export=True)

    # menu item case: add to stack
    from ux import the_ux
    the_ux.push(active_request)
Exemple #13
0
async def export_multisig_xpubs(*a):
    # WAS: Create a single text file with lots of docs, and all possible useful xpub values.
    # THEN: Just create the one-liner xpub export value they need/want to support BIP45
    # NOW: Export JSON with one xpub per useful address type and semi-standard derivation path
    #
    # Consumer for this file is supposed to be ourselves, when we build on-device multisig.
    #
    from main import settings
    xfp = xfp2str(settings.get('xfp', 0))
    chain = chains.current_chain()

    fname_pattern = 'ccxp-%s.json' % xfp

    msg = '''\
This feature creates a small file containing \
the extended public keys (XPUB) you would need to join \
a multisig wallet using the 'Create Airgapped' feature.

The public keys exported are:

BIP45:
   m/45'
P2WSH-P2SH:
   m/48'/{coin}'/0'/1'
P2WSH:
   m/48'/{coin}'/0'/2'

OK to continue. X to abort.
'''.format(coin=chain.b44_cointype)

    resp = await ux_show_story(msg)
    if resp != 'y': return

    try:
        with CardSlot() as card:
            fname, nice = card.pick_filename(fname_pattern)
            # do actual write: manual JSON here so more human-readable.
            with open(fname, 'wt') as fp:
                fp.write('{\n')
                with stash.SensitiveValues() as sv:
                    for deriv, name, fmt in [
                        ("m/45'", 'p2sh', AF_P2SH),
                        ("m/48'/{coin}'/0'/1'", 'p2wsh_p2sh', AF_P2WSH_P2SH),
                        ("m/48'/{coin}'/0'/2'", 'p2wsh', AF_P2WSH)
                    ]:

                        dd = deriv.format(coin=chain.b44_cointype)
                        node = sv.derive_path(dd)
                        xp = chain.serialize_public(node, fmt)
                        fp.write('  "%s_deriv": "%s",\n' % (name, dd))
                        fp.write('  "%s": "%s",\n' % (name, xp))

                fp.write('  "xfp": "%s"\n}\n' % xfp)

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

    msg = '''BIP45 multisig xpub file written:\n\n%s''' % nice
    await ux_show_story(msg)
Exemple #14
0
async def start_login_sequence():
    # Boot up login sequence here.
    #
    from main import pa, settings, dis, loop, numpad
    import version

    if pa.is_blank():
        # Blank devices, with no PIN set all, can continue w/o login

        # Do green-light set immediately after firmware upgrade
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

        goto_top_menu()
        return

    # Allow impatient devs and crazy people to skip the PIN
    guess = settings.get('_skip_pin', None)
    if guess is not None:
        try:
            dis.fullscreen("(Skip PIN)")
            pa.setup(guess)
            pa.login()
        except:
            pass

    # if that didn't work, or no skip defined, force
    # them to login succefully.
    while not pa.is_successful():
        # always get a PIN and login first
        await block_until_login()

    # Must read settings after login
    settings.set_key()
    settings.load()

    # Restore a login preference or two
    numpad.sensitivity = settings.get('sens', numpad.sensitivity)

    # Do green-light set immediately after firmware upgrade
    if not pa.is_secondary:
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

    # Populate xfp/xpub values, if missing.
    # - can happen for first-time login of duress wallet
    # - may indicate lost settings, which we can easily recover from
    # - these values are important to USB protocol
    if not (settings.get('xfp', 0)
            and settings.get('xpub', 0)) and not pa.is_secret_blank():
        try:
            import stash

            # Recalculate xfp/xpub values (depends both on secret and chain)
            with stash.SensitiveValues() as sv:
                sv.capture_xpub()
        except Exception as exc:
            # just in case, keep going; we're not useless and this
            # is early in boot process
            print("XFP save failed: %s" % exc)

    # Allow USB protocol, now that we are auth'ed
    from usb import enable_usb
    enable_usb(loop, False)

    goto_top_menu()
Exemple #15
0
# load up the simulator w/ indicated list of seed words
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 seed import set_seed_value

tn = chains.BitcoinTestnet

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

    set_seed_value(main.WORDS)

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

Exemple #16
0
 def get(cls):
     from main import settings
     rv = settings.get(KEY)
     return rv or dict()
Exemple #17
0
 def load_cache(cls):
     # first time: read saved value, but rest of time; use what's in memory
     if not cls._cache_loaded:
         saved = settings.get(cls.KEY) or []
         cls.runtime_cache.extend(saved)
         cls._cache_loaded = True
Exemple #18
0
]

DebugFunctionsMenu = [
    #         xxxxxxxxxxxxxxxx
    MenuItem('Debug: assert', f=debug_assert),
    MenuItem('Debug: except', f=debug_except),
    MenuItem('Check: BL FW', f=check_firewall_read),
    MenuItem('Warm Reset', f=reset_self),
    #MenuItem("Perform Selftest", f=start_selftest),
]

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("Destroy Seed", f=clear_seed),
    MenuItem("I Am Developer.", menu=maybe_dev_menu),
    MenuItem("Wipe Patch Area", f=wipe_filesystem),             # needs better label
    MenuItem('Perform Selftest', f=start_selftest),             # little harmful
    MenuItem("Set High-Water", f=set_highwater),
]

BackupStuffMenu = [
    #         xxxxxxxxxxxxxxxx
    MenuItem("Backup System", f=backup_everything),
    MenuItem("Verify Backup", f=verify_backup),
    MenuItem("Restore Backup", f=restore_everything),   # just a redirect really
    MenuItem("Dump Summary", f=dump_summary),
]
Exemple #19
0
    async def handle(self, cmd, args):
        # Dispatch incoming message, and provide reply.

        try:
            cmd = bytes(cmd).decode()
        except:
            raise FramingError('decode')

        if cmd == 'dfu_':
            # only useful in factory, undocumented.
            return self.call_after(callgate.enter_dfu)

        if cmd == 'rebo':
            import machine
            return self.call_after(machine.reset)

        if cmd == 'logo':
            return self.call_after(callgate.show_logout)

        if cmd == 'ping':
            return b'biny' + args

        if cmd == 'upld':
            offset, total_size = unpack_from('<II', args)
            data = memoryview(args)[4 + 4:]

            return await self.handle_upload(offset, total_size, data)

        if cmd == 'dwld':
            offset, length, fileno = unpack_from('<III', args)
            return await self.handle_download(offset, length, fileno)

        if cmd == 'ncry':
            version, his_pubkey = unpack_from('<I64s', args)

            return self.handle_crypto_setup(version, his_pubkey)

        if cmd == 'vers':
            from version import get_mpy_version
            from callgate import get_bl_version

            # Returning: date, version(human), bootloader version, full date version
            # BUT: be ready for additions!
            rv = list(get_mpy_version())
            rv.insert(2, get_bl_version()[0])

            return b'asci' + ('\n'.join(rv)).encode()

        if cmd == 'sha2':
            return b'biny' + self.file_checksum.digest()

        if cmd == 'xpub':
            assert self.encrypted_req, 'must encrypt'
            return self.handle_xpub(str(args, 'ascii'))

        if cmd == 'mitm':
            assert self.encrypted_req, 'must encrypt'
            return await self.handle_mitm_check()

        if cmd == 'smsg':
            # sign message
            addr_fmt, len_subpath, len_msg = unpack_from('<III', args)
            subpath = args[12:12 + len_subpath]
            msg = args[12 + len_subpath:]
            assert len(msg) == len_msg, "badlen"

            from auth import sign_msg
            sign_msg(msg, subpath, addr_fmt)
            return None

        if cmd == 'p2sh':
            # show P2SH (probably multisig) address on screen (also provides it back)
            # - must provide redeem script, and list of [xfp+path]
            from auth import start_show_p2sh_address

            # new multsig goodness, needs mapping from xfp->path and M values
            addr_fmt, M, N, script_len = unpack_from('<IBBH', args)

            assert addr_fmt & AFC_SCRIPT
            assert 1 <= M <= N <= 20
            assert 30 <= script_len <= 520

            offset = 8
            witdeem_script = args[offset:offset + script_len]
            offset += script_len

            assert len(witdeem_script) == script_len

            xfp_paths = []
            for i in range(N):
                ln = args[offset]
                assert 2 <= ln <= 16, 'badlen'
                xfp_paths.append(unpack_from('<%dI' % ln, args, offset + 1))
                offset += (ln * 4) + 1

            assert offset == len(args)

            return b'asci' + start_show_p2sh_address(M, N, addr_fmt, xfp_paths,
                                                     witdeem_script)

        if cmd == 'show':
            # simple cases, older code: text subpath
            from auth import start_show_address

            addr_fmt, = unpack_from('<I', args)
            assert not (addr_fmt & AFC_SCRIPT)

            return b'asci' + start_show_address(addr_fmt, subpath=args[4:])

        if cmd == 'enrl':
            # Enroll new xpubkey to be involved in multisigs.
            # - text config file must already be uploaded

            file_len, file_sha = unpack_from('<I32s', args)
            if file_sha != self.file_checksum.digest():
                return b'err_Checksum'
            assert 100 < file_len <= (20 * 200), "badlen"

            # Start an UX interaction, return immediately here
            from auth import maybe_enroll_xpub
            maybe_enroll_xpub(sf_len=file_len, ux_reset=True)
            return None

        if cmd == 'msck':
            # Quick check to test if we have a wallet already installed.
            from multisig import MultisigWallet
            M, N, xfp_xor = unpack_from('<3I', args)

            return int(MultisigWallet.quick_check(M, N, xfp_xor))

        if cmd == 'stxn':
            # sign transaction
            txn_len, finalize, txn_sha = unpack_from('<II32s', args)
            if txn_sha != self.file_checksum.digest():
                return b'err_Checksum'

            assert 50 < txn_len <= MAX_TXN_LEN, "bad txn len"

            from auth import sign_transaction
            sign_transaction(txn_len, bool(finalize))
            return None

        if cmd == 'stok' or cmd == 'bkok' or cmd == 'smok' or cmd == 'pwok' or cmd == 'enok':
            # Have we finished (whatever) the transaction,
            # which needed user approval? If so, provide result.
            from auth import active_request, UserAuthorizedAction

            if not active_request:
                return b'err_No active request'

            if active_request.refused:
                UserAuthorizedAction.cleanup()
                return b'refu'

            if active_request.failed:
                rv = b'err_' + active_request.failed.encode()
                UserAuthorizedAction.cleanup()
                return rv

            if not active_request.result:
                # STILL waiting on user
                return None

            if cmd == 'pwok' or cmd == 'enok':
                # return new root xpub
                xpub = active_request.result
                UserAuthorizedAction.cleanup()
                return b'asci' + bytes(xpub, 'ascii')
            elif cmd == 'smok':
                # signed message done: just give them the signature
                addr, sig = active_request.address, active_request.result
                UserAuthorizedAction.cleanup()
                return pack('<4sI', 'smrx', len(addr)) + addr.encode() + sig
            else:
                # generic file response
                resp_len, sha = active_request.result
                UserAuthorizedAction.cleanup()
                return pack('<4sI32s', 'strx', resp_len, sha)

        if cmd == 'pass':
            # bip39 passphrase provided, maybe use it if authorized
            assert self.encrypted_req, 'must encrypt'
            from auth import start_bip39_passphrase
            from main import settings

            assert settings.get('words', True), 'no seed'
            assert len(args) < 400, 'too long'
            pw = str(args, 'utf8')
            assert len(pw) < 100, 'too long'

            return start_bip39_passphrase(pw)

        if cmd == 'back':
            # start backup: asks user, takes long time.
            from auth import start_remote_backup
            return start_remote_backup()

        if cmd == 'bagi':
            return self.handle_bag_number(args)

        if is_simulator() and cmd[0].isupper():
            # special hacky commands to support testing w/ the simulator
            from sim_usb import do_usb_command
            return do_usb_command(cmd, args)

        print("USB garbage: %s +[%d]" % (cmd, len(args)))

        return b'err_Unknown cmd'
Exemple #20
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()
Exemple #21
0
#   execfile('../../testing/devtest/nvram.py')

from ubinascii import hexlify as b2a_hex
from ubinascii import unhexlify as a2b_hex

import tcc, ustruct
from main import settings, sf
from nvstore import SLOTS

# reset whatever's there
sf.chip_erase()
settings.load()

for v in [123, 'hello', 34.56, dict(a=45)]:
    settings.set('abc', v)
    assert settings.get('abc') == v

a = settings.get('_age', -1)
settings.save()
assert settings.get('_age') >= a + 1, [settings.get('_age'), a + 1]

chk = dict(settings.current)
settings.load()

# some minor differences in values: bytes vs. strings, so just check keys
assert sorted(list(chk)) == sorted(list(settings.current)), \
    'readback fail: \n%r != \n%r' % (chk, settings.current)

if 1:
    # fill it up
    covered = set()
Exemple #22
0
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import main
from main import settings
from ujson import dumps
RV.write(dumps(settings.get(main.SKEY)))

Exemple #23
0
 def exists(cls):
     # are there any wallets defined?
     from main import settings
     return bool(settings.get('multisig', False))
Exemple #24
0
    def from_file(cls, config, name=None):
        # Given a simple text file, parse contents and create instance (unsaved).
        # format is:         label: value
        # where label is:
        #       name: nameforwallet
        #       policy: M of N
        #       (8digithex): xpub of cosigner
        #
        # quick checks:
        # - name: 1-20 ascii chars
        # - M of N line (assume N of N if not spec'd)
        # - xpub: any bip32 serialization we understand, but be consistent
        #
        from main import settings

        my_xfp = settings.get('xfp')
        common_prefix = None
        xpubs = []
        path_tops = set()
        M, N = -1, -1
        has_mine = False
        addr_fmt = AF_P2SH
        expect_chain = chains.current_chain().ctype

        lines = config.split('\n')

        for ln in lines:
            # remove comments
            comm = ln.find('#')
            if comm != -1:
                ln = ln[0:comm]

            ln = ln.strip()

            if ':' not in ln:
                if 'pub' in ln:
                    # optimization: allow bare xpub if we can calc xfp
                    label = '0' * 8
                    value = ln
                else:
                    # complain?
                    if ln: print("no colon: " + ln)
                    continue
            else:
                label, value = ln.split(':')
                label = label.lower()

            value = value.strip()

            if label == 'name':
                name = value
            elif label == 'policy':
                try:
                    # accepts: 2 of 3    2/3    2,3    2 3   etc
                    mat = ure.search(r'(\d+)\D*(\d+)', value)
                    assert mat
                    M = int(mat.group(1))
                    N = int(mat.group(2))
                    assert 1 <= M <= N <= MAX_SIGNERS
                except:
                    raise AssertionError('bad policy line')

            elif label == 'derivation':
                # reveal the **common** path derivation for all keys
                try:
                    mat = ure.search(r"(m/)([0123456789/']+)", value)
                    assert mat
                    common_prefix = mat.group(2)
                    assert common_prefix
                    assert 1 <= len(common_prefix) < 30
                except:
                    raise AssertionError('bad derivation line')

            elif label == 'format':
                # pick segwit vs. classic vs. wrapped version
                value = value.lower()
                for fmt_code, fmt_label in cls.FORMAT_NAMES:
                    if value == fmt_label:
                        addr_fmt = fmt_code
                        break
                else:
                    raise AssertionError('bad format line')
            elif len(label) == 8:
                try:
                    xfp = str2xfp(label)
                except:
                    # complain?
                    #print("Bad xfp: " + ln)
                    continue

                # deserialize, update list and lots of checks
                xfp = cls.check_xpub(xfp, value, expect_chain, xpubs,
                                     path_tops)

                if xfp == my_xfp:
                    # not conclusive, but enough for error catching.
                    has_mine = True

        assert len(xpubs), 'need xpubs'

        if M == N == -1:
            # default policy: all keys
            N = M = len(xpubs)

        if not name:
            # provide a default name
            name = '%d-of-%d' % (M, N)

        try:
            name = str(name, 'ascii')
            assert 1 <= len(name) <= 20
        except:
            raise AssertionError('name must be ascii, 1..20 long')

        assert 1 <= M <= N <= MAX_SIGNERS, 'M/N range'
        assert N == len(xpubs), 'wrong # of xpubs, expect %d' % N
        assert addr_fmt & AFC_SCRIPT, 'script style addr fmt'

        # check we're included... do not insert ourselves, even tho we
        # have enough info, simply because other signers need to know my xpubkey anyway
        assert has_mine, 'my key not included'

        if not common_prefix and len(path_tops) == 1:
            # fill in the common prefix iff we can deduce it from xpubs
            common_prefix = path_tops.pop()

        # done. have all the parts
        return cls(name, (M, N),
                   xpubs,
                   addr_fmt=addr_fmt,
                   chain_type=expect_chain,
                   common_prefix=common_prefix)
Exemple #25
0
# quickly clear all multisig wallets installed
from main import settings
from ux import restore_menu

if settings.get('multisig'):
    del settings.current['multisig']
    settings.save()

    print("cleared multisigs")

restore_menu()
Exemple #26
0
async def start_login_sequence():
    # Boot up login sequence here.
    #
    from main import pa, settings, dis, loop, numpad
    from ux import idle_logout

    if pa.is_blank():
        # Blank devices, with no PIN set all, can continue w/o login

        # Do green-light set immediately after firmware upgrade
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

        goto_top_menu()
        return

    # maybe show a nickname before we do anything
    nickname = settings.get('nick', None)
    if nickname:
        try:
            await show_nickname(nickname)
        except: pass

    # Allow impatient devs and crazy people to skip the PIN
    guess = settings.get('_skip_pin', None)
    if guess is not None:
        try:
            dis.fullscreen("(Skip PIN)")
            pa.setup(guess)
            pa.login()
        except: pass

    # if that didn't work, or no skip defined, force
    # them to login succefully.
    while not pa.is_successful():
        # always get a PIN and login first
        await block_until_login()

    # Must re-read settings after login
    settings.set_key()
    settings.load()

    # implement "login countdown" feature
    delay = settings.get('lgto', 0)
    if delay:
        pa.reset()
        await login_countdown(delay)
        await block_until_login()

    # implement idle timeout now that we are logged-in
    loop.create_task(idle_logout())

    # Do green-light set immediately after firmware upgrade
    if not pa.is_secondary:
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

    # Populate xfp/xpub values, if missing.
    # - can happen for first-time login of duress wallet
    # - may indicate lost settings, which we can easily recover from
    # - these values are important to USB protocol
    if not (settings.get('xfp', 0) and settings.get('xpub', 0)) and not pa.is_secret_blank():
        try:
            import stash

            # Recalculate xfp/xpub values (depends both on secret and chain)
            with stash.SensitiveValues() as sv:
                sv.capture_xpub()
        except Exception as exc:
            # just in case, keep going; we're not useless and this
            # is early in boot process
            print("XFP save failed: %s" % exc)

    # If HSM policy file is available, offer to start that,
    # **before** the USB is even enabled.
    if version.has_fatram:
        try:
            import hsm, hsm_ux

            if hsm.hsm_policy_available():
                ar = await hsm_ux.start_hsm_approval(usb_mode=False, startup_mode=True)
                if ar:
                    await ar.interact()
        except: pass

    # Allow USB protocol, now that we are auth'ed
    from usb import enable_usb
    enable_usb(loop, False)

    goto_top_menu()
Exemple #27
0
    def from_file(cls, config, name=None):
        # Given a simple text file, parse contents and create instance (unsaved).
        # format is:         label: value
        # where label is:
        #       name: nameforwallet
        #       policy: M of N
        #       format: p2sh  (+etc)
        #       derivation: m/45'/0     (common prefix)
        #       (8digithex): xpub of cosigner
        # 
        # quick checks:
        # - name: 1-20 ascii chars
        # - M of N line (assume N of N if not spec'd)
        # - xpub: any bip32 serialization we understand, but be consistent
        #
        from main import settings

        my_xfp = settings.get('xfp')
        deriv = None
        xpubs = []
        M, N = -1, -1
        has_mine = 0
        addr_fmt = AF_P2SH
        expect_chain = chains.current_chain().ctype

        lines = config.split('\n')

        for ln in lines:
            # remove comments
            comm = ln.find('#')
            if comm == 0:
                continue
            if comm != -1:
                if not ln[comm+1:comm+2].isdigit():
                    ln = ln[0:comm]

            ln = ln.strip()

            if ':' not in ln:
                if 'pub' in ln:
                    # pointless optimization: allow bare xpub if we can calc xfp
                    label = '0'*8
                    value = ln
                else:
                    # complain?
                    #if ln: print("no colon: " + ln)
                    continue
            else:
                label, value = ln.split(':', 1)
                label = label.lower()

            value = value.strip()

            if label == 'name':
                name = value
            elif label == 'policy':
                try:
                    # accepts: 2 of 3    2/3    2,3    2 3   etc
                    mat = ure.search(r'(\d+)\D*(\d+)', value)
                    assert mat
                    M = int(mat.group(1))
                    N = int(mat.group(2))
                    assert 1 <= M <= N <= MAX_SIGNERS
                except:
                    raise AssertionError('bad policy line')

            elif label == 'derivation':
                # reveal the path derivation for following key(s)
                try:
                    assert value, 'blank'
                    deriv = cleanup_deriv_path(value)
                except BaseException as exc:
                    raise AssertionError('bad derivation line: ' + str(exc))

            elif label == 'format':
                # pick segwit vs. classic vs. wrapped version
                value = value.lower()
                for fmt_code, fmt_label in cls.FORMAT_NAMES:
                    if value == fmt_label:
                        addr_fmt = fmt_code
                        break
                else:
                    raise AssertionError('bad format line')
            elif len(label) == 8:
                try:
                    xfp = str2xfp(label)
                except:
                    # complain?
                    #print("Bad xfp: " + ln)
                    continue

                # deserialize, update list and lots of checks
                is_mine = cls.check_xpub(xfp, value, deriv, expect_chain, my_xfp, xpubs)
                if is_mine:
                    has_mine += 1

        assert len(xpubs), 'need xpubs'

        if M == N == -1:
            # default policy: all keys
            N = M = len(xpubs)

        if not name:
            # provide a default name
            name = '%d-of-%d' % (M, N)

        try:
            name = str(name, 'ascii')
            assert 1 <= len(name) <= 20
        except:
            raise AssertionError('name must be ascii, 1..20 long')

        assert 1 <= M <= N <= MAX_SIGNERS, 'M/N range'
        assert N == len(xpubs), 'wrong # of xpubs, expect %d' % N
        assert addr_fmt & AFC_SCRIPT, 'script style addr fmt'

        # check we're included... do not insert ourselves, even tho we
        # have enough info, simply because other signers need to know my xpubkey anyway
        assert has_mine != 0, 'my key not included'
        assert has_mine == 1    # 'my key included more than once'

        # done. have all the parts
        return cls(name, (M, N), xpubs, addr_fmt=addr_fmt, chain_type=expect_chain)
Exemple #28
0
async def test_7z():
    # test full 7z round-trip
    # Altho cleartext mode is not for real, if the code is written, I must test it.
    from backups import write_complete_backup, restore_complete_doit
    from sffile import SFFile
    import tcc, version, uos
    from main import settings, sf, numpad, pa

    if version.has_fatram:
        import hsm
        had_policy = hsm.hsm_policy_available()
    else:
        had_policy = False

    today = tcc.random.uniform(1000000)

    import machine
    machine.reset = lambda: None

    for chain in ['BTC', 'XTN']:
        for words in ([], ['abc', 'def']):
            settings.set('check', today)
            settings.set('chain', chain)

            if version.has_608:
                ls = b'%416d' % today
                pa.ls_change(ls)

            ll, sha = await write_complete_backup(words, None, True)

            result = SFFile(0, ll).read()

            if words:
                #open('debug.7z', 'wb').write(result)
                assert ll > 800
                assert len(sha) == 32
                assert result[0:6] == b"7z\xbc\xaf'\x1c"
                assert tcc.sha256(result).digest() == sha
                assert len(set(result)) >= 240  # encrypted
            else:
                sr = str(result, 'ascii')
                print("Backup contents:\n" + sr)
                assert sr[0] == '#', result
                assert 'Coldcard' in sr
                assert len(set(sr)) < 100  # cleartext, english
                assert ('chain = "%s"' % chain) in result

            # test restore
            # - cant wipe flash, since the backup file is there
            # - cant wipe all settings becuase PIN and stuff is simulated there
            del settings.current['check']

            if had_policy:
                from hsm import POLICY_FNAME
                uos.unlink(POLICY_FNAME)
                assert not hsm.hsm_policy_available()

            with SFFile(0, ll) as fd:
                numpad.inject('y')  # for 'success' message
                await restore_complete_doit(fd, words)

                assert settings.get('check') == today, \
                            (settings.get('check'), '!=',  today)
                assert settings.get('chain') == chain, \
                            (settings.get('chain'), '!=',  chain)

                if version.has_608:
                    assert pa.ls_fetch() == ls

            if had_policy:
                assert had_policy == hsm.hsm_policy_available()

            today += 3

            import ux
            ux.restore_menu()
Exemple #29
0
    async def handle(self, cmd, args):
        # Dispatch incoming message, and provide reply.
        from main import hsm_active, is_devmode

        try:
            cmd = bytes(cmd).decode()
        except:
            raise FramingError('decode')

        if cmd[0].isupper() and (is_simulator() or is_devmode):
            # special hacky commands to support testing w/ the simulator
            try:
                from usb_test_commands import do_usb_command
                return do_usb_command(cmd, args)
            except:
                raise
                pass

        if hsm_active:
            # only a few commands are allowed during HSM mode
            if cmd not in HSM_WHITELIST:
                raise HSMDenied

        if cmd == 'dfu_':
            # only useful in factory, undocumented.
            return self.call_after(callgate.enter_dfu)

        if cmd == 'rebo':
            import machine
            return self.call_after(machine.reset)

        if cmd == 'logo':
            from utils import clean_shutdown
            return self.call_after(clean_shutdown)

        if cmd == 'ping':
            return b'biny' + args

        if cmd == 'upld':
            offset, total_size = unpack_from('<II', args)
            data = memoryview(args)[4 + 4:]

            return await self.handle_upload(offset, total_size, data)

        if cmd == 'dwld':
            offset, length, fileno = unpack_from('<III', args)
            return await self.handle_download(offset, length, fileno)

        if cmd == 'ncry':
            version, his_pubkey = unpack_from('<I64s', args)

            return self.handle_crypto_setup(version, his_pubkey)

        if cmd == 'vers':
            from version import get_mpy_version, hw_label
            from callgate import get_bl_version

            # Returning: date, version(human), bootloader version, full date version
            # BUT: be ready for additions!
            rv = list(get_mpy_version())
            rv.insert(2, get_bl_version()[0])
            rv.append(hw_label)

            return b'asci' + ('\n'.join(rv)).encode()

        if cmd == 'sha2':
            return b'biny' + self.file_checksum.digest()

        if cmd == 'xpub':
            assert self.encrypted_req, 'must encrypt'
            return self.handle_xpub(args)

        if cmd == 'mitm':
            assert self.encrypted_req, 'must encrypt'
            return await self.handle_mitm_check()

        if cmd == 'smsg':
            # sign message
            addr_fmt, len_subpath, len_msg = unpack_from('<III', args)
            subpath = args[12:12 + len_subpath]
            msg = args[12 + len_subpath:]
            assert len(msg) == len_msg, "badlen"

            from auth import sign_msg
            sign_msg(msg, subpath, addr_fmt)
            return None

        if cmd == 'p2sh':
            # show P2SH (probably multisig) address on screen (also provides it back)
            # - must provide redeem script, and list of [xfp+path]
            from auth import start_show_p2sh_address

            if hsm_active and not hsm_active.approve_address_share(
                    is_p2sh=True):
                raise HSMDenied

            # new multsig goodness, needs mapping from xfp->path and M values
            addr_fmt, M, N, script_len = unpack_from('<IBBH', args)

            assert addr_fmt & AFC_SCRIPT
            assert 1 <= M <= N <= 20
            assert 30 <= script_len <= 520

            offset = 8
            witdeem_script = args[offset:offset + script_len]
            offset += script_len

            assert len(witdeem_script) == script_len

            xfp_paths = []
            for i in range(N):
                ln = args[offset]
                assert 1 <= ln <= 16, 'badlen'
                xfp_paths.append(unpack_from('<%dI' % ln, args, offset + 1))
                offset += (ln * 4) + 1

            assert offset == len(args)

            return b'asci' + start_show_p2sh_address(M, N, addr_fmt, xfp_paths,
                                                     witdeem_script)

        if cmd == 'show':
            # simple cases, older code: text subpath
            from auth import start_show_address

            addr_fmt, = unpack_from('<I', args)
            assert not (addr_fmt & AFC_SCRIPT)

            return b'asci' + start_show_address(addr_fmt, subpath=args[4:])

        if cmd == 'enrl':
            # Enroll new xpubkey to be involved in multisigs.
            # - text config file must already be uploaded

            file_len, file_sha = unpack_from('<I32s', args)
            if file_sha != self.file_checksum.digest():
                return b'err_Checksum'
            assert 100 < file_len <= (20 * 200), "badlen"

            # Start an UX interaction, return immediately here
            from auth import maybe_enroll_xpub
            maybe_enroll_xpub(sf_len=file_len, ux_reset=True)

            return None

        if cmd == 'msck':
            # Quick check to test if we have a wallet already installed.
            from multisig import MultisigWallet
            M, N, xfp_xor = unpack_from('<3I', args)

            return int(MultisigWallet.quick_check(M, N, xfp_xor))

        if cmd == 'stxn':
            # sign transaction
            txn_len, flags, txn_sha = unpack_from('<II32s', args)
            if txn_sha != self.file_checksum.digest():
                return b'err_Checksum'

            assert 50 < txn_len <= MAX_TXN_LEN, "bad txn len"

            from auth import sign_transaction
            sign_transaction(txn_len, (flags & STXN_FLAGS_MASK), txn_sha)
            return None

        if cmd == 'stok' or cmd == 'bkok' or cmd == 'smok' or cmd == 'pwok':
            # Have we finished (whatever) the transaction,
            # which needed user approval? If so, provide result.
            from auth import UserAuthorizedAction

            req = UserAuthorizedAction.active_request
            if not req:
                return b'err_No active request'

            if req.refused:
                UserAuthorizedAction.cleanup()
                return b'refu'

            if req.failed:
                rv = b'err_' + req.failed.encode()
                UserAuthorizedAction.cleanup()
                return rv

            if not req.result:
                # STILL waiting on user
                return None

            if cmd == 'pwok':
                # return new root xpub
                xpub = req.result
                UserAuthorizedAction.cleanup()
                return b'asci' + bytes(xpub, 'ascii')
            elif cmd == 'smok':
                # signed message done: just give them the signature
                addr, sig = req.address, req.result
                UserAuthorizedAction.cleanup()
                return pack('<4sI', 'smrx', len(addr)) + addr.encode() + sig
            else:
                # generic file response
                resp_len, sha = req.result
                UserAuthorizedAction.cleanup()
                return pack('<4sI32s', 'strx', resp_len, sha)

        if cmd == 'pass':
            # bip39 passphrase provided, maybe use it if authorized
            assert self.encrypted_req, 'must encrypt'
            from auth import start_bip39_passphrase
            from main import settings

            assert settings.get('words', True), 'no seed'
            assert len(args) < 400, 'too long'
            pw = str(args, 'utf8')
            assert len(pw) < 100, 'too long'

            return start_bip39_passphrase(pw)

        if cmd == 'back':
            # start backup: asks user, takes long time.
            from auth import start_remote_backup
            return start_remote_backup()

        if cmd == 'blkc':
            # report which blockchain we are configured for
            from chains import current_chain
            chain = current_chain()
            return b'asci' + chain.ctype

        if cmd == 'bagi':
            return self.handle_bag_number(args)

        if has_fatram:
            # HSM and user-related  features only larger-memory Mk3

            if cmd == 'hsms':
                # HSM mode "start" -- requires user approval
                if args:
                    file_len, file_sha = unpack_from('<I32s', args)
                    if file_sha != self.file_checksum.digest():
                        return b'err_Checksum'
                    assert 2 <= file_len <= (200 * 1000), "badlen"
                else:
                    file_len = 0

                # Start an UX interaction but return (mostly) immediately here
                from hsm_ux import start_hsm_approval
                await start_hsm_approval(sf_len=file_len, usb_mode=True)

                return None

            if cmd == 'hsts':
                # can always query HSM mode
                from hsm import hsm_status_report
                import ujson
                return b'asci' + ujson.dumps(hsm_status_report())

            if cmd == 'gslr':
                # get the value held in the Storage Locker
                assert hsm_active, 'need hsm'
                return b'biny' + hsm_active.fetch_storage_locker()

            # User Mgmt
            if cmd == 'nwur':  # new user
                from users import Users
                auth_mode, ul, sl = unpack_from('<BBB', args)
                username = bytes(args[3:3 + ul]).decode('ascii')
                secret = bytes(args[3 + ul:3 + ul + sl])

                return b'asci' + Users.create(username, auth_mode,
                                              secret).encode('ascii')

            if cmd == 'rmur':  # delete user
                from users import Users
                ul, = unpack_from('<B', args)
                username = bytes(args[1:1 + ul]).decode('ascii')

                return Users.delete(username)

            if cmd == 'user':  # auth user (HSM mode)
                from users import Users
                totp_time, ul, tl = unpack_from('<IBB', args)
                username = bytes(args[6:6 + ul]).decode('ascii')
                token = bytes(args[6 + ul:6 + ul + tl])

                if hsm_active:
                    # just queues these details, can't be checked until PSBT on-hand
                    hsm_active.usb_auth_user(username, token, totp_time)
                    return None
                else:
                    # dryrun/testing purposes: validate only, doesn't unlock nothing
                    return b'asci' + Users.auth_okay(username, token,
                                                     totp_time).encode('ascii')

        print("USB garbage: %s +[%d]" % (cmd, len(args)))

        return b'err_Unknown cmd'
Exemple #30
0
if '--seed' in sys.argv:
    # --xfp aabbccdd   => pretend we know that key (won't be able to sign)
    from ustruct import unpack
    from utils import xfp2str
    from seed import set_seed_value
    from main import pa, settings

    words = sys.argv[sys.argv.index('--seed') + 1].split(' ')
    assert len(words) == 24, "Expected 24 space-separated words: add some quotes"
    pa.pin = b'12-12'
    set_seed_value(words)
    settings.set('terms_ok', 1)
    settings.set('_skip_pin', '12-12')
    settings.set('chain', 'XTN')
    print("Seed phrase set, resulting XFP: " + xfp2str(settings.get('xfp')))

if '-g' in sys.argv:
    # do login
    sim_defaults.pop('_skip_pin', 0)

if '--nick' in sys.argv:
    nick = sys.argv[sys.argv.index('--nick') + 1]
    sim_defaults['nick'] = nick
    sim_defaults['terms_ok'] = 1
    sim_defaults.pop('_skip_pin', 0)

if '--delay' in sys.argv:
    delay = int(sys.argv[sys.argv.index('--delay') + 1])
    sim_defaults['lgto'] = delay
Exemple #31
0
    async def done(psbt):
        orig_path, basename = filename.rsplit('/', 1)
        orig_path += '/'
        base = basename.rsplit('.', 1)[0]
        out2_fn = None
        out_fn = None
        txid = None

        from main import settings
        import os
        del_after = settings.get('del', 0)

        while 1:
            # try to put back into same spot, but also do top-of-card
            is_comp = psbt.is_complete()
            if not is_comp:
                # keep the filename under control during multiple passes
                target_fname = base.replace('-part', '')+'-part.psbt'
            else:
                # add -signed to end. We won't offer to sign again.
                target_fname = base+'-signed.psbt'

            for path in [orig_path, None]:
                try:
                    with CardSlot() as card:
                        out_full, out_fn = card.pick_filename(target_fname, path)
                        out_path = path
                        if out_full: break
                except CardMissingError:
                    prob = 'Missing card.\n\n'
                    out_fn = None

            if not out_fn: 
                # need them to insert a card
                prob = ''
            else:
                # attempt write-out
                try:
                    with CardSlot() as card:
                        if is_comp and del_after:
                            # don't write signed PSBT if we'd just delete it anyway
                            out_fn = None
                        else:
                            with output_encoder(open(out_full, 'wb')) as fd:
                                # save as updated PSBT
                                psbt.serialize(fd)

                        if is_comp:
                            # write out as hex too, if it's final
                            out2_full, out2_fn = card.pick_filename(
                                base+'-final.txn' if not del_after else 'tmp.txn', out_path)

                            if out2_full:
                                with HexWriter(open(out2_full, 'w+t')) as fd:
                                    # save transaction, in hex
                                    txid = psbt.finalize(fd)

                                if del_after:
                                    # rename it now that we know the txid
                                    after_full, out2_fn = card.pick_filename(
                                                            txid+'.txn', out_path, overwrite=True)
                                    os.rename(out2_full, after_full)

                    if del_after:
                        # this can do nothing if they swapped SDCard between steps, which is ok,
                        # but if the original file is still there, this blows it away.
                        # - if not yet final, the foo-part.psbt file stays
                        try:
                            securely_blank_file(filename)
                        except: pass

                    # success and done!
                    break

                except OSError as exc:
                    prob = 'Failed to write!\n\n%s\n\n' % exc
                    sys.print_exception(exc)
                    # fall thru to try again

            # prompt them to input another card?
            ch = await ux_show_story(prob+"Please insert an SDCard to receive signed transaction, "
                                        "and press OK.", title="Need Card")
            if ch == 'x':
                await ux_aborted()
                return

        # done.
        if out_fn:
            msg = "Updated PSBT is:\n\n%s" % out_fn
            if out2_fn:
                msg += '\n\n'
        else:
            # del_after is probably set
            msg = ''

        if out2_fn:
            msg += 'Finalized transaction (ready for broadcast):\n\n%s' % out2_fn
            if txid and not del_after:
                msg += '\n\nFinal TXID:\n'+txid

        await ux_show_story(msg, title='PSBT Signed')

        UserAuthorizedAction.cleanup()
Exemple #32
0
DebugFunctionsMenu = [
    #         xxxxxxxxxxxxxxxx
    MenuItem('Debug: assert', f=debug_assert),
    MenuItem('Debug: except', f=debug_except),
    MenuItem('Check: BL FW', f=check_firewall_read),
    MenuItem('Warm Reset', f=reset_self),
    #MenuItem("Perform Selftest", f=start_selftest),
]

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("Destroy Seed", f=clear_seed),
    MenuItem("I Am Developer.", menu=maybe_dev_menu),
    MenuItem("Wipe Patch Area", f=wipe_filesystem),  # needs better label
    MenuItem('Perform Selftest', f=start_selftest),  # little harmful
    MenuItem("Set High-Water", f=set_highwater),
]

BackupStuffMenu = [
    #         xxxxxxxxxxxxxxxx
    MenuItem("Backup System", f=backup_everything),
    MenuItem("Verify Backup", f=verify_backup),
    MenuItem("Restore Backup", f=restore_everything),  # just a redirect really
    MenuItem("Dump Summary", f=dump_summary),
]
Exemple #33
0
async def ready2sign(*a):
    # Top menu choice of top menu! Signing!
    # - check if any signable in SD card, if so do it
    # - if nothing, then talk about USB connection
    from public_constants import MAX_TXN_LEN
    import stash

    if stash.bip39_passphrase:
        title = '[%s]' % settings.get('xfp')
    else:
        title = None

    def is_psbt(filename):
        if '-signed' in filename.lower():
            return False

        with open(filename, 'rb') as fd:
            taste = fd.read(10)
            if taste[0:5] == b'psbt\xff':
                return True
            if taste[0:10] == b'70736274ff':  # hex-encoded
                return True
            if taste[0:6] == b'cHNidP':  # base64-encoded
                return True
            return False

    # just check if we have candidates, no UI
    choices = await file_picker(None,
                                suffix='psbt',
                                min_size=50,
                                max_size=MAX_TXN_LEN,
                                taster=is_psbt)

    if not choices:
        await ux_show_story("""\
Coldcard is ready to sign spending transactions!

Put the proposed transaction onto MicroSD card \
in PSBT format (Partially Signed Bitcoin Transaction) \
or upload a transaction to be signed \
from your wallet software (Electrum) or command line tools. \

You will always be prompted to confirm the details before any signature is performed.\
""",
                            title=title)
        return

    if len(choices) == 1:
        # skip the menu
        label, path, fn = choices[0]
        input_psbt = path + '/' + fn
    else:
        input_psbt = await file_picker('Choose PSBT file to be signed.',
                                       choices=choices,
                                       title=title)
        if not input_psbt:
            return

    # start the process
    from auth import sign_psbt_file

    await sign_psbt_file(input_psbt)