Ejemplo n.º 1
0
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
from main import dis
from ubinascii import hexlify as b2a_hex

RV.write(b2a_hex(dis.dis.buffer))

Ejemplo n.º 2
0
def gate(method, buf_io, arg2):
    # the "callgate" into the bootloader

    # - only spuratically implemented (say it like in _Clueless_)
    # - none of the error checking is repeated here
    # - not trying too hard to fake the hardware-related features

    if method == 0:
        # version string
        hc = b'2.0.0 time=20180220.092345 git=master@f8d1758'
        buf_io[0:len(hc)] = hc
        return len(hc)

    if method == 16:
        if len(buf_io) < 8: return ERANGE
        return pin_prefix(buf_io[0:arg2], buf_io)

    if method == 18:
        from sim_secel import pin_stuff
        return pin_stuff(arg2, buf_io)

    if method == 4:
        # control the green/red light
        global genuine_led
        if arg2 == 1:
            # clear it
            genuine_led = False
            led_pipe.write(bytes([0x10]))
        return 1 if genuine_led else 0

    if method == 5:
        # are we a brick? No.
        return 0

    if method == 6:
        # do we have 608?
        return ENOENT if not version.has_608 else 0

    if method == 19:
        # bag number
        if arg2 == 0:
            buf_io[0:32] = b'CSIM0000' + b'\0' * (32 - 8)
        if arg2 == 1:
            # not supported: write
            return buf_io

    if method == 21:
        # high water mark
        if arg2 == 0:
            #buf_io[0:8] = b'\x18\x07\x11\x19S\x08\x00\x00'
            #buf_io[0:8] = b'!\x04)\x21\'"\x00\x00'
            buf_io[0:8] = b'!\x03)\x19\'"\x00\x00'
            #buf_io[0:8] = bytes(8)
        elif arg2 == 2:
            print("New highwater: %s" % b2a_hex(buf_io[0:8]))
        return 0

    if method == 20:
        # read 608 config bytes (128)
        assert len(buf_io) == 128
        buf_io[:] = b'\x01#\xbf\x0b\x00\x00`\x03CP,\xbf\xeeap\x00\xe1\x00a\x00\x00\x00\x8f-\x8f\x80\x8fC\xaf\x80\x00C\x00C\x8fG\xc3C\xc3C\xc7G\x00G\x00\x00\x8fM\x8fC\x00\x00\x00\x00\x1f\xff\x00\x1a\x00\x1a\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\xff\x02\x15\x00\x00\x00\x00<\x00\\\x00\xbc\x01\xfc\x01\xbc\x01\x9c\x01\x9c\x01\xfc\x01\xdc\x03\xdc\x03\xdc\x07\x9c\x01<\x00\xfc\x01\xdc\x01<\x00'
        return 0

    return ENOENT
Ejemplo n.º 3
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)
Ejemplo n.º 4
0
async def clone_start(*a):
    # Begins cloning process, on target device.
    from files import CardSlot, CardMissingError

    ch = await ux_show_story(
        '''Insert a MicroSD card and press OK to start. A small \
file with an ephemeral public key will be written.''')
    if ch != 'y': return

    # pick a random key pair, just for this cloning session
    pair = ngu.secp256k1.keypair()
    my_pubkey = pair.pubkey().to_bytes(False)

    # write to SD Card, fixed filename for ease of use
    try:
        with CardSlot() as card:
            fname, nice = card.pick_filename('ccbk-start.json', overwrite=True)

            with open(fname, 'wb') as fd:
                fd.write(ujson.dumps(dict(pubkey=b2a_hex(my_pubkey))))

    except CardMissingError:
        await needs_microsd()
        return
    except Exception as e:
        await ux_show_story('Error: ' + str(e))
        return

    # Wait for incoming clone file, allow retries
    ch = await ux_show_story(
        '''Keep power on this Coldcard, and take MicroSD card \
to source Coldcard. Select Advanced > MicroSD > Clone Coldcard to write to card. Bring that card \
back and press OK to complete clone process.''')

    while 1:
        if ch != 'y':
            # try to clean up, but card probably not there? No errors.
            try:
                with CardSlot() as card:
                    uos.remove(fname)
            except:
                pass

            await ux_dramatic_pause('Aborted.', 2)
            return

        # Hopefully we have a suitable 7z file now. Pubkey in the filename
        incoming = None
        try:
            with CardSlot() as card:
                for path in card.get_paths():
                    for fn, ftype, *var in uos.ilistdir(path):
                        if fn.endswith('-ccbk.7z'):
                            incoming = path + '/' + fn
                            his_pubkey = a2b_hex(fn[0:66])

                            assert len(his_pubkey) == 33
                            assert 2 <= his_pubkey[0] <= 3
                            break

        except CardMissingError:
            await needs_microsd()
            continue
        except Exception as e:
            pass

        if incoming:
            break

        ch = await ux_show_story(
            "Clone file not found. OK to try again, X to stop.")

    # calculate point
    session_key = pair.ecdh_multiply(his_pubkey)

    # "password" is that hex value
    words = [b2a_hex(session_key).decode()]

    def delme(xfn):
        # Callback to delete file after its read; could still fail but
        # need to start over in that case anyway.
        uos.remove(xfn)
        uos.remove(fname)  # ccbk-start.json

    # this will reset in successful case, no return (but delme is called)
    prob = await restore_complete_doit(incoming, words, file_cleanup=delme)

    if prob:
        await ux_show_story(prob, title='FAILED')
