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)
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
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)
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
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
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)
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