Beispiel #1
0
    def parse(cls: Type["Sig"],
              data: BinaryData,
              check_validity: bool = True) -> "Sig":
        """Return a Sig by parsing binary data.

        Deserialize a strict ASN.1 DER representation of an ECDSA signature.
        """

        stream = bytesio_from_binarydata(data)
        ec = secp256k1

        # [0x30] [data-size][0x02][r-size][r][0x02][s-size][s]
        marker = stream.read(1)
        if marker != _DER_SIG_MARKER:
            err_msg = f"invalid compound header: {marker.hex()}"
            err_msg += f", instead of DER sequence tag {_DER_SIG_MARKER.hex()}"
            raise BTClibValueError(err_msg)

        # [data-size][0x02][r-size][r][0x02][s-size][s]
        sig_data = var_bytes.parse(stream, forbid_zero_size=True)

        # [0x02][r-size][r][0x02][s-size][s]
        sig_data_substream = bytesio_from_binarydata(sig_data)
        r = _deserialize_scalar(sig_data_substream)
        s = _deserialize_scalar(sig_data_substream)

        # to prevent malleability
        # the sig_data_substream must have been consumed entirely
        if sig_data_substream.read(1) != b"":
            err_msg = "invalid DER sequence length"
            raise BTClibValueError(err_msg)

        return cls(r, s, ec, check_validity)
    def parse(cls: Type[_Sig], data: BinaryData, check_validity: bool = True) -> _Sig:

        stream = bytesio_from_binarydata(data)
        ec = secp256k1
        r = int.from_bytes(stream.read(ec.p_size), byteorder="big", signed=False)
        s = int.from_bytes(stream.read(ec.n_size), byteorder="big", signed=False)
        return cls(r, s, ec, check_validity)
Beispiel #3
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     len_addresses = varint.decode(stream)
     addresses = []
     for x in range(len_addresses):
         addresses.append(NetworkAddress.deserialize(stream))
     return cls(addresses=addresses)
Beispiel #4
0
    def parse(cls: Type[_Tx],
              data: BinaryData,
              check_validity: bool = True) -> _Tx:
        "Return a Tx by parsing binary data."

        stream = bytesio_from_binarydata(data)

        # version is a signed int (int32_t, not uint32_t)
        version = int.from_bytes(stream.read(4),
                                 byteorder="little",
                                 signed=True)

        segwit = stream.read(2) == _SEGWIT_MARKER
        if not segwit:
            # Change stream position: seek to byte offset relative to position
            stream.seek(-2, SEEK_CUR)  # current position

        n = var_int.parse(stream)
        vin = [TxIn.parse(stream) for _ in range(n)]

        n = var_int.parse(stream)
        vout = [TxOut.parse(stream) for _ in range(n)]

        if segwit:
            for tx_in in vin:
                tx_in.script_witness = Witness.parse(stream, check_validity)

        lock_time = int.from_bytes(stream.read(4),
                                   byteorder="little",
                                   signed=False)

        return cls(version, lock_time, vin, vout, check_validity)
Beispiel #5
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     header = BlockHeader.deserialize(stream)
     index = varint.decode(stream)
     status = BlockStatus.from_bytes(stream.read(1), "little")
     downloaded = bool(int.from_bytes(stream.read(1), "little"))
     return cls(header, index, status, downloaded)
Beispiel #6
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     message = stream.read(varint.decode(stream)).decode()
     code = RejectCode.from_bytes(stream.read(1), "little")
     reason = stream.read(varint.decode(stream)).decode()
     data = stream.read(32).hex()
     return cls(message, code, reason, data)
Beispiel #7
0
    def parse(cls: Type["BlockHeader"],
              data: BinaryData,
              check_validity: bool = True) -> "BlockHeader":
        "Return a BlockHeader by parsing 80 bytes from binary data."

        stream = bytesio_from_binarydata(data)

        # version is a signed int (int32_t, not uint32_t)
        version = int.from_bytes(stream.read(4),
                                 byteorder="little",
                                 signed=True)
        previous_block_hash = stream.read(_HF_LEN)[::-1]
        merkle_root_ = stream.read(_HF_LEN)[::-1]
        t = int.from_bytes(stream.read(4), byteorder="little", signed=False)
        time = datetime.fromtimestamp(t, timezone.utc)
        bits = stream.read(4)[::-1]
        nonce = int.from_bytes(stream.read(4),
                               byteorder="little",
                               signed=False)

        return cls(
            version,
            previous_block_hash,
            merkle_root_,
            time,
            bits,
            nonce,
            check_validity,
        )