Ejemplo n.º 5
0
def bytes_to_hex_str(s):
    return str(b2a_hex(s), 'ascii')
Ejemplo n.º 6
0
    ('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx',
     '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6',
     1),
    ('BC1SW50QA3JX3S', '6002751e', 16),
    ('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj',
     '5210751e76e8199196d454941c45d1b3a323', 2),
    ('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy',
     '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433',
     0),
]

for addr, expect, v in decode:
    hrp, version, data = bech32_decode(addr)
    assert version == v, (addr, version, v)
    assert hrp == addr.lower()[0:2]
    assert expect.endswith(b2a_hex(data)), (expect, data)
    #print("%s ok" % addr)

# some bad checksums

chk = [
    'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5',
    'li1dgmt3',
    'de1lg7wt\xff',
    'A1G7SGD8',
]

for v in chk:
    try:
        hrp, version, data = bech32_decode(v)
        assert False, ("%s => %s,%d,%r" % (v, hrp, version, data))
Ejemplo n.º 7
0
    def write(self, b):
        self.checksum.update(b)
        self.pos += len(b)

        self.fd.write(b2a_hex(b))
Ejemplo n.º 8
0
def go():

    # see bootloader/data/ae_layout.h

    c1 = bytes([
        0xe1, 0x00, 0x55, 0x00, 0x00, 0x00, 0x8f, 0x2d, 0x8f, 0x80,   \
        0x8f, 0x43, 0xc3, 0x43, 0x00, 0x43, 0x8f, 0x46, 0xc6, 0x46,   \
        0x00, 0x46, 0x8f, 0x49, 0xc9, 0x49, 0x8f, 0x4b, 0xcb, 0x4b,   \
        0x8f, 0x4d, 0xc1, 0x41, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,   \
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,   \
        0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,   \
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff   \
    ])

    c2 = bytes([
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x5c, 0x00,   \
        0xbc, 0x01, 0xfc, 0x01, 0xdc, 0x03, 0x9c, 0x01, 0xfc, 0x01,   \
        0xdc, 0x06, 0x9c, 0x01, 0xfc, 0x01, 0xdc, 0x09, 0xfc, 0x01,   \
        0xdc, 0x0b, 0xfc, 0x01, 0xdc, 0x01, 0x3c, 0x00   \
    ])


    if not ae.is_config_locked():
        ae.data[16:84] = c1
        ae.data[90:128] = c2
        assert len(ae.data) == 128
        b4 = bytes(ae.data)
        ae.write()
        ae.read()
        assert b4 == ae.data
        print("locking configzone")
        ae.LOCK(is_config=1)
        ae.reset_watchdog()
        ae.read()

    # assume all slots are blank
    ae.assume_data_blank()

    if not ae.is_slot_locked(KN_pairing):
        assert len(pairing_key) == 32
        ae.write_data_slot(KN_pairing, pairing_key)
    
        # cannot lock this slot, or else we can't burn it via DeriveKey
        #ae.LOCK(data=pairing_key+FF4, slot_num=KN_pairing, datazone=True)
    else:
        ae.d_slot[KN_pairing] = pairing_key+FF4

    # check pairing key works
    ae.reset_watchdog()
    ae.do_checkmac(KN_pairing, pairing_key)
    print("checkmac pairing works")

    if not ae.is_slot_locked(KN_words):
        assert len(words_key) == 32
        ae.write_data_slot(KN_words, words_key)
        ae.do_checkmac(KN_words, words_key)      # check write

        # always lock it
        ae.LOCK(data=words_key+b'\xff\xff\xff\xff', slot_num=KN_words, datazone=True)
    else:
        ae.d_slot[KN_words] = words_key+FF4

    # need both keys to be able to do this!
    ae.reset_watchdog()
    ae.do_checkmac(KN_pairing, pairing_key)
    ae.do_checkmac(KN_words, words_key)         # will fail if we didn't just auth w/ pairing key
    print("checkmac words works")

    ae.reset_watchdog()
    ae.do_checkmac(KN_pairing, pairing_key)
    hm = ae.hmac(KN_words, b'0'*32, diverse=False)      # production: will be diverse mode
    assert hm == a2b_hex('aadec702b4855df0c1838a48978d56a65e5871f291e835d0f833aa2fdfd30290'), b2a_hex(hm)
    print("HMAC(words, '0'*32) works")

    # Set PIN's to something known.
    # simple non-encrypted write can work while data unlocked
    if not ae.is_data_locked():
        for idx in range(4):
            ae.reset_watchdog()
            ae.write_data_slot(KN_pins[idx], BLANK)
            ae.write_data_slot(KN_secrets[idx], BLANK)
            if idx < len(KN_lastgood):
                ae.write_data_slot(KN_lastgood[idx], BLANK)

        ae.reset_watchdog()
        ae.write_data_slot(KN_brickme, BLANK)
        ae.write_data_slot(KN_firmware, BLANK)

        print("Locking data zone")
        ae.LOCK(datazone=True, ecc_slots=[], no_crc=1)
        ae.reset_watchdog()
        ae.read()

    print("Success")
