コード例 #1
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def bip39_passphrase(passphrase, verbose=False):
    "Provide a BIP39 passphrase"

    dev = ColdcardDevice(sn=force_serial)

    dev.check_mitm()

    ok = dev.send_recv(CCProtocolPacker.bip39_passphrase(passphrase), timeout=None)
    assert ok == None

    print("Waiting for OK on the Coldcard...", end='', file=sys.stderr)
    sys.stderr.flush()

    while 1:
        time.sleep(0.250)
        done = dev.send_recv(CCProtocolPacker.get_passphrase_done(), timeout=None)
        if done == None:
            continue
        break

    print("\r                                  \r", end='', file=sys.stderr)
    sys.stderr.flush()

    if verbose:
        xpub = done
        click.echo(xpub)
    else:
        click.echo('Done.')
コード例 #2
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def hsm_setup(policy=None, dry_run=False):
    '''
Enable Hardware Security Module (HSM) mode.

Upload policy file (or use existing policy) and start HSM mode on device. User must approve startup.
All PSBT's will be signed automatically based on that policy.

'''
    dev = ColdcardDevice(sn=force_serial)
    dev.check_mitm()

    if policy:
        if dry_run:
            # check it looks reasonable, but jsut a JSON check
            raw = open(policy, 'rt').read()
            j = json.loads(raw)

            click.echo("Policy ok")
            sys.exit(0)

        file_len, sha = real_file_upload(open(policy, 'rb'), dev=dev)

        dev.send_recv(CCProtocolPacker.hsm_start(file_len, sha))
    else:
        if dry_run:
            raise click.UsageError("Dry run not useful without a policy file to check.")

        dev.send_recv(CCProtocolPacker.hsm_start())

    click.echo("Approve HSM policy on Coldcard screen.")
コード例 #3
0
ファイル: cli.py プロジェクト: nondejus/ckcc-protocol
def sign_message(message,
                 path,
                 verbose=True,
                 just_sig=False,
                 wrap=False,
                 segwit=False):
    "Sign a short text message"

    dev = ColdcardDevice(sn=force_serial)

    if wrap:
        addr_fmt = AF_P2WPKH_P2SH
    elif segwit:
        addr_fmt = AF_P2WPKH
    else:
        addr_fmt = AF_CLASSIC

    # NOTE: initial version of firmware not expected to do segwit stuff right, since
    # standard very much still in flux, see: <https://github.com/bitcoin/bitcoin/issues/10542>

    # not enforcing policy here on msg contents, so we can define that on product
    message = message.encode('ascii') if not isinstance(message,
                                                        bytes) else message

    ok = dev.send_recv(CCProtocolPacker.sign_message(message, path, addr_fmt),
                       timeout=None)
    assert ok == None

    print("Waiting for OK on the Coldcard...", end='', file=sys.stderr)
    sys.stderr.flush()

    while 1:
        time.sleep(0.250)
        done = dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=None)
        if done == None:
            continue

        break

    print("\r                                  \r", end='', file=sys.stderr)
    sys.stderr.flush()

    if len(done) != 2:
        click.echo('Failed: %r' % done)
        sys.exit(1)

    addr, raw = done

    sig = str(b64encode(raw), 'ascii').replace('\n', '')

    if just_sig:
        click.echo(str(sig))
    elif verbose:
        click.echo(
            '-----BEGIN SIGNED MESSAGE-----\n{msg}\n-----BEGIN '
            'SIGNATURE-----\n{addr}\n{sig}\n-----END SIGNED MESSAGE-----'.
            format(msg=message.decode('ascii'), addr=addr, sig=sig))
    else:
        click.echo('%s\n%s\n%s' % (message.decode('ascii'), addr, sig))
コード例 #4
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def file_upload(filename, blksize, multisig=False):
    "Send file to Coldcard (PSBT transaction or firmware)"

    # NOTE: mostly for debug/dev usage.
    dev = ColdcardDevice(sn=force_serial)

    file_len, sha = real_file_upload(filename, blksize, dev=dev)

    if multisig:
        dev.send_recv(CCProtocolPacker.multisig_enroll(file_len, sha))