Beispiel #8
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     blockhash = stream.read(32)[::-1].hex()
     num_indexes = varint.decode(stream)
     indexes = []
     for x in range(num_indexes):
         indexes.append(varint.decode(stream))
     return cls(blockhash=blockhash, indexes=indexes)
Beispiel #9
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     blockhash = stream.read(32)[::-1].hex()
     num_transactions = varint.decode(stream)
     transactions = []
     for x in range(num_transactions):
         transactions.append(TxData.deserialize(stream))
     return cls(blockhash=blockhash, transactions=transactions)
Beispiel #10
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     headers_num = varint.decode(stream)
     headers = []
     for x in range(headers_num):
         header = BlockHeader.deserialize(stream)
         stream.read(1)
         headers.append(header)
     return cls(headers)
Beispiel #11
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     inventory_length = varint.decode(stream)
     inventory = []
     for x in range(inventory_length):
         item_type = int.from_bytes(stream.read(4), "little")
         item_hash = stream.read(32)[::-1].hex()
         inventory.append((item_type, item_hash))
     return cls(inventory)
Beispiel #12
0
 def parse(cls: Type["TxOut"],
           data: BinaryData,
           check_validity: bool = True) -> "TxOut":
     stream = bytesio_from_binarydata(data)
     value = int.from_bytes(stream.read(8),
                            byteorder="little",
                            signed=False)
     script = var_bytes.parse(stream)
     return cls(value, ScriptPubKey(script, "mainnet"), check_validity)
Beispiel #13
0
    def parse(cls: Type["Witness"],
              data: BinaryData,
              check_validity: bool = True) -> "Witness":
        "Return a Witness by parsing binary data."

        data = bytesio_from_binarydata(data)
        n = var_int.parse(data)
        stack = [var_bytes.parse(data) for _ in range(n)]
        return cls(stack, check_validity)
Beispiel #14
0
    def parse(cls: Type["OutPoint"],
              data: BinaryData,
              check_validity: bool = True) -> "OutPoint":
        "Return an OutPoint from the first 36 bytes of the provided data."

        data = bytesio_from_binarydata(data)
        tx_id = data.read(32)[::-1]
        vout = int.from_bytes(data.read(4), "little", signed=False)

        return cls(tx_id, vout, check_validity)
Beispiel #15
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     version = int.from_bytes(stream.read(4), "little")
     block_hashes = []
     for x in range(varint.decode(stream)):
         block_hash = stream.read(32)[::-1].hex()
         block_hashes.append(block_hash)
     hash_stop = stream.read(32)[::-1].hex()
     return cls(version=version,
                block_hashes=block_hashes,
                hash_stop=hash_stop)
Beispiel #16
0
 def deserialize(cls, data, version_msg=False):
     stream = bytesio_from_binarydata(data)
     if not version_msg:
         time = int.from_bytes(stream.read(4), "little")
     else:
         time = 0
     services = int.from_bytes(stream.read(8), "little")
     a = stream.read(16)
     ip = IPv6Address(a)
     port = int.from_bytes(stream.read(2), "big")
     return cls(time=time, services=services, ip=ip, port=port)
Beispiel #17
0
    def parse(cls: Type[_Block],
              data: BinaryData,
              check_validity: bool = True) -> _Block:
        "Return a Block by parsing binary data."

        stream = bytesio_from_binarydata(data)
        header = BlockHeader.parse(stream)
        n = var_int.parse(stream)
        # TODO: is a block required to have a coinbase tx?
        transactions = [Tx.parse(stream) for _ in range(n)]

        return cls(header, transactions, check_validity)
Beispiel #18
0
    def parse(cls: Type["TxIn"],
              data: BinaryData,
              check_validity: bool = True) -> "TxIn":

        stream = bytesio_from_binarydata(data)
        prev_out = OutPoint.parse(stream)
        script_sig = var_bytes.parse(stream)
        sequence = int.from_bytes(stream.read(4),
                                  byteorder="little",
                                  signed=False)

        return cls(prev_out, script_sig, sequence, Witness(), check_validity)
