Ejemplo n.º 1
0
async def get_tx_signatures(
        client: blue.Ledger,
        t: tx.Tx,
        prevouts: List[PrevoutInfo],
        derivation: str,
        sighash_type: int = SIGHASH_ALL) -> List[Optional[bytes]]:
    '''
    Sign a transaction
    Args:
        client              (Ledger): the Ledger context manager object
        t                    (tx.Tx): The transaction to sign
        prevouts (List[PrevoutInfo]): value for each Prevout
            must include the script if we intend to sign the input
            script must NOT be length-prefixed (e.g. 76a914... NOT 1976a914...)
        derivation             (str): m-prefixed derication for the signing key
        sighash_type           (int): Bitcoin-consensus sighash type, ledger
            firmware currently only supports ALL
    Returns:
        List[Optional[bytes]]: For each input, either a signature or None
    '''
    if len(prevouts) != len(t.tx_ins):
        raise ValueError('mismatch between txins and prevouts')

    if sighash_type != SIGHASH_ALL:
        raise ValueError('ledger firmware only supports SIGHASH_ALL')

    # Let's get the key so we can scan scripts for it
    key = await get_uncompressed_public_key(client, derivation)

    # start by packetizing version and len(vin)
    first_packet = _packetize_version_and_vin_length(t)
    packets = [first_packet]  # collect a list of packets for sending later

    # packetize each input
    for pair in zip(t.tx_ins, prevouts):
        packets.extend(_packetize_input(*pair))

    # packetize the whole vout
    packets.extend(_packetize_vout(t.tx_outs))

    # send all vin/vout packets
    for packet in packets:
        await client.exchange(packet)

    # calculate the request packet
    indices = utils.parse_derivation(derivation)
    last_packet = _transaction_final_packet(t.lock_time, indices, sighash_type)

    # build sigs. If we're not signing the input, return None at its index
    sigs = []
    for pair in zip(t.tx_ins, prevouts):
        sigs.append(await _get_sig(client, first_packet, last_packet, *pair)
                    if _signable(key, pair[1]) else None)

    return sigs
Ejemplo n.º 2
0
async def sign_transaction(client: blue.Ledger, t: UnsignedEthTx,
                           derivation: str) -> SignedEthTx:
    if (t['chainId'] * 2 + 35) + 1 > 255:
        raise ValueError('chainIds above 109 not currently supported.')

    deriv_indices = utils.parse_derivation(derivation)
    derivation_data = blue.derivation_path_to_apdu_data(deriv_indices)

    dummy_tx = SignedEthTx(to=t['to'],
                           value=t['value'],
                           gas=t['gas'],
                           gasPrice=t['gasPrice'],
                           nonce=t['nonce'],
                           data=t['data'],
                           v=t['chainId'],
                           r=0,
                           s=0)

    ser_tx = bytes.fromhex(transactions.serialize(dummy_tx))

    packets = _packetize_data(derivation_data, ser_tx)
    try:
        for packet in packets:
            result = await client.exchange(packet)
    except blue.LedgerException as e:
        if '6804' in str(e):
            details = 'Hint: is your derivation in the correct bip44 subtree?'
        if t['data'] == b'' or '6a80' not in str(e):
            raise e
        if t['gasPrice'] < 1000 or t['gas'] < 21000:
            details = 'Hint: gasPrice or gas too low?'
        else:
            details = 'Hint: enable data in the Ethereum app on the device?'
        raise blue.LedgerException(f'{str(e)}. {details}')

    v = result[0]
    r = int.from_bytes(result[1:33], 'big')
    s = int.from_bytes(result[33:65], 'big')

    signed = SignedEthTx(to=t['to'],
                         value=t['value'],
                         gas=t['gas'],
                         gasPrice=t['gasPrice'],
                         nonce=t['nonce'],
                         data=t['data'],
                         v=v,
                         r=r,
                         s=s)

    return signed
Ejemplo n.º 3
0
def _make_child_xpub(derivation: str,
                     parent_or_none: Optional[LedgerPubkey],
                     child: LedgerPubkey,
                     mainnet: bool = True) -> str:
    '''
    Builds an xpub for a derived child using its parent and path
    Args:
        derivation      (str): the m-prefixed derivation path e.g. m/44h/0h/0h
        parent (LedgerPubkey): the parent public key
        child  (LedgerPubkey): the child public key
        mainnet        (bool): whether to use mainnet prefixes
    '''
    indices = utils.parse_derivation(derivation)

    # determine appropriate xpub version bytes
    if not mainnet:
        prefix = utils.VERSION_BYTES['testnet']['public']
    else:
        prefix = utils.VERSION_BYTES['mainnet']['public']

    if parent_or_none is not None:
        # xpubs include the parent fingerprint
        parent = cast(LedgerPubkey, parent_or_none)
        compressed_parent_key = utils.compress_pubkey(parent['pubkey'])
        parent_fingerprint = rutils.hash160(compressed_parent_key)[:4]
        child_index = indices[-1].to_bytes(4, byteorder='big')
        depth = len(indices)
    else:
        # this means it's a master key
        parent_fingerprint = b'\x00' * 4
        child_index = b'\x00' * 4
        depth = 0

    # xpubs always use compressed pubkeys
    compressed_pubkey = utils.compress_pubkey(child['pubkey'])

    # build the xpub
    xpub = bytearray()
    xpub.extend(prefix)  # xpub prefix
    xpub.extend([depth])  # depth
    xpub.extend(parent_fingerprint)  # paren't fingerprint
    xpub.extend(child_index)  # index
    xpub.extend(child['chain_code'])  # chain_code
    xpub.extend(compressed_pubkey)  # pubkey (comp)
    return base58.encode(xpub)
Ejemplo n.º 4
0
async def get_key_info(client: blue.Ledger, derivation: str) -> LedgerPubkey:
    '''
    Fetch the pubkey at a specific derivation
    '''
    deriv_indices = utils.parse_derivation(derivation)
    derivation_data = blue.derivation_path_to_apdu_data(deriv_indices)

    pubkey_req_apdu = blue.make_apdu(
        command=b'\x02',  # ETH get key at derivation command
        p1=b'\x01',
        data=derivation_data)

    # It comes in a blob with chaincode and address
    pubkey_response = await client.exchange(pubkey_req_apdu)

    # return the parsed response
    pubkey = _parse_public_key_response(pubkey_response)
    return pubkey
Ejemplo n.º 5
0
async def _get_key_info(derivation: str) -> LedgerPubkey:
    '''
    This corresponds to the GET WALLET PUBLIC KEY command
    It asks the ledger for the key at a derivation path
    Args:
        derivation (str): the derivatoin path string
    Returns:
        (LedgerPubkey): The parsed public key with type, address and chain_code
    '''
    # first we get the path into a form that the ledger can understand
    deriv_indices = utils.parse_derivation(derivation)

    # make the adpu formatted request body
    pubkey_adpu_data = _derivation_path_to_adpu_data(deriv_indices)
    pubkey_adpu = _make_adpu(command=b'\x40',
                             data=pubkey_adpu_data,
                             p2=b'\x02')  # native segwit address

    # It comes in a blob with chaincode and address
    pubkey_response = await _exchange(pubkey_adpu)

    # return the parsed response
    pubkey = _parse_public_key_response(pubkey_response)
    return pubkey