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