Beispiel #19
0
    def parse(cls: Type["Psbt"],
              psbt_bin: Octets,
              check_validity: bool = True) -> "Psbt":
        "Return a Psbt by parsing binary data."

        # FIXME: psbt_bin should be BinaryData
        # stream = bytesio_from_binarydata(psbt_bin)
        # and the deserialization should happen reading the stream
        # not slicing bytes

        tx = Tx(check_validity=False)
        version = 0
        hd_key_paths: Dict[Octets, BIP32KeyOrigin] = {}
        unknown: Dict[Octets, Octets] = {}

        # psbt_bin = bytes_from_octets(psbt_bin)
        stream = bytesio_from_binarydata(psbt_bin)

        if stream.read(4) != PSBT_MAGIC_BYTES:
            raise BTClibValueError("malformed psbt: missing magic bytes")
        if stream.read(1) != PSBT_SEPARATOR:
            raise BTClibValueError("malformed psbt: missing separator")

        global_map, stream = deserialize_map(stream)
        for k, v in global_map.items():
            if k[:1] == PSBT_GLOBAL_UNSIGNED_TX:
                tx = deserialize_tx(k, v, "global unsigned tx", False)
            elif k[:1] == PSBT_GLOBAL_VERSION:
                version = deserialize_int(k, v, "global version")
            elif k[:1] == PSBT_GLOBAL_XPUB:
                hd_key_paths[k[1:]] = BIP32KeyOrigin.parse(v)
            else:  # unknown
                unknown[k] = v

        inputs: List[PsbtIn] = []
        for _ in tx.vin:
            input_map, stream = deserialize_map(stream)
            inputs.append(PsbtIn.parse(input_map))

        outputs: List[PsbtOut] = []
        for _ in tx.vout:
            output_map, stream = deserialize_map(stream)
            outputs.append(PsbtOut.parse(output_map))

        return cls(
            tx,
            inputs,
            outputs,
            version,
            hd_key_paths,
            unknown,
            check_validity,
        )
def parse(stream: BinaryData, forbid_zero_size: bool = False) -> bytes:
    """Return the variable-length octets read from a stream."""

    stream = bytesio_from_binarydata(stream)
    i = var_int.parse(stream)
    if forbid_zero_size and i == 0:
        raise BTClibRuntimeError("zero size")

    result = stream.read(i)
    if len(result) != i:
        raise BTClibRuntimeError("not enough binary data")
    return result
Beispiel #21
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     hash = stream.read(32).hex()
     to_add = []
     for x in range(varint.decode(stream)):
         out_point = OutPoint.deserialize(stream)
         tx_out = TxOut.deserialize(stream)
         to_add.append([out_point, tx_out])
     to_remove = []
     for x in range(varint.decode(stream)):
         out_point = OutPoint.deserialize(stream)
         to_remove.append(out_point)
     return cls(hash, to_add, to_remove)
Beispiel #22
0
def parse(stream: BinaryData,
          exit_on_op_success: bool = False) -> List[Command]:

    s = bytesio_from_binarydata(stream)
    r: List[Command] = []  # initialize the result list

    while True:

        t = s.read(1)  # get one byte
        if not t:
            break
        i = t[0]  # convert the first byte to an integer
        if 0 < i <= 78:  # push
            data_length = i  # 0 < i < 76 -> 1-byte-data-length | data
            if 75 < i < 79:
                # i == 76 -> OP_PUSHDATA1 | 1-byte-data-length | data
                # i == 77 -> OP_PUSHDATA2 | 2-byte-data-length | data
                x = i - 75
                if i == 78:  # OP_PUSHDATA4 | 4-byte-data-length | data
                    x = 4
                y = s.read(x)
                if len(y) != x:
                    raise BTClibValueError(
                        "Not enough data for pushdata length")
                data_length = int.from_bytes(y, byteorder="little")
                if data_length > 520:
                    raise BTClibValueError(
                        f"Invalid pushdata length: {data_length}")
            data = s.read(data_length)
            if len(data) != data_length:
                raise BTClibValueError("Not enough data for pushdata")
            command = data.hex().upper()
        elif i in OP_CODE_NAME_FROM_INT:  # OP_CODE
            command = OP_CODE_NAME_FROM_INT[i]
            # Opcodes which take integers and bools off the stack require
            # that they be no more than 4 bytes long.
            # If this is the case, parse that command as int
            # t = r[-1]
            # if isinstance(t, bytes) and len(t) <= 4:
            #    r[-1] = decode_num(t)
        else:  # OP_SUCCESSx
            command = f"OP_SUCCESS{i}"
            if exit_on_op_success:
                return ["OP_SUCCESS"]

        r.append(command)

    return r