Ejemplo n.º 9
0
def pin_stuff(submethod, buf_io):
    from pincodes import (PIN_ATTEMPT_SIZE, PIN_ATTEMPT_FMT_V1, PA_ZERO_SECRET,
                        PIN_ATTEMPT_SIZE_V1, CHANGE_LS_OFFSET,
                        PA_SUCCESSFUL, PA_IS_BLANK, PA_HAS_DURESS, PA_HAS_BRICKME,
                        CHANGE_WALLET_PIN, CHANGE_DURESS_PIN, CHANGE_BRICKME_PIN,
                        CHANGE_SECRET, CHANGE_DURESS_SECRET, CHANGE_SECONDARY_WALLET_PIN )

    if len(buf_io) != (PIN_ATTEMPT_SIZE if version.has_608 else PIN_ATTEMPT_SIZE_V1):
        return ERANGE

    global SECRETS

    (magic, is_secondary,
            pin, pin_len,
            delay_achieved,
            delay_required,
            num_fails,
            attempts_left,
            state_flags,
            private_state,
            hmac,
            change_flags,
            old_pin, old_pin_len,
            new_pin, new_pin_len,
            secret) = ustruct.unpack_from(PIN_ATTEMPT_FMT_V1, buf_io)

    # NOTE: ignoring mk2 additions for now, we have no need for it.

    # NOTE: using strings here, not bytes; real bootrom uses bytes
    pin = pin[0:pin_len].decode()
    old_pin = old_pin[0:old_pin_len].decode()
    new_pin = new_pin[0:new_pin_len].decode()

    kk = '_pin1' if not is_secondary else '_pin2'

    if submethod == 0:
        # setup
        state_flags = (PA_SUCCESSFUL | PA_IS_BLANK) if not SECRETS.get(kk, False) else 0

        if pin.startswith('77'):
            delay_required = int(pin.split('-')[1]) * 2
            num_fails = 3
        if pin.startswith('88'):
            attempts_left = 2
            num_fails = 11

        if version.has_608:
            attempts_left = 13
            num_fails = 0
            if 0:       # XXX  test
                num_fails = 10
                attempts_left = 3

    elif submethod == 1:
        # delay
        time.sleep(0.500)
        delay_achieved += 1

    elif submethod == 2:
        # Login

        expect = SECRETS.get(kk, '')
        if pin == expect:
            state_flags = PA_SUCCESSFUL

            ts = a2b_hex(SECRETS.get(kk+'_secret', '00'*72))

        elif pin == SECRETS.get(kk + '_duress', None):
            state_flags = PA_SUCCESSFUL

            ts = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72))

        else:
            if version.has_608:
                num_fails += 1
                attempts_left -= 1
            else:
                state_flags = 0

            return EPIN_AUTH_FAIL

        time.sleep(0.5)

        if ts == b'\0'*72:
            state_flags |= PA_ZERO_SECRET

        if kk+'_duress' in SECRETS:
            state_flags |= PA_HAS_DURESS
        if kk+'_brickme' in SECRETS:
            state_flags |= PA_HAS_BRICKME

        del ts

    elif submethod == 3:
        # CHANGE pin and/or wallet secrets

        cf = change_flags

        # NOTE: this logic copied from real deal

        # must be here to do something.
        if cf == 0: return EPIN_RANGE_ERR;

        if cf & CHANGE_BRICKME_PIN:
            if is_secondary:
                # only main PIN holder can define brickme PIN
                return EPIN_PRIMARY_ONLY
            if cf != CHANGE_BRICKME_PIN:
                # only pin can be changed, nothing else.
                return EPIN_BAD_REQUEST

        if (cf & CHANGE_DURESS_SECRET) and (cf & CHANGE_SECRET):
            # can't change two secrets at once.
            return EPIN_BAD_REQUEST

        if (cf & CHANGE_SECONDARY_WALLET_PIN):
            if is_secondary:
                # only main user uses this call 
                return EPIN_BAD_REQUEST;

            if (cf != CHANGE_SECONDARY_WALLET_PIN):
                # only changing PIN, no secret-setting
                return EPIN_BAD_REQUEST;

            kk = '_pin2'

        
        # what PIN will we change
        pk = None
        if change_flags & (CHANGE_WALLET_PIN | CHANGE_SECONDARY_WALLET_PIN):
            pk = kk
        if change_flags & CHANGE_DURESS_PIN:
            pk = kk+'_duress'
        if change_flags & CHANGE_BRICKME_PIN:
            pk = kk+'_brickme'

        if pin == SECRETS.get(kk + '_duress', None):
            # acting duress mode... let them only change duress pin
            if pk == kk:
                pk = kk+'_duress'
            else:
                return EPIN_OLD_AUTH_FAIL

        if pk != None:
            # Must match old pin correctly, if it is defined.
            if SECRETS.get(pk, '') != old_pin:
                print("secel: wrong OLD pin (expect %s, got %s)" % (SECRETS[pk], old_pin))
                return EPIN_OLD_AUTH_FAIL

            # make change
            SECRETS[pk] = new_pin

            if not new_pin and pk != kk:
                del SECRETS[pk]

        if change_flags & CHANGE_SECRET:
            SECRETS[kk+'_secret'] = str(b2a_hex(secret), 'ascii')
        if change_flags & CHANGE_DURESS_SECRET:
            SECRETS[kk+'_duress_secret'] = str(b2a_hex(secret), 'ascii')

        time.sleep(1.5)

    elif submethod == 4:
        # Fetch secrets
        duress_pin = SECRETS.get(kk+'_duress')

        if pin == duress_pin:
            secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72))
        else:
            # main/sec secrets
            expect = SECRETS.get(kk, '')
            if pin == expect:
                secret = a2b_hex(SECRETS.get(kk+'_secret', '00'*72))
            else:
                return EPIN_AUTH_FAIL

    elif submethod == 5:
        # greenlight firmware
        from ckcc import genuine_led, led_pipe
        genuine_led = True
        led_pipe.write(b'\x01')

    elif submethod == 6:
        if not version.has_608:
            return ENOENT

        # long secret read/change.
        cf = change_flags
        assert CHANGE_LS_OFFSET == 0xf00
        blk = (cf >> 8) & 0xf
        if blk > 13: return EPIN_RANGE_ERR
        off = blk * 32

        if 'ls' not in SECRETS:
            SECRETS['ls'] = bytearray(416)

        if (cf & CHANGE_SECRET):
            SECRETS['ls'][off:off+32] = secret
        else:
            secret = SECRETS['ls'][off:off+32]

    else:
        # bogus submethod
        return ENOENT


    hmac = b'69'*16

    ustruct.pack_into(PIN_ATTEMPT_FMT_V1, buf_io, 0, magic, is_secondary,
            pin.encode(), pin_len, delay_achieved, delay_required,
            num_fails, attempts_left,
            state_flags, private_state, hmac,
            change_flags, old_pin.encode(), old_pin_len, new_pin.encode(), new_pin_len, secret)

    return 0