コード例 #5
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def start_backup(outdir, outfile, verbose=False):
    '''Creates 7z encrypted backup file after prompting user to remember a massive passphrase. \
Downloads the AES-encrypted data backup and by default, saves into current directory using \
a filename based on today's date.'''

    dev = ColdcardDevice(sn=force_serial)

    dev.check_mitm()

    ok = dev.send_recv(CCProtocolPacker.start_backup())
    assert ok == None

    result, chk = wait_and_download(dev, CCProtocolPacker.get_backup_file(), 0)

    if outfile:
        outfile.write(result)
        outfile.close()
        fn = outfile.name
    else:
        assert outdir

        # pick a useful filename, if they gave a dirname
        fn = os.path.join(outdir, time.strftime('backup-%Y%m%d-%H%M.7z'))

        open(fn, 'wb').write(result)

    click.echo("Wrote %d bytes into: %s\nSHA256: %s" % (len(result), fn, str(b2a_hex(chk), 'ascii')))
コード例 #6
0
ファイル: cli.py プロジェクト: nondejus/ckcc-protocol
def enroll_xpub(name,
                min_signers,
                path,
                num_signers,
                output_file=None,
                verbose=False,
                just_add=False):
    '''
Create a skeleton file which defines a multisig wallet.

When completed, use with: "ckcc upload -m wallet.txt" or put on SD card.
'''

    dev = ColdcardDevice(sn=force_serial)
    dev.check_mitm()

    xfp = dev.master_fingerprint
    my_xpub = dev.send_recv(CCProtocolPacker.get_xpub(path), timeout=None)
    new_line = "%s: %s" % (xfp2str(xfp), my_xpub)

    if just_add:
        click.echo(new_line)
        sys.exit(0)

    N = num_signers

    if N < min_signers:
        N = min_signers

    if not (1 <= N < 15):
        click.echo("N must be 1..15")
        sys.exit(1)

    if min_signers == 0:
        min_signers = N

    if not (1 <= min_signers <= N):
        click.echo(
            f"Minimum number of signers (M) must be between 1 and N={N}")
        sys.exit(1)

    if not (1 <= len(name) <= 20) or name != str(name.encode('utf8'), 'ascii',
                                                 'ignore'):
        click.echo("Name must be between 1 and 20 characters of ASCII.")
        sys.exit(1)

    # render into a template
    config = f'name: {name}\npolicy: {min_signers} of {N}\n\n#path: {path}\n{new_line}\n'
    if num_signers != 1:
        config += '\n'.join(f'#{i+2}# FINGERPRINT: xpub123123123123123'
                            for i in range(num_signers - 1))
        config += '\n'

    if verbose or not output_file:
        click.echo(config[:-1])

    if output_file:
        output_file.write(config)
        output_file.close()
        click.echo(f"Wrote to: {output_file.name}")
コード例 #7
0
ファイル: cli.py プロジェクト: missaghi/ckcc-protocol
def show_address(path_prefix,
                 script,
                 fingerprints,
                 min_signers=0,
                 quiet=False,
                 segwit=False,
                 wrap=False):
    '''Show a multisig payment address on-screen

    Append subkey path to fingerprint value (4369050F/1/0/0 for example) or omit for 0/0/0

    This is provided as a demo or debug feature: you'll need the full redeem script (hex),
    and the fingerprints and paths used to generate each public key inside that.
    Order of fingerprint/path must match order of pubkeys in script.
    '''

    dev = ColdcardDevice(sn=force_serial)

    addr_fmt = AF_P2SH
    if segwit:
        addr_fmt = AF_P2WSH
    if wrap:
        addr_fmt = AF_P2WSH_P2SH

    script = a2b_hex(script)
    N = len(fingerprints)

    if N <= 16:
        if not min_signers:
            assert N <= 15
            min_signers = script[0] - 80
        else:
            assert min_signers == script[0], "M conficts with script"

        assert script[-1] == 0xAE, "expect script to end with OP_CHECKMULTISIG"
        assert script[-2] == 80 + N, "second last byte should encode N"

    xfp_paths = []
    for idx, xfp in enumerate(fingerprints):
        if '/' not in xfp:
            # this isn't BIP45 compliant but we don't know the cosigner's index
            # values, since they would have been suffled when the redeem script is sorted
            p = '0/0/0'
        else:
            # better if all paths provided
            xfp, p = xfp.split('/', 1)
            p = path_prefix + p

        xfp_paths.append(str_to_int_path(xfp, p))

    addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(min_signers,
                                                            xfp_paths,
                                                            script,
                                                            addr_fmt=addr_fmt),
                         timeout=None)

    if quiet:
        click.echo(addr)
    else:
        click.echo('Displaying address:\n\n%s\n' % addr)