Beispiel #23
0
def deserialize_map(data: BinaryData) -> Tuple[Dict[bytes, bytes], BytesIO]:
    stream = bytesio_from_binarydata(data)
    if (
        len(stream.getbuffer()) == stream.tell()
    ):  # we are at the end of the stream buffer
        raise BTClibValueError("malformed psbt: at least a map is missing")
    partial_map: Dict[bytes, bytes] = {}
    while True:
        if stream.read(1)[0] == 0:
            return partial_map, stream
        stream.seek(-1, 1)  # reset stream position
        key = stream.read(var_int.parse(stream))
        value = stream.read(var_int.parse(stream))
        if key in partial_map:
            raise BTClibValueError(f"duplicated key in psbt map: 0x{key.hex()}")
        partial_map[key] = value
Beispiel #24
0
 def parse(
     cls: Type["TxOut"],
     data: BinaryData,
     check_validity: bool = True,
 ) -> "TxOut":
     stream = bytesio_from_binarydata(data)
     value = int.from_bytes(stream.read(8),
                            byteorder="little",
                            signed=False)
     script = var_bytes.parse(stream)
     return cls(
         value,
         ScriptPubKey(script, "mainnet", check_validity=False
                      ),  # https://github.com/bitcoin/bitcoin/issues/320
         check_validity,
     )
Beispiel #25
0
def parse(stream: BinaryData,
          exit_on_op_success: bool = False) -> List[Command]:

    s = bytesio_from_binarydata(stream)
    r: List[Command] = []  # initialize the result list

    while True:

        t = s.read(1)  # get one byte
        if not t:
            break
        i = t[0]  # convert the byte to an integer
        if 0 < i <= 78:  # push
            if 0 < i < 76:  # 1-byte-data-length | data
                data_length = i
            if 76 <= i <= 78:
                if i == 76:  # OP_PUSHDATA1 | 1-byte-data-length | data
                    x = 1
                elif i == 77:  # OP_PUSHDATA2 | 2-byte-data-length | data
                    x = 2
                elif i == 78:  # OP_PUSHDATA4 | 4-byte-data-length | data
                    x = 4
                y = s.read(x)
                if len(y) != x:
                    raise BTClibValueError("Invalid pushdata length")
                data_length = int.from_bytes(y, byteorder="little")
            if data_length > 520:
                raise BTClibValueError("Invalid pushdata length")
            data = s.read(data_length)
            if len(data) != data_length:
                raise BTClibValueError("Invalid pushdata length")
            # if <= 0xFFFFFFFF, parse it as integer
            if i < 6 and decode_num(data) <= 0xFFFFFFFF:
                new_op_code: Command = decode_num(data)
            else:
                new_op_code = data.hex().upper()
        elif i in OP_CODE_NAMES.keys():  # OP_CODE
            new_op_code = OP_CODE_NAMES[i]
        else:  # OP_SUCCESSx
            new_op_code = f"OP_SUCCESS{i}"
            if exit_on_op_success:
                return ["OP_SUCCESS"]

        r.append(new_op_code)

    return r
Beispiel #26
0
def parse(stream: BinaryData) -> int:
    """Return the variable-length integer read from a stream."""

    stream = bytesio_from_binarydata(stream)

    i = stream.read(1)[0]
    if i < 0xFD:
        # one byte integer
        return i
    if i == 0xFD:
        # 0xfd marks the next two bytes as the number
        return int.from_bytes(stream.read(2), byteorder="little", signed=False)
    if i == 0xFE:
        # 0xfe marks the next four bytes as the number
        return int.from_bytes(stream.read(4), byteorder="little", signed=False)
    # 0xff marks the next eight bytes as the number
    return int.from_bytes(stream.read(8), byteorder="little", signed=False)
