def decode_transaction(stream: io.BytesIO) -> (bool, Transaction):
    # ensure the at the magic is correctly configured
    magic = stream.read(1)[0]
    if magic != MAGIC:
        raise RuntimeError('Unable to parse transaction from stream, invalid magic')

    # extract the header bytes
    header = stream.read(2)

    # parse the header types
    version = (header[0] & 0xE0) >> 5
    charge_unit_flag = bool((header[0] & 0x08) >> 3)
    transfer_flag = bool((header[0] & 0x04) >> 2)
    multiple_transfers_flag = bool((header[0] & 0x02) >> 1)
    valid_from_flag = bool((header[0] & 0x01))

    contract_type = (header[1] & 0xC0) >> 6
    signature_count_minus1 = (header[1] & 0x3F)

    num_signatures = signature_count_minus1 + 1

    # ensure that the version is correct
    if version != VERSION:
        raise RuntimeError('Unable to parse transaction from stream, incompatible version')

    # Ready empty reserved byte
    stream.read(1)

    tx = Transaction()

    # decode the address from the thread
    tx.from_address = address.decode(stream)

    if transfer_flag:

        # determine the number of transfers that are present in the transaction
        if multiple_transfers_flag:
            transfer_count = integer.decode(stream) + 2
        else:
            transfer_count = 1

        for n in range(transfer_count):
            to = address.decode(stream)
            amount = integer.decode(stream)

            tx.add_transfer(to, amount)

    if valid_from_flag:
        tx.valid_from = integer.decode(stream)

    tx.valid_until = integer.decode(stream)
    tx.charge_rate = integer.decode(stream)

    assert not charge_unit_flag, "Currently the charge unit field is not supported"

    tx.charge_limit = integer.decode(stream)

    if contract_type != NO_CONTRACT:
        contract_header = int(stream.read(1)[0])

        wildcard = bool(contract_header & 0x80)

        shard_mask = BitVector()
        if not wildcard:
            extended_shard_mask_flag = bool(contract_header & 0x40)

            if not extended_shard_mask_flag:

                if contract_header & 0x10:
                    mask = 0xf
                    bit_size = 4
                else:
                    mask = 0x3
                    bit_size = 2

                # extract the shard mask from the header
                shard_mask = BitVector.from_bytes(bytes([contract_header & mask]), bit_size)

            else:
                bit_length = 1 << ((contract_header & 0x3F) + 3)
                byte_length = bit_length // 8

                assert (bit_length % 8) == 0  # this should be enforced as part of the spec

                # extract the mask from the next N bytes
                shard_mask = BitVector.from_bytes(stream.read(byte_length), bit_length)

        if contract_type == SMART_CONTRACT or contract_type == SYNERGETIC:
            contract_digest = address.decode(stream)
            contract_address = address.decode(stream)

            tx.target_contract(contract_digest, contract_address, shard_mask)

        elif contract_type == CHAIN_CODE:
            encoded_chain_code_name = bytearray.decode(stream)

            tx.target_chain_code(encoded_chain_code_name.decode('ascii'), shard_mask)

        else:
            # this is mostly a guard against a desync between this function and `_map_contract_mode`
            raise RuntimeError("Unhandled contract type")

        tx.action = bytearray.decode(stream).decode('ascii')
        tx.data = bytearray.decode(stream)

    # Read counter value
    tx.counter = struct.unpack('<Q', stream.read(8))[0]

    if signature_count_minus1 == 0x3F:
        additional_signatures = integer.decode(stream)
        num_signatures += additional_signatures

    # extract all the signing public keys from the stream
    public_keys = [identity.decode(stream) for _ in range(num_signatures)]

    # extract full copy of the payload
    payload_bytes = stream.getvalue()[:stream.tell()]

    verified = []
    for ident in public_keys:
        # for n in range(num_signatures):

        # extract the signature from the stream
        signature = bytearray.decode(stream)

        # verify if this signature is correct
        verified.append(ident.verify(payload_bytes, signature))

        # build a metadata object to store in the tx
        tx._signers[ident] = {
            'signature': signature,
            'verified': verified[-1],
        }

    return all(verified), tx
Beispiel #2
0
 def test_binary(self):
     bits = BitVector.from_bytes(bytes([0x1f]), 8)
     self.assertEqual('00011111', bits.as_binary())