コード例 #8
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def sign_transaction(psbt_in, psbt_out=None, verbose=False, b64_mode=False, hex_mode=False, finalize=False, visualize=False, signed=False):
    "Approve a spending transaction by signing it on Coldcard"

    dev = ColdcardDevice(sn=force_serial)
    dev.check_mitm()

    # Handle non-binary encodings, and incorrect files.
    taste = psbt_in.read(10)
    psbt_in.seek(0)
    if taste == b'70736274ff' or taste == b'70736274FF':
        # Looks hex encoded; make into binary again
        hx = ''.join(re.findall(r'[0-9a-fA-F]*', psbt_in.read().decode('ascii')))
        psbt_in = io.BytesIO(a2b_hex(hx))
    elif taste[0:6] == b'cHNidP':
        # Base64 encoded input
        psbt_in = io.BytesIO(b64decode(psbt_in.read()))
    elif taste[0:5] != b'psbt\xff':
        click.echo("File doesn't have PSBT magic number at start.")
        sys.exit(1)

    # upload the transaction
    txn_len, sha = real_file_upload(psbt_in, dev=dev)

    flags = 0x0
    if visualize or signed:
        flags |= STXN_VISUALIZE
        if signed:
            flags |= STXN_SIGNED
    elif finalize:
        flags |= STXN_FINALIZE

    # start the signing process
    ok = dev.send_recv(CCProtocolPacker.sign_transaction(txn_len, sha, flags=flags), timeout=None)
    assert ok == None

    # errors will raise here, no need for error display
    result, _ = wait_and_download(dev, CCProtocolPacker.get_signed_txn(), 1)

    # If 'finalize' is set, we are outputing a bitcoin transaction,
    # ready for the p2p network. If the CC wasn't able to finalize it,
    # an exception would have occured. Most people will want hex here, but
    # resisting the urge to force it.

    if visualize:
        if psbt_out:
            psbt_out.write(result)
        else:
            click.echo(result, nl=False)
    else:
        # save it
        if hex_mode:
            result = b2a_hex(result)
        elif b64_mode or (not psbt_out and os.isatty(0)):
            result = b64encode(result)

        if psbt_out:
            psbt_out.write(result)
        else:
            click.echo(result)
コード例 #9
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def get_version():
    "Get the version of the firmware installed"

    dev = ColdcardDevice(sn=force_serial)

    v = dev.send_recv(CCProtocolPacker.version())

    click.echo(v)
コード例 #10
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def get_storage_locker():
    "Get the value held in the Storage Locker (not Bitcoin related, reserved for HSM use)"

    dev = ColdcardDevice(sn=force_serial)

    ls = dev.send_recv(CCProtocolPacker.get_storage_locker(), timeout=None)

    click.echo(ls)
コード例 #11
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def bag_number(number):
    "Factory: set or read bag number -- single use only!"
    dev = ColdcardDevice(sn=force_serial)

    nn = b'' if not number else number.encode('ascii')

    resp = dev.send_recv(CCProtocolPacker.bag_number(nn))

    print("Bag number: %r" % resp)
コード例 #12
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def run_eval(stmt):
    "Simulator only: exec a python script"
        
    dev = ColdcardDevice(sn=force_serial)

    stmt = ' '.join(stmt)

    v = dev.send_recv(b'EXEC' + stmt.encode('utf-8'))

    click.echo(v)