Ejemplo n.º 10
0
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# utils.py - Misc utils. My favourite kind of source file.
#
import gc, sys, ustruct, ngu
from ubinascii import unhexlify as a2b_hex
from ubinascii import hexlify as b2a_hex
from ubinascii import a2b_base64, b2a_base64
from uhashlib import sha256

B2A = lambda x: str(b2a_hex(x), 'ascii')

class imported:
    # Context manager that temporarily imports
    # a list of modules.
    # LATER: doubtful this saves any memory when all the code is frozen.

    def __init__(self, *modules):
        self.modules = modules

    def __enter__(self):
        # import everything required
        rv = tuple(__import__(n) for n in self.modules)

        return rv[0] if len(self.modules) == 1 else rv

    def __exit__(self, exc_type, exc_value, traceback):

        for n in self.modules:
            if n in sys.modules:
                del sys.modules[n]
Ejemplo n.º 11
0
try:
    import ngu
    from ubinascii import hexlify as b2a_hex
    sha512 = ngu.hash.sha512
    hmac_sha1 = ngu.hmac.hmac_sha1
    hmac_sha256 = ngu.hmac.hmac_sha256
    hmac_sha512 = ngu.hmac.hmac_sha512
except ImportError:
    # desktop
    from binascii import b2a_hex
    from hashlib import sha256, sha1, sha512
    from hmac import HMAC

    hmac_sha1 = lambda key, msg=None: HMAC(key, msg, sha1).digest()
    hmac_sha256 = lambda key, msg=None: HMAC(key, msg, sha256).digest()
    hmac_sha512 = lambda key, msg=None: HMAC(key, msg, sha512).digest()