Beispiel #27
0
 def deserialize(cls, data):
     stream = bytesio_from_binarydata(data)
     header = BlockHeader.deserialize(stream)
     nonce = int.from_bytes(stream.read(8), "little")
     short_ids = []
     short_ids_length = varint.decode(stream)
     for x in range(short_ids_length):
         short_ids.append(stream.read(6)[::-1].hex())
     prefilled_tx_list = []
     prefilled_tx_num = varint.decode(stream)
     for x in range(prefilled_tx_num):
         tx_index = varint.decode(stream)
         tx = Tx.deserialize(stream)
         prefilled_tx_list.append((tx_index, tx))
     return cls(
         header=header,
         nonce=nonce,
         short_ids=short_ids,
         prefilled_tx_list=prefilled_tx_list,
     )
Beispiel #28
0
    def parse(
        cls: Type["BIP32KeyData"], xkey_bin: BinaryData, check_validity: bool = True
    ) -> "BIP32KeyData":
        "Return a BIP32KeyData by parsing 73 bytes from binary data."

        stream = bytesio_from_binarydata(xkey_bin)
        xkey_bin = stream.read(_REQUIRED_LENGHT)

        if check_validity and len(xkey_bin) != _REQUIRED_LENGHT:
            err_msg = f"invalid decoded length: {len(xkey_bin)}"
            err_msg += f" instead of {_REQUIRED_LENGHT}"
            raise BTClibValueError(err_msg)

        return cls(
            version=xkey_bin[0:4],
            depth=xkey_bin[4],
            parent_fingerprint=xkey_bin[5:9],
            index=int.from_bytes(xkey_bin[9:13], byteorder="big", signed=False),
            chain_code=xkey_bin[13:45],
            key=xkey_bin[45:78],
            check_validity=check_validity,
        )
Beispiel #29
0
    def parse(cls: Type["Sig"],
              data: BinaryData,
              check_validity: bool = True) -> "Sig":

        stream = bytesio_from_binarydata(data)
        sig_bin = stream.read(_REQUIRED_LENGHT)

        if check_validity and len(sig_bin) != _REQUIRED_LENGHT:
            err_msg = f"invalid decoded length: {len(sig_bin)}"
            err_msg += f" instead of {_REQUIRED_LENGHT}"
            raise BTClibValueError(err_msg)

        rf = sig_bin[0]
        ec = secp256k1
        n_size = ec.n_size
        r = int.from_bytes(sig_bin[1:1 + n_size], "big", signed=False)
        s = int.from_bytes(sig_bin[1 + n_size:1 + 2 * n_size],
                           "big",
                           signed=False)
        dsa_sig = dsa.Sig(r, s, ec, check_validity=False)

        return cls(rf, dsa_sig, check_validity)
Beispiel #30
0
def parse(stream: BinaryData) -> List[Command]:

    s = bytesio_from_binarydata(stream)
    # initialize the result list
    r: List[Command] = []
    while True:
        # get one byte
        t = s.read(1)
        if not t:
            break
        # convert the byte to an integer
        i = t[0]
        if 0 < i < 76:
            # 1-byte-data-length | data
            data = s.read(i)
            # if <= 0xFFFFFFFF, parse it as integer
            as_int = decode_num(data)
            r.append(as_int if i < 6 and as_int <= 0xFFFFFFFF else data.hex().upper())
        elif i == 76:
            # OP_PUSHDATA1 | 1-byte-data-length | data
            data_length = int.from_bytes(s.read(1), byteorder="little", signed=False)
            data = s.read(data_length)
            r.append(data.hex().upper())
        elif i == 77:
            # OP_PUSHDATA2 | 2-byte-data-length | data
            data_length = int.from_bytes(s.read(2), byteorder="little", signed=False)
            data = s.read(data_length)
            r.append(data.hex().upper())
        elif i == 78:
            # OP_PUSHDATA4 | 4-byte-data-length | data
            data_length = int.from_bytes(s.read(4), byteorder="little", signed=False)
            data = s.read(data_length)
            r.append(data.hex().upper())
        else:
            # OP_CODE
            r.append(OP_CODE_NAMES[i])

    return r