コード例 #13
0
def run_eval(stmt):
    "Simulator only: eval a python statement"

    dev = ColdcardDevice(sn=force_serial)

    stmt = ' '.join(stmt)

    v = dev.send_recv(b'EVAL' + stmt.encode('utf-8'))

    print(v)
コード例 #14
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def get_block_chain():
    '''Get which blockchain (Bitcoin/Testnet) is configured.

    BTC=>Bitcoin  or  XTN=>Bitcoin Testnet
    '''

    dev = ColdcardDevice(sn=force_serial)

    code = dev.send_recv(CCProtocolPacker.block_chain())

    click.echo(code)
コード例 #15
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def get_xpub(subpath):
    "Get the XPUB for this wallet (master level, or any derivation)"

    dev = ColdcardDevice(sn=force_serial)

    if len(subpath) == 1:
        if subpath[0] == 'bip44':
            subpath = BIP44_FIRST

    xpub = dev.send_recv(CCProtocolPacker.get_xpub(subpath), timeout=None)

    click.echo(xpub)
コード例 #16
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def hsm_status():
    '''
Get current status of HSM feature.

Is it running, what is the policy (summary only).
'''
    
    dev = ColdcardDevice(sn=force_serial)
    dev.check_mitm()

    resp = dev.send_recv(CCProtocolPacker.hsm_status())

    o = json.loads(resp)

    click.echo(pformat(o))
コード例 #17
0
ファイル: cli.py プロジェクト: nondejus/ckcc-protocol
def show_address(script, fingerprints, quiet=False, segwit=False, wrap=False):
    '''Show a multisig payment address on-screen.

    Needs a redeem script and list of fingerprint/path (4369050F/1/0/0 for example).

    This is provided as a demo or debug feature. You'll need need some way to
    generate the full redeem script (hex), and the fingerprints and paths used to
    generate each public key inside that. The order of fingerprint/paths must
    match order of pubkeys in the script.
    '''

    dev = ColdcardDevice(sn=force_serial)

    addr_fmt = AF_P2SH
    if segwit:
        addr_fmt = AF_P2WSH
    if wrap:
        addr_fmt = AF_P2WSH_P2SH

    script = a2b_hex(script)
    N = len(fingerprints)

    assert 1 <= N <= 15, "bad N"

    min_signers = script[0] - 80
    assert 1 <= min_signers <= N, "bad M"

    assert script[-1] == 0xAE, "expect script to end with OP_CHECKMULTISIG"
    assert script[-2] == 80 + N, "second last byte should encode N"

    xfp_paths = []
    for idx, xfp in enumerate(fingerprints):
        assert '/' in xfp, 'Needs a XFP/path: ' + xfp
        xfp, p = xfp.split('/', 1)

        xfp_paths.append(str_to_int_path(xfp, p))

    addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(min_signers,
                                                            xfp_paths,
                                                            script,
                                                            addr_fmt=addr_fmt),
                         timeout=None)

    if quiet:
        click.echo(addr)
    else:
        click.echo('Displaying address:\n\n%s\n' % addr)
コード例 #18
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def get_pubkey(subpath):
    '''Get the public key for a derivation path

    Dump 33-byte (compressed, SEC encoded) public key value.
    '''

    try:
        from pycoin.key.BIP32Node import BIP32Node
    except:
        raise click.Abort("pycoin must be installed, not found.")

    dev = ColdcardDevice(sn=force_serial)

    xpub = dev.send_recv(CCProtocolPacker.get_xpub(subpath), timeout=None)

    node = BIP32Node.from_hwif(xpub)

    click.echo(b2a_hex(node.sec()))
コード例 #19
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def show_address(path, quiet=False, segwit=False, wrap=False):
    "Show the human version of an address"

    dev = ColdcardDevice(sn=force_serial)

    if wrap:
        addr_fmt = AF_P2WPKH_P2SH
    elif segwit:
        addr_fmt = AF_P2WPKH
    else:
        addr_fmt = AF_CLASSIC

    addr = dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=None)

    if quiet:
        click.echo(addr)
    else:
        click.echo('Displaying address:\n\n%s\n' % addr)