assert b2a_hex(hmac_sha1(
    b'ab' * 16, b'hello')) == b'6ffb8c2e3e85677f02913c480a44486018db0552'
assert b2a_hex(
    hmac_sha256(b'ab' * 16, b'hello')
) == b'254ac1adfb14b1de23b5bfc5d1a7d2a0ca0ebcdc2676fed0b81b121912ce6a67'
assert b2a_hex(
    hmac_sha512(b'ab' * 16, b'hello')
) == b'435c32af24b24c1c47257e5f055dccb743f732b0723a4ff014cdf0c9a8500b087e01a85033f0a109c5841c1362e783e0776e8e45f9c09c070025fa35fe9d8160'

print("PASS - test_hmac")
Ejemplo n.º 12
0
    def determine_my_signing_key(self, my_idx, utxo):
        # See what it takes to sign this particular input
        # - type of script
        # - which pubkey needed
        # - scriptSig value
        addr_type, addr_or_pubkey, addr_is_segwit = utxo.get_address()

        which_key = None
        self.is_multisig = False
        self.is_p2sh = False
        self.amount = utxo.nValue

        if addr_is_segwit and not self.is_segwit:
            self.is_segwit = True

        if addr_type == 'p2sh':
            # multisig input
            self.is_p2sh = True

            # we must have the redeem script already (else fail)
            if not self.redeem_script:
                raise AssertionError("missing redeem script for in #%d" %
                                     my_idx)

            redeem_script = self.get(self.redeem_script)
            self.scriptSig = ser_string(redeem_script)

            # new cheat: psbt creator probably telling us exactly what key
            # to use, by providing exactly one. This is ideal for p2sh wrapped p2pkh
            if len(self.subpaths) == 1:
                which_key, = self.subpaths.keys()
            else:
                # messy P2SH multisig guessing?
                ws = self.get(self.witness_script
                              ) if self.witness_script else redeem_script
                for pubkey in self.subpaths:
                    if pubkey in ws:
                        # limitations:
                        # - we could be holding multiple legs of the P2SH
                        # - text match like this could be fooled w/ crafting
                        which_key = pubkey
                        break

            if not addr_is_segwit and \
                    len(redeem_script) == 22 and \
                    redeem_script[0] == 0 and redeem_script[1] == 20:
                # it's actually segwit p2pkh inside p2sh
                addr_type = 'p2wpkh-p2sh'
                addr = redeem_script[2:22]
                self.is_segwit = True
            else:
                # multiple keys involved, we probably can't do the finalize step
                self.is_multisig = True

        elif addr_type == 'p2pkh':
            # input is hash160 of a single public key
            self.scriptSig = utxo.scriptPubKey
            addr = addr_or_pubkey

            for pubkey in self.subpaths:
                if hash160(pubkey) == addr:
                    which_key = pubkey
                    break

        elif addr_type == 'p2pk':
            # input is single public key (less common)
            self.scriptSig = utxo.scriptPubKey
            assert len(addr_or_pubkey) == 33

            if addr_or_pubkey in self.subpaths:
                which_key = addr_or_pubkey

        else:
            # we don't know how to "solve" this type of input
            pass

        if not which_key:
            print(
                "no key: input #%d: type=%s segwit=%d a_or_pk=%s scriptPubKey=%s"
                % (my_idx, addr_type, self.is_segwit, b2a_hex(addr_or_pubkey),
                   b2a_hex(utxo.scriptPubKey)))

        self.required_key = which_key

        if self.is_segwit:
            if ('pkh' in addr_type):
                # This comment from <https://bitcoincore.org/en/segwit_wallet_dev/>:
                #
                #   Please note that for a P2SH-P2WPKH, the scriptCode is always 26
                #   bytes including the leading size byte, as 0x1976a914{20-byte keyhash}88ac,
                #   NOT the redeemScript nor scriptPubKey
                #
                # Also need this scriptCode for native segwit p2pkh
                #
                assert not self.is_multisig
                self.scriptCode = b'\x19\x76\xa9\x14' + addr + b'\x88\xac'
            elif not self.scriptCode:
                # Segwit P2SH segwit. We need the script!
                if not self.witness_script:
                    raise AssertionError('Need witness script for input #%d' %
                                         my_idx)

                self.scriptCode = self.get(self.witness_script)
