Example #1
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)
Example #2
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
Example #3
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)
Example #4
0
def deserialize_map(psbt_bin: bytes) -> Tuple[Dict[bytes, bytes], bytes]:
    if len(psbt_bin) == 0:
        raise BTClibValueError("malformed psbt: at least a map is missing")
    partial_map: Dict[bytes, bytes] = {}
    while True:
        if psbt_bin[0] == 0:
            psbt_bin = psbt_bin[1:]
            return partial_map, psbt_bin
        key_len = var_int.parse(psbt_bin)
        psbt_bin = psbt_bin[len(var_int.serialize(key_len)):]
        key = psbt_bin[:key_len]
        psbt_bin = psbt_bin[key_len:]
        value_len = var_int.parse(psbt_bin)
        psbt_bin = psbt_bin[len(var_int.serialize(value_len)):]
        value = psbt_bin[:value_len]
        psbt_bin = psbt_bin[value_len:]
        if key in partial_map:
            raise BTClibValueError(
                f"duplicated key in psbt map: 0x{key.hex()}")
        partial_map[key] = value
Example #5
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)
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
Example #7
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) in bitcoin_core
        # However there are at least two transactions:
        # 35e79ee733fad376e76d16d1f10088273c2f4c2eaba1374a837378a88e530005
        # c659729a7fea5071361c2c1a68551ca2bf77679b27086cc415adeeb03852e369
        # where the version number is negative if it is considered as a signed
        # integer. As such in btclib the version is an UNSIGNED integer.
        # This has been discussed in: https://github.com/bitcoin/bitcoin/pull/16525
        version = int.from_bytes(stream.read(4),
                                 byteorder="little",
                                 signed=False)

        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)
Example #8
0
def test_var_int_conversion() -> None:

    int_ = -1
    with pytest.raises(BTClibValueError, match="negative integer: "):
        var_int.serialize(int_)

    int_ = 0x00
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 1
    assert var_int.parse(bytes_) == int_

    int_ += 1
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 1
    assert var_int.parse(bytes_) == int_

    int_ = 0xFC
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 1
    assert var_int.parse(bytes_) == int_

    int_ += 1
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 3
    assert var_int.parse(bytes_) == int_

    int_ = 0xFFFF
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 3
    assert var_int.parse(bytes_) == int_

    int_ += 1
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 5
    assert var_int.parse(bytes_) == int_

    int_ = 0xFFFFFFFF
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 5
    assert var_int.parse(bytes_) == int_

    int_ += 1
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 9
    assert var_int.parse(bytes_) == int_

    int_ = 0xFFFFFFFFFFFFFFFF
    bytes_ = var_int.serialize(int_)
    assert len(bytes_) == 9
    assert var_int.parse(bytes_) == int_

    int_ += 1
    with pytest.raises(BTClibValueError,
                       match="integer too big for var_int encoding: "):
        var_int.serialize(int_)

    assert var_int.parse("6a") == 106
    assert var_int.parse("fd2602") == 550
    assert var_int.parse("fe703a0f00") == 998000