コード例 #20
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def usb_test(single):
    "Test USB connection (debug/dev)"
    dev = ColdcardDevice(sn=force_serial)

    rng = []
    rng.extend(range(55, 66))       # buggy lengths are around 64 
    rng.extend(range(1013, 1024))

    # we have 4 bytes of overhead (args) for ping cmd, so this will be max-length
    rng.extend(range(MAX_MSG_LEN-10, MAX_MSG_LEN-4))

    #print(repr(rng))

    for i in rng:
        print("Ping with length: %d" % i, end='')
        body = os.urandom(i) if single is None else bytes([single]*i)
        rb = dev.send_recv(CCProtocolPacker.ping(body))
        assert rb == body, "Fail @ len: %d, got back %d bytes\n%r !=\n%r" % (
                                        i, len(rb), b2a_hex(body), b2a_hex(rb))
        print("  Okay")
コード例 #21
0
ファイル: cli.py プロジェクト: spirosrap/ckcc-protocol
def sign_transaction(psbt_in,
                     psbt_out,
                     verbose=False,
                     hex_mode=False,
                     finalize=True):
    "Approve a spending transaction (by signing it on Coldcard)"

    dev = ColdcardDevice(sn=force_serial)

    dev.check_mitm()

    # not enforcing policy here on msg contents, so we can define that on product
    taste = psbt_in.read(10)
    psbt_in.seek(0)
    if taste == b'70736274ff':
        # hex encoded; make binary
        psbt_in = io.BytesIO(a2b_hex(psbt_in.read()))
        hex_mode = True
    elif taste[0:5] != b'psbt\xff':
        click.echo("File doesn't have PSBT magic number at start.")
        sys.exit(1)

    # upload the transaction
    txn_len, sha = real_file_upload(psbt_in, dev=dev)

    # start the signing process
    ok = dev.send_recv(CCProtocolPacker.sign_transaction(txn_len, sha),
                       timeout=None)
    assert ok == None

    result, _ = wait_and_download(dev, CCProtocolPacker.get_signed_txn(), 1)

    if finalize:
        # assume(?) transaction is completely signed, and output the
        # bitcoin transaction to be sent.
        # XXX maybe do this on embedded side, when txn is final?
        # XXX otherwise, need to parse PSBT and also handle combining properly
        pass

    # save it
    psbt_out.write(b2a_hex(result) if hex_mode else result)
コード例 #22
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def user_auth(psbt_file, next_code=None):
    '''
Generate the 6-digit code needed for a specific PSBT file to authorize
it's signing on the Coldcard in HSM mode.
'''

    if not next_code:
        dev = ColdcardDevice(sn=force_serial)
        dev.check_mitm()

        resp = dev.send_recv(CCProtocolPacker.hsm_status())
        o = json.loads(resp)

        assert o['active'], "Coldcard not in HSM mode"

        next_code = o['next_local_code']

    psbt_hash = sha256(psbt_file.read()).digest()

    rv = calc_local_pincode(psbt_hash, next_code)

    print("Local authorization code is:\n\n\t%s\n" % rv)