Ejemplo n.º 13
0
 def update(self, d):
     print(b2a_hex(d), end=' ')
     self.rv.update(d)
Ejemplo n.º 14
0
    async def approve_transaction(self, psbt, psbt_sha, story):
        # Approve or don't a transaction. Catch assertions and other
        # reasons for failing/rejecting into the log.
        # - return 'y' or 'x'
        chain = chains.current_chain()
        assert psbt_sha and len(psbt_sha) == 32
        self.get_time_left()

        with AuditLogger('psbt', psbt_sha, self.never_log) as log:

            if self.must_log and log.is_unsaved:
                self.refuse(log, "Could not log details, and must_log is set")
                return 'x'

            log.info('Transaction signing requested:')
            log.info('SHA256(PSBT) = ' + b2a_hex(psbt_sha).decode('ascii'))
            log.info('-vvv-\n%s\n-^^^-' % story)

            # reset pending auth list and "consume" it now
            auth = self.pending_auth
            self.pending_auth = {}

            try:
                # do this super early so always cleared even if other issues
                local_ok = self.consume_local_code(psbt_sha)

                if not self.rules:
                    raise ValueError("no txn signing allowed")

                # reject anything with warning, probably
                if psbt.warnings:
                    if self.warnings_ok:
                        log.info(
                            "Txn has warnings, but policy is to accept anyway."
                        )
                    else:
                        raise ValueError("has %d warning(s)" %
                                         len(psbt.warnings))

                # See who has entered creditials already (all must be valid).
                users = []
                for u, (token, counter) in auth.items():
                    problem = Users.auth_okay(u,
                                              token,
                                              totp_time=counter,
                                              psbt_hash=psbt_sha)
                    if problem:
                        self.refuse(
                            log, "User '%s' gave wrong auth value: %s" %
                            (u, problem))
                        return 'x'
                    users.append(u)

                # was right code provided locally? (also resets for next attempt)
                if local_ok:
                    log.info("Local operator gave correct code.")
                if users:
                    log.info("These users gave correct auth codes: " +
                             ', '.join(users))

                # Where is it going?
                total_out = 0
                dests = []
                for idx, tx_out in psbt.output_iter():
                    if not psbt.outputs[idx].is_change:
                        total_out += tx_out.nValue
                        dests.append(chain.render_address(tx_out.scriptPubKey))

                # Pick a rule to apply to this specific txn
                reasons = []
                for rule in self.rules:
                    try:
                        if rule.matches_transaction(psbt, users, total_out,
                                                    dests, local_ok):
                            break
                    except BaseException as exc:
                        # let's not share these details, except for debug; since
                        # they are not errors, just picking best rule in priority order
                        r = "rule #%d: %s" % (rule.index, str(exc)
                                              or problem_file_line(exc))
                        reasons.append(r)
                        print(r)
                else:
                    err = "Rejected: " + ', '.join(reasons)
                    self.refuse(log, err)
                    return 'x'

                if users:
                    msg = ', '.join(auth.keys())
                    if local_ok:
                        msg += ', and the local operator.' if msg else 'local operator'

                # looks good, do it
                self.approve(log, "Acceptable by rule #%d" % rule.index)

                if rule.per_period is not None:
                    self.record_spend(rule, total_out)

                return 'y'
            except BaseException as exc:
                sys.print_exception(exc)
                err = "Rejected: " + (str(exc) or problem_file_line(exc))
                self.refuse(log, err)

                return 'x'
