def write_file_encrypted(file_name: str, hw_session: HwSessionInfo, data: bytes):
    label = os.path.basename(file_name)

    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 = DMT_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
Exemplo n.º 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 dip2_type == DashTxType.SPEC_CB_TX:
                data["extraPayload"] = serialize_cbTx(data)
            elif dip2_type == DashTxType.LELANTUS_JSPLIT:
                data["extraPayload"] = serialize_Lelantus(data)
            else:
                raise NotImplementedError(
                    "Only spending of V3 coinbase outputs has been inplemented. "
                    "Please file an issue at https://github.com/firoorg/firo-masternode-tool/issues containing "
                    "the tx type=" + str(dip2_type))
            data["extraPayloadSize"] = len(data["extraPayload"]) >> 1

            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 = dash_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
def json_to_tx(coin, data):
    t = messages.TransactionType()
    t.version = data["version"]
    t.lock_time = data.get("locktime")

    if coin["decred"]:
        t.expiry = data["expiry"]

    t.inputs = [_json_to_input(coin, vin) for vin in data["vin"]]
    t.bin_outputs = [_json_to_bin_output(coin, vout) for vout in data["vout"]]

    # zcash extra data
    if is_zcash(coin) and t.version >= 2:
        joinsplit_cnt = len(data["vjoinsplit"])
        if joinsplit_cnt == 0:
            t.extra_data = b"\x00"
        elif joinsplit_cnt >= 253:
            # we assume cnt < 253, so we can treat varIntLen(cnt) as 1
            raise ValueError("Too many joinsplits")
        elif "hex" not in data:
            raise ValueError("Raw TX data required for Zcash joinsplit transaction")
        else:
            rawtx = bytes.fromhex(data["hex"])
            extra_data_len = 1 + joinsplit_cnt * 1802 + 32 + 64
            t.extra_data = rawtx[-extra_data_len:]

    if is_dash(coin):
        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 = dash_utils.num_to_varint(data["extraPayloadSize"]) + bytes.fromhex(
                data["extraPayload"]
            )

        # Trezor 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
Exemplo n.º 4
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 = dash_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
Exemplo n.º 5
0
def json_to_tx(tx_json):
    t = btc.from_json(tx_json)
    dip2_type = tx_json.get("type", 0)

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

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

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

    # Trezor 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
Exemplo n.º 6
0
def sign_tx(hw_session: HWSessionBase, rt_data: AppRuntimeData,
            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 previous transaction>,
    #    'txid': <hash of the previous 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 transactions
    for utxo in utxos_to_spend:
        if utxo.txid not in rawtransactions:
            tx = rt_data.dashd_intf.getrawtransaction(utxo.txid,
                                                      1,
                                                      skip_cache=False)
            if tx and tx.get('hex'):
                tx_raw = tx.get('hex')
            else:
                tx_raw = rt_data.dashd_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 = dash_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('Incorrect 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,
                                                  rt_data.dash_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 signing 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

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

        starting = False

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

    tx_raw = bytearray(new_transaction.serialize())
    return tx_raw, amount
Exemplo n.º 7
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
Exemplo n.º 8
0
def write_bytes_buf(data: ByteString) -> bytearray:
    return num_to_varint(len(data)) + data
Exemplo n.º 9
0
def json_to_tx(coin, data):
    t = messages.TransactionType()
    t.version = data["version"]
    t.lock_time = data.get("locktime")

    if coin["decred"]:
        t.expiry = data["expiry"]

    t.inputs = []
    for vin in data["vin"]:
        if "scriptSig" in vin and vin["scriptSig"]["hex"] == "c9":
            i = messages.TxInputType()
            i.prev_hash = b"\0" * 32
            i.prev_index = vin["sequence"]
            i.script_sig = bytes.fromhex(vin["scriptSig"]["hex"])
            i.sequence = vin["sequence"]
            t.inputs.append(i)
        else:
            t.inputs.append(_json_to_input(coin, vin))
    t.bin_outputs = [_json_to_bin_output(coin, vout) for vout in data["vout"]]

    # zcash extra data
    if is_zcash(coin) and t.version >= 2:
        joinsplit_cnt = len(data["vjoinsplit"])
        if joinsplit_cnt == 0:
            t.extra_data = b"\x00"
        elif joinsplit_cnt >= 253:
            # we assume cnt < 253, so we can treat varIntLen(cnt) as 1
            raise ValueError("Too many joinsplits")
        elif "hex" not in data:
            raise ValueError(
                "Raw TX data required for Zcash joinsplit transaction")
        else:
            rawtx = bytes.fromhex(data["hex"])
            extra_data_len = 1 + joinsplit_cnt * 1802 + 32 + 64
            t.extra_data = rawtx[-extra_data_len:]

    if is_extra_payload(coin):
        dip2_type = data.get("type", 0)

        if t.version == 3 and dip2_type != 0:
            # It's a DIP2 special TX with payload
            if dip2_type == DashTxType.SPEC_CB_TX:
                data["extraPayload"] = serialize_cbTx(data)
            elif dip2_type == DashTxType.LELANTUS_JSPLIT:
                data["extraPayload"] = serialize_Lelantus(data)
            else:
                raise NotImplementedError(
                    "Only spending of V3 coinbase outputs has been inplemented. "
                    "Please file an issue at https://github.com/firoorg/firo-masternode-tool/issues containing "
                    "the tx type=" + str(dip2_type))
            data["extraPayloadSize"] = len(data["extraPayload"]) >> 1

            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 = dash_utils.num_to_varint(
                data["extraPayloadSize"]) + bytes.fromhex(data["extraPayload"])

        # Trezor 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