コード例 #23
0
ファイル: coldcardi.py プロジェクト: nvk/HWI
class ColdCardClient(HardwareWalletClient):

    # device is an HID device that has already been opened.
    def __init__(self, device):
        super(ColdCardClient, self).__init__(device)
        self.device = ColdcardDevice(dev=device)

    # Must return a dict with the xpub
    # Retrieves the public key at the specified BIP 32 derivation path
    def get_pubkey_at_path(self, path):
        path = path.replace('h', '\'')
        path = path.replace('H', '\'')
        xpub = self.device.send_recv(CCProtocolPacker.get_xpub(path),
                                     timeout=None)
        if self.is_testnet:
            return {'xpub': xpub_main_2_test(xpub)}
        else:
            return {'xpub': xpub}

    # Must return a hex string with the signed transaction
    # The tx must be in the combined unsigned transaction format
    def sign_tx(self, tx):
        self.device.check_mitm()

        # Get psbt in hex and then make binary
        fd = io.BytesIO(base64.b64decode(tx.serialize()))

        # learn size (portable way)
        offset = 0
        sz = fd.seek(0, 2)
        fd.seek(0)

        left = sz
        chk = sha256()
        for pos in range(0, sz, MAX_BLK_LEN):
            here = fd.read(min(MAX_BLK_LEN, left))
            if not here: break
            left -= len(here)
            result = self.device.send_recv(
                CCProtocolPacker.upload(pos, sz, here))
            assert result == pos
            chk.update(here)

        # do a verify
        expect = chk.digest()
        result = self.device.send_recv(CCProtocolPacker.sha256())
        assert len(result) == 32
        if result != expect:
            raise ValueError("Wrong checksum:\nexpect: %s\n   got: %s" %
                             (b2a_hex(expect).decode('ascii'),
                              b2a_hex(result).decode('ascii')))

        # start the signing process
        ok = self.device.send_recv(CCProtocolPacker.sign_transaction(
            sz, expect),
                                   timeout=None)
        assert ok == None

        print("Waiting for OK on the Coldcard...")

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_signed_txn(),
                                         timeout=None)
            if done == None:
                continue
            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        result_len, result_sha = done

        result = self.device.download_file(result_len,
                                           result_sha,
                                           file_number=1)
        return {'psbt': base64.b64encode(result).decode()}

    # Must return a base64 encoded string with the signed message
    # The message can be any string. keypath is the bip 32 derivation path for the key to sign with
    def sign_message(self, message, keypath):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Display address of specified type on the device. Only supports single-key based addresses.
    def display_address(self, keypath, p2sh_p2wpkh, bech32):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Setup a new device
    def setup_device(self):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Wipe this device
    def wipe_device(self):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Close the device
    def close(self):
        self.device.close()
コード例 #24
0
 def cleanup_simulator():
     dev = ColdcardDevice(sn='/tmp/ckcc-simulator.sock')
     resp = dev.send_recv(CCProtocolPacker.logout())
コード例 #25
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def user_auth(username, token=None, password=None, prompt=None, totp=None, psbt_file=None, debug=False):
    '''
Indicate specific user is present (for HSM).

Username and 2FA (TOTP, 6-digits) value or password are required. To use
password, the PSBT file in question must be provided.
'''
    import time
    from hmac import HMAC
    from hashlib import pbkdf2_hmac, sha256

    dryrun = True
    dev = ColdcardDevice(sn=force_serial)
    dev.check_mitm()

    if psbt_file or password:
        if psbt_file:
            psbt_hash = sha256(psbt_file.read()).digest()
            dryrun = False
        else:
            psbt_hash = bytes(32)

        pw = token or click.prompt('Password (hidden)', hide_input=True)
        secret = dev.hash_password(pw.encode('utf8'))

        token = HMAC(secret, msg=psbt_hash, digestmod=sha256).digest()

        if debug:
            click.echo("  secret = %s" % B2A(secret))
            click.echo("    salt = %s" % B2A(salt))

        totp_time = 0
    else:
        if not token:
            token = click.prompt('2FA Token (6 digits)', hide_input=False)

        if len(token) != 6 or not token.isdigit():
            raise click.UsageError("2FA Token must be 6 decimal digits")

        token = token.encode('ascii')

        now = int(time.time())
        if now % 30 < 5:
            click.echo("NOTE: TOTP was on edge of expiry limit! Might not work.")
        totp_time =  now // 30

    #raise click.UsageError("Need PSBT file as part of HMAC for password")

    assert token and len(token) in {6, 32}
    username = username.encode('ascii')

    if debug:
        click.echo(" username = %s" % username.decode('ascii'))
        click.echo("    token = %s" % (B2A(token) if len(token) > 6 else token.decode('ascii')))
        click.echo("totp_time = %d" % totp_time)

    resp = dev.send_recv(CCProtocolPacker.user_auth(username, token, totp_time))

    if not resp:
        click.echo("Correct or queued")
    else:
        click.echo(f'Problem: {resp}')
