コード例 #1
0
def write_file_encrypted(file_name: str, hw_session: HwSessionInfo, data: bytes):
    label = os.path.basename(file_name)

    if hw_session.app_config.hw_type:
        if not hw_session.hw_client:
            if not hw_session.hw_connect():
                return
    else:
        raise Exception('Invalid hardware wallet type in the app configuration.')

    protocol, hw_type_bin, bip32_path_n, encryption_key, encrypted_key_bin, pub_key_hash = \
        prepare_hw_encryption_attrs(hw_session, label)

    fer = Fernet(encryption_key)

    with open(file_name, 'wb') as f_ptr:

        header = HMT_ENCRYPTED_DATA_PREFIX + \
                 num_to_varint(protocol) + num_to_varint(hw_type_bin) + \
                 write_bytes_buf(bytearray(base64.b64encode(bytearray(label, 'utf-8')))) + \
                 write_bytes_buf(encrypted_key_bin) + \
                 write_int_list_buf(bip32_path_n) + \
                 write_bytes_buf(pub_key_hash)
        f_ptr.write(header)

        # slice the input data into ENC_FILE_BLOCK_SIZE-byte chunks, encrypt them and
        # write to file; each block will be preceded with the length of the encrypted
        # data chunk size
        begin_idx = 0
        while True:
            data_left = len(data) - begin_idx
            if data_left <= 0:
                break
            cur_input_chunk_size = min(ENC_FILE_BLOCK_SIZE, data_left)

            data_enc_base64 = fer.encrypt(data[begin_idx : begin_idx+cur_input_chunk_size])
            data_enc = base64.urlsafe_b64decode(data_enc_base64)
            cur_chunk_size_bin = len(data_enc).to_bytes(8, byteorder='little')  # write the size of the data chunk
            f_ptr.write(cur_chunk_size_bin)     # write the data
            f_ptr.write(data_enc)     # write the data
            begin_idx += cur_input_chunk_size
コード例 #2
0
    def get_tx(self, txhash):
        data = self.fetch_json(self.url, 'tx', txhash)

        t = proto_types.TransactionType()
        t.version = data['version']
        t.lock_time = data['locktime']

        for vin in data['vin']:
            i = t.inputs.add()
            if 'coinbase' in vin.keys():
                i.prev_hash = b"\0" * 32
                i.prev_index = 0xffffffff  # signed int -1
                i.script_sig = binascii.unhexlify(vin['coinbase'])
                i.sequence = vin['sequence']

            else:
                i.prev_hash = binascii.unhexlify(vin['txid'])
                i.prev_index = vin['vout']
                i.script_sig = binascii.unhexlify(vin['scriptSig']['hex'])
                i.sequence = vin['sequence']

        for vout in data['vout']:
            o = t.bin_outputs.add()
            o.amount = int(Decimal(str(vout['value'])) * 100000000)
            o.script_pubkey = binascii.unhexlify(vout['scriptPubKey']['hex'])

        dip2_type = data.get("type", 0)

        if t.version == 3 and dip2_type != 0:
            # It's a DIP2 special TX with payload

            if "extraPayloadSize" not in data or "extraPayload" not in data:
                raise ValueError("Payload data missing in DIP2 transaction")

            if data["extraPayloadSize"] * 2 != len(data["extraPayload"]):
                raise ValueError(
                    "extra_data_len (%d) does not match calculated length (%d)"
                    %
                    (data["extraPayloadSize"], len(data["extraPayload"]) * 2))
            t.extra_data = hatch_utils.num_to_varint(
                data["extraPayloadSize"]) + bytes.fromhex(data["extraPayload"])

        # KeepKey firmware doesn't understand the split of version and type, so let's mimic the
        # old serialization format
        t.version |= dip2_type << 16

        return t
コード例 #3
0
def write_int_list_buf(data: List[int]) -> bytearray:
    ret_data = num_to_varint(len(data))
    for item in data:
        ret_data += num_to_varint(item)
    return ret_data
コード例 #4
0
def write_bytes_buf(data: ByteString) -> bytearray:
    return num_to_varint(len(data)) + data