Ejemplo n.º 15
0
def xfp2str(xfp):
    # Standardized way to show an xpub's fingerprint... it's a 4-byte string
    # and not really an integer. Used to show as '0x%08x' but that's wrong endian.
    return b2a_hex(ustruct.pack('<I', xfp)).decode().upper()
Ejemplo n.º 16
0
 def hexdump(label, x):
     print(label + ASC(b2a_hex(x)) + ('  len=%d' % len(x)))
Ejemplo n.º 17
0
def secel_dump(blk, rnd=None, which_nums=range(16)):
    ASC = lambda b: str(b, 'ascii')

    def hexdump(label, x):
        print(label + ASC(b2a_hex(x)) + ('  len=%d' % len(x)))

    #hexdump('SN: ', blk[0:4]+blk[8:13])
    hexdump('RevNum: ', blk[4:8])

    # guessing this nibble in RevNum corresponds to chip 508a vs 608a
    print("Chip type: atecc%x08a" % ((blk[6] >> 4) & 0xf))
    partno = ((blk[6] >> 4) & 0xf)
    assert partno in [5, 6]

    if partno == 6:
        print('AES_Enable = 0x%x' % (blk[13] & 0x1))

    print('I2C_Enable = 0x%x' % (blk[14] & 0x1))
    if blk[14] & 0x01 == 0x01:
        print('I2C_Address = 0x%x' % (blk[16] >> 1))
    else:
        print('GPIO Mode = 0x%x' % (blk[16] & 0x3))
        print('GPIO Default = 0x%x' % ((blk[16] >> 2) & 0x1))
        print('GPIO Detect (vs authout) = 0x%x' % ((blk[16] >> 3) & 0x1))
        print('GPIO SignalKey/KeyId = 0x%x' % ((blk[16] >> 4) & 0xf))
        print('I2C_Address(sic) = 0x%x' % blk[16])

    if partno == 5:
        print('OTPmode = 0x%x' % blk[18])
    if partno == 6:
        print('CountMatchKey = 0x%x' % ((blk[18] >> 4) & 0xf))
        print('CounterMatch enable = %d' % (blk[18] & 0x1))

    print('ChipMode = 0x%x' % blk[19])

    print()

    for i in which_nums:
        slot_conf = blk[20 + (2 * i):22 + (2 * i)]
        conf = SlotConfig.unpack(slot_conf)
        print('     Slot[%d] = 0x%s = %r' % (i, ASC(b2a_hex(slot_conf)), conf))

        key_conf = blk[96 + (2 * i):2 + 96 + (2 * i)]

        cls = KeyConfig_508 if partno == 5 else KeyConfig_608

        print('KeyConfig[%d] = 0x%s = %r' %
              (i, ASC(b2a_hex(key_conf)), cls.unpack(key_conf)))

        print()

    hexdump('Counter[0]: ', blk[52:60])
    hexdump('Counter[1]: ', blk[60:68])
    if partno == 5:
        hexdump('LastKeyUse: ', blk[68:84])
    if partno == 6:
        print('UseLock = 0x%x' % blk[68])
        print('VolatileKeyPermission = 0x%x' % blk[69])
        hexdump('SecureBoot: ', blk[70:72])

        print('KldfvLoc = 0x%x' % blk[72])
        hexdump('KdflvStr: ', blk[73:75])

    # 75->83 reserved
    print('UserExtra = 0x%x' % blk[84])
    if partno == 5:
        print('Selector = 0x%x' % blk[85])
    if partno == 6:
        print('UserExtraAdd = 0x%x' % blk[85])

    print('LockValue = 0x%x' % blk[86])
    print('LockConfig = 0x%x' % blk[87])
    hexdump('SlotLocked: ', blk[88:90])

    if partno == 6:
        hexdump('ChipOptions: ', blk[90:92])
        print('ChipOptions = %r' % ChipOptions.unpack(blk[90:92]))

    hexdump('X509format: ', blk[92:96])

    if rnd is not None:
        hexdump('Random: ', rnd)