コード例 #26
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def new_user(username, totp_create=False, totp_secret=None, text_secret=None, ask_pass=False,
                do_delete=False, debug=False, show_qr=False, hotp=False, pick_pass=False):
    '''
Create a new user on the Coldcard for HSM policy (also delete).

You can input a password (interactively), or one can be picked
by the Coldcard. When possible the QR to enrol your 2FA app will
be shown on the Coldcard screen.
'''
    from base64 import b32encode, b32decode

    username = username.encode('ascii')
    assert 1 <= len(username) <= MAX_USERNAME_LEN, "Username length wrong"

    dev = ColdcardDevice(sn=force_serial)
    dev.check_mitm()

    if do_delete:
        dev.send_recv(CCProtocolPacker.delete_user(username))
        click.echo('Deleted, if it was there')
        return

    if ask_pass:
        assert not text_secret, "dont give and ask for password"
        text_secret = click.prompt('Password (hidden)', hide_input=True, confirmation_prompt=True)
        mode = USER_AUTH_HMAC

    if totp_secret:
        secret = b32decode(totp_secret, casefold=True)
        assert len(secret) in {10, 20}
        mode = USER_AUTH_TOTP
    elif hotp:
        mode = USER_AUTH_HOTP
        secret = b''
    elif pick_pass or text_secret:
        mode = USER_AUTH_HMAC
    else:
        # default is TOTP
        secret = b''
        mode = USER_AUTH_TOTP
    
    if mode == USER_AUTH_HMAC:
        # default is text passwords
        secret = dev.hash_password(text_secret.encode('utf8')) if text_secret else b''
        assert not show_qr, 'QR not appropriate for text passwords'

    if not secret and not show_qr:
        # ask the Coldcard to show the QR (for password or TOTP shared secret)
        mode |= USER_AUTH_SHOW_QR

    new_secret = dev.send_recv(CCProtocolPacker.create_user(username, mode, secret))

    if show_qr and new_secret:
        # format the URL thing ... needs a spec
        username = username.decode('ascii')
        secret = new_secret or b32encode(secret).decode('ascii')
        mode = 'hotp' if mode == USER_AUTH_HOTP else 'totp'
        click.echo(f'otpauth://{mode}/{username}?secret={secret}&issuer=Coldcard%20{dev.serial}')
    elif not text_secret and new_secret:
        click.echo(f'New password is: {new_secret}')
    else:
        click.echo('Done')