コード例 #5
0
def sign_tx(hw_session: HwSessionInfo,
            utxos_to_spend: List[wallet_common.UtxoType],
            tx_outputs: List[wallet_common.TxOutputType], tx_fee):
    client = hw_session.hw_client
    rawtransactions = {}
    decodedtransactions = {}

    # Each of the UTXOs will become an input in the new transaction. For each of those inputs, create
    # a Ledger's 'trusted input', that will be used by the the device to sign a transaction.
    trusted_inputs = []

    # arg_inputs: list of dicts
    #  {
    #    'locking_script': <Locking script of the UTXO used as an input. Used in the process of signing
    #                       transaction.>,
    #    'outputIndex': <index of the UTXO within the previus transaction>,
    #    'txid': <hash of the previus transaction>,
    #    'bip32_path': <BIP32 path of the HW key controlling UTXO's destination>,
    #    'pubkey': <Public key obtained from the HW using the bip32_path.>
    #    'signature' <Signature obtained as a result of processing the input. It will be used as a part of the
    #               unlocking script.>
    #  }
    #  Why do we need a locking script of the previous transaction? When hashing a new transaction before creating its
    #  signature, all placeholders for input's unlocking script has to be filled with locking script of the
    #  corresponding UTXO. Look here for the details:
    #    https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand)
    arg_inputs = []

    # A dictionary mapping bip32 path to a pubkeys obtained from the Ledger device - used to avoid
    # reading it multiple times for the same bip32 path
    bip32_to_address = {}

    # read previous transactins
    for utxo in utxos_to_spend:
        if utxo.txid not in rawtransactions:
            tx = hw_session.hatchd_intf.getrawtransaction(utxo.txid,
                                                          1,
                                                          skip_cache=False)
            if tx and tx.get('hex'):
                tx_raw = tx.get('hex')
            else:
                tx_raw = hw_session.hatchd_intf.getrawtransaction(
                    utxo.txid, 0, skip_cache=False)

            if tx_raw:
                rawtransactions[utxo.txid] = tx_raw
            decodedtransactions[utxo.txid] = tx

    amount = 0
    starting = True
    for idx, utxo in enumerate(utxos_to_spend):
        amount += utxo.satoshis

        raw_tx = rawtransactions.get(utxo.txid)
        if not raw_tx:
            raise Exception("Can't find raw transaction for txid: " +
                            utxo.txid)
        else:
            raw_tx = bytearray.fromhex(raw_tx)

        # parse the raw transaction, so that we can extract the UTXO locking script we refer to
        prev_transaction = bitcoinTransaction(raw_tx)

        data = decodedtransactions[utxo.txid]
        dip2_type = data.get("type", 0)
        if data['version'] == 3 and dip2_type != 0:
            # It's a DIP2 special TX with payload

            if "extraPayloadSize" not in data or "extraPayload" not in data:
                raise ValueError("Payload data missing in DIP2 transaction")

            if data["extraPayloadSize"] * 2 != len(data["extraPayload"]):
                raise ValueError(
                    "extra_data_len (%d) does not match calculated length (%d)"
                    %
                    (data["extraPayloadSize"], len(data["extraPayload"]) * 2))
            prev_transaction.extra_data = hatch_utils.num_to_varint(
                data["extraPayloadSize"]) + bytes.fromhex(data["extraPayload"])
        else:
            prev_transaction.extra_data = bytes()

        utxo_tx_index = utxo.output_index
        if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs):
            raise Exception('Incorrent value of outputIndex for UTXO %s' %
                            str(idx))

        trusted_input = client.getTrustedInput(prev_transaction, utxo_tx_index)
        trusted_inputs.append(trusted_input)

        bip32_path = utxo.bip32_path
        bip32_path = clean_bip32_path(bip32_path)
        pubkey = bip32_to_address.get(bip32_path)
        if not pubkey:
            pubkey = compress_public_key(
                client.getWalletPublicKey(bip32_path)['publicKey'])
            bip32_to_address[bip32_path] = pubkey
        pubkey_hash = bitcoin.bin_hash160(pubkey)

        # verify if the public key hash of the wallet's bip32 path is the same as specified in the UTXO locking script
        # if they differ, signature and public key we produce and are going to include in the unlocking script won't
        # match the locking script conditions - transaction will be rejected by the network
        pubkey_hash_from_script = extract_pkh_from_locking_script(
            prev_transaction.outputs[utxo_tx_index].script)
        if pubkey_hash != pubkey_hash_from_script:
            logging.error(
                "Error: different public key hashes for the BIP32 path %s (UTXO %s) and the UTXO locking "
                "script. Your signed transaction will not be validated by the network."
                % (bip32_path, str(idx)))

        arg_inputs.append({
            'locking_script':
            prev_transaction.outputs[utxo.output_index].script,
            'pubkey':
            pubkey,
            'bip32_path':
            bip32_path,
            'outputIndex':
            utxo.output_index,
            'txid':
            utxo.txid
        })

    amount -= int(tx_fee)
    amount = int(amount)

    new_transaction = bitcoinTransaction(
    )  # new transaction object to be used for serialization at the last stage
    new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00])
    for out in tx_outputs:
        output = bitcoinOutput()
        output.script = compose_tx_locking_script(
            out.address, hw_session.app_config.hatch_network)
        output.amount = int.to_bytes(out.satoshis, 8, byteorder='little')
        new_transaction.outputs.append(output)

    # join all outputs - will be used by Ledger for sigining transaction
    all_outputs_raw = new_transaction.serializeOutputs()

    # sign all inputs on Ledger and add inputs in the new_transaction object for serialization
    for idx, new_input in enumerate(arg_inputs):

        client.startUntrustedTransaction(starting, idx, trusted_inputs,
                                         new_input['locking_script'])
        client.finalizeInputFull(all_outputs_raw)
        sig = client.untrustedHashSign(new_input['bip32_path'], lockTime=0)
        new_input['signature'] = sig

        input = bitcoinInput()
        input.prevOut = bytearray.fromhex(new_input['txid'])[::-1] + \
                        int.to_bytes(new_input['outputIndex'], 4, byteorder='little')
        input.script = bytearray([len(sig)]) + sig + bytearray(
            [0x21]) + new_input['pubkey']
        input.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF])
        new_transaction.inputs.append(input)

        starting = False

    new_transaction.lockTime = bytearray([0, 0, 0, 0])

    tx_raw = bytearray(new_transaction.serialize())
    return tx_raw, amount