Ejemplo n.º 18
0
 def __repr__(self):
     return "CTxOut(nValue=%d.%08d scriptPubKey=%s)" \
         % (self.nValue//1E8, self.nValue%1E8, b2a_hex(self.scriptPubKey))
Ejemplo n.º 19
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()

    if s_mode == 'words':
        # BIP-39 seed phrase, various lengths
        words = bip39.b2a_words(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' + ngu.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 = ngu.hdnode.HDNode().from_chaincode_privkey(ch, pk)

        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 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)
Ejemplo n.º 20
0
 def __repr__(self):
     return '<PinAttempt: num_fails={} delay={}/{} attempts_left={} state=0x{} hmac={}>'.format(
         self.num_fails, self.delay_achieved, self.delay_required,
         self.attempts_left, hex(self.state_flags), b2a_hex(self.hmac))
Ejemplo n.º 21
0
    settings.set('chain', 'XTN')
    print("Seed phrase set, resulting XFP: " + xfp2str(settings.get('xfp')))

if '--secret' in sys.argv:
    # --secret 01a1a1a....   Set SE master secret directly. See SecretStash.encode
    from ubinascii import unhexlify as a2b_hex
    from ubinascii import hexlify as b2a_hex

    val = sys.argv[sys.argv.index('--secret') + 1]
    val = a2b_hex(val)
    assert val[0] in {0x01, 0x80, 0x81, 0x82
                      } or 16 <= val[0] <= 64, "bad first byte"
    val += bytes(72 - len(val))

    SECRETS.update({
        '_pin1_secret': b2a_hex(val),
    })

    sim_defaults['terms_ok'] = 1
    sim_defaults['_skip_pin'] = '12-12'
    sim_defaults['chain'] = 'XTN'
    sim_defaults['words'] = bool(val[0] & 0x80)
    sim_defaults.pop('xfp')
    sim_defaults.pop('xpub')

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]
Ejemplo n.º 22
0
def render_backup_contents():
    # simple text format:
    #   key = value
    # or #comments
    # but value is JSON
    from main import settings, pa

    rv = StringIO()

    def COMMENT(val=None):
        if val:
            rv.write('\n# %s\n' % val)
        else:
            rv.write('\n')

    def ADD(key, val):
        rv.write('%s = %s\n' % (key, ujson.dumps(val)))

    rv.write('# Coldcard backup file! DO NOT CHANGE.\n')

    chain = chains.current_chain()

    COMMENT('Private key details: ' + chain.name)

    with stash.SensitiveValues(bypass_pw=True) as sv:

        if sv.mode == 'words':
            ADD('mnemonic', tcc.bip39.from_data(sv.raw))

        if sv.mode == 'master':
            ADD('bip32_master_key', b2a_hex(sv.raw))

        ADD('chain', chain.ctype)
        ADD('xprv', chain.serialize_private(sv.node))
        ADD('xpub', chain.serialize_public(sv.node))

        # BTW: everything is really a duplicate of this value
        ADD('raw_secret', b2a_hex(sv.secret).rstrip(b'0'))

        if pa.has_duress_pin():
            COMMENT('Duress Wallet (informational)')
            dpk = sv.duress_root()
            ADD('duress_xprv', chain.serialize_private(dpk))
            ADD('duress_xpub', chain.serialize_public(dpk))

        if version.has_608:
            # save the so-called long-secret
            ADD('long_secret', b2a_hex(pa.ls_fetch()))

    COMMENT('Firmware version (informational)')
    date, vers, timestamp = version.get_mpy_version()[0:3]
    ADD('fw_date', date)
    ADD('fw_version', vers)
    ADD('fw_timestamp', timestamp)
    ADD('serial', version.serial_number())

    COMMENT('User preferences')

    # user preferences
    for k, v in settings.current.items():
        if k[0] == '_': continue  # debug stuff in simulator
        if k == 'xpub': continue  # redundant, and wrong if bip39pw
        if k == 'xfp': continue  # redundant, and wrong if bip39pw
        ADD('setting.' + k, v)

    if version.has_fatram:
        import hsm
        if hsm.hsm_policy_available():
            ADD('hsm_policy', hsm.capture_backup())

    rv.write('\n# EOF\n')

    return rv.getvalue()
Ejemplo n.º 23
0
 def get_serial(self):
     return b2a_hex(self.data[0:4] + self.data[8:13])
Ejemplo n.º 24
0
def repr_params(args):
    return 'op=0x%x, p1=0x%x, p2=0x%x, body=%s' % (args.get(
        'opcode', 0xEF), args['p1'], args['p2'], ASC(b2a_hex(args['body'])) if
                                                   'body' in args else 'None')
Ejemplo n.º 25
0
 def write(self, b):
     self.fd.write(b2a_hex(b))