コード例 #27
0
class ColdcardClient(HardwareWalletClient):
    def __init__(self, path, password=''):
        super(ColdcardClient, self).__init__(path, password)
        # Simulator hard coded pipe socket
        if path == CC_SIMULATOR_SOCK:
            self.device = ColdcardDevice(sn=path)
        else:
            device = hid.device()
            device.open_path(path.encode())
            self.device = ColdcardDevice(dev=device)

    # Must return a dict with the xpub
    # Retrieves the public key at the specified BIP 32 derivation path
    def get_pubkey_at_path(self, path):
        self.device.check_mitm()
        path = path.replace('h', '\'')
        path = path.replace('H', '\'')
        xpub = self.device.send_recv(CCProtocolPacker.get_xpub(path),
                                     timeout=None)
        if self.is_testnet:
            return {'xpub': xpub_main_2_test(xpub)}
        else:
            return {'xpub': xpub}

    # Must return a hex string with the signed transaction
    # The tx must be in the combined unsigned transaction format
    def sign_tx(self, tx):
        self.device.check_mitm()

        # Get psbt in hex and then make binary
        fd = io.BytesIO(base64.b64decode(tx.serialize()))

        # learn size (portable way)
        offset = 0
        sz = fd.seek(0, 2)
        fd.seek(0)

        left = sz
        chk = sha256()
        for pos in range(0, sz, MAX_BLK_LEN):
            here = fd.read(min(MAX_BLK_LEN, left))
            if not here: break
            left -= len(here)
            result = self.device.send_recv(
                CCProtocolPacker.upload(pos, sz, here))
            assert result == pos
            chk.update(here)

        # do a verify
        expect = chk.digest()
        result = self.device.send_recv(CCProtocolPacker.sha256())
        assert len(result) == 32
        if result != expect:
            raise ValueError("Wrong checksum:\nexpect: %s\n   got: %s" %
                             (b2a_hex(expect).decode('ascii'),
                              b2a_hex(result).decode('ascii')))

        # start the signing process
        ok = self.device.send_recv(CCProtocolPacker.sign_transaction(
            sz, expect),
                                   timeout=None)
        assert ok == None
        if self.device.is_simulator:
            self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))

        print("Waiting for OK on the Coldcard...")

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_signed_txn(),
                                         timeout=None)
            if done == None:
                continue
            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        result_len, result_sha = done

        result = self.device.download_file(result_len,
                                           result_sha,
                                           file_number=1)
        return {'psbt': base64.b64encode(result).decode()}

    # Must return a base64 encoded string with the signed message
    # The message can be any string. keypath is the bip 32 derivation path for the key to sign with
    def sign_message(self, message, keypath):
        self.device.check_mitm()
        keypath = keypath.replace('h', '\'')
        keypath = keypath.replace('H', '\'')

        try:
            ok = self.device.send_recv(CCProtocolPacker.sign_message(
                message.encode(), keypath, AF_CLASSIC),
                                       timeout=None)
            assert ok == None
            if self.device.is_simulator:
                self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))
        except CCProtoError as e:
            raise ValueError(str(e))

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_signed_msg(),
                                         timeout=None)
            if done == None:
                continue

            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        addr, raw = done

        sig = str(base64.b64encode(raw), 'ascii').replace('\n', '')
        return {"signature": sig}

    # Display address of specified type on the device. Only supports single-key based addresses.
    def display_address(self, keypath, p2sh_p2wpkh, bech32):
        self.device.check_mitm()
        keypath = keypath.replace('h', '\'')
        keypath = keypath.replace('H', '\'')

        if p2sh_p2wpkh:
            format = AF_P2WPKH_P2SH
        elif bech32:
            format = AF_P2WPKH
        else:
            format = AF_CLASSIC
        address = self.device.send_recv(CCProtocolPacker.show_address(
            keypath, format),
                                        timeout=None)
        if self.device.is_simulator:
            self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))
        return {'address': address}

    # Setup a new device
    def setup_device(self, label='', passphrase=''):
        raise UnavailableActionError(
            'The Coldcard does not support software setup')

    # Wipe this device
    def wipe_device(self):
        raise UnavailableActionError(
            'The Coldcard does not support wiping via software')

    # Restore device from mnemonic or xprv
    def restore_device(self, label=''):
        raise UnavailableActionError(
            'The Coldcard does not support restoring via software')

    # Begin backup process
    def backup_device(self, label='', passphrase=''):
        self.device.check_mitm()

        ok = self.device.send_recv(CCProtocolPacker.start_backup())
        assert ok == None

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_backup_file(),
                                         timeout=None)
            if done == None:
                continue
            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        result_len, result_sha = done

        result = self.device.download_file(result_len,
                                           result_sha,
                                           file_number=0)
        filename = time.strftime('backup-%Y%m%d-%H%M.7z')
        open(filename, 'wb').write(result)
        return {
            'success': True,
            'message': 'The backup has be written to {}'.format(filename)
        }

    # Close the device
    def close(self):
        self.device.close()
コード例 #28
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def logout():
    "Securely logout of device (will require replug to start over)"
    dev = ColdcardDevice(sn=force_serial)

    resp = dev.send_recv(CCProtocolPacker.logout())
    print("Device says: %r" % resp if resp else "Okay!")
コード例 #29
0
ファイル: cli.py プロジェクト: justinmoon/ckcc-protocol
def reboot():
    "Reboot coldcard, force relogin and start over"
    dev = ColdcardDevice(sn=force_serial)

    resp = dev.send_recv(CCProtocolPacker.reboot())
    print("Device says: %r" % resp if resp else "Okay!")