diff options
author | glozow <gloriajzhao@gmail.com> | 2024-01-30 11:55:19 +0000 |
---|---|---|
committer | glozow <gloriajzhao@gmail.com> | 2024-01-30 12:00:47 +0000 |
commit | 78c06a38c41cba8e8c5463ee05e38aa7c12038e1 (patch) | |
tree | 2ed79286bc009048aaacba10c75e12175ba8caee /test | |
parent | 411ba32af21a56efa0a570b6aa8bf8f035410230 (diff) | |
parent | 55556a64a8e4e6238f990cf66295c3b9594c2c3d (diff) |
Merge bitcoin/bitcoin#29067: test: Treat msg_version.relay as unsigned, Remove `struct` packing in messages.py
55556a64a8e4e6238f990cf66295c3b9594c2c3d test: Remove struct import from messages.py (MarcoFalke)
fa3fa86ddaaa2246e873b7a3f19bc589a3f46c11 scripted-diff: test: Use int from_bytes and to_bytes over struct packing (MarcoFalke)
fafc0d68eef9c9381b1a3d8e160aad9eeb8540a7 test: Use int from_bytes and to_bytes over struct packing (MarcoFalke)
fa3886b7c69cbbe564478f30bb2c35e9e6b1cffa test: Treat msg_version.relay as unsigned (MarcoFalke)
Pull request description:
`struct` has many issues in messages.py:
* For unpacking, it requires to specify the length a second time, even when it is already clear from the `f.read(num_bytes)` context.
* For unpacking, it is designed to support a long format string and returning a tuple of many values. However, except for 3 instances in `messages.py`, usually only a single value is unpacked and all those cases require an `[0]` access.
* For packing and unpacking of a single value, the format string consists of characters that may be confusing and may need to be looked up in the documentation, as opposed to using easy to understand self-documenting code.
I presume the above issues lead to accidentally treat `msg_version.relay` as a "signed bool", which is fine, but confusing.
Fix all issues by using the built-in `int` helpers `to_bytes` and `from_bytes` via a scripted diff.
Review notes:
* `struct.unpack` throws an error if the number of bytes passed is incorrect. `int.from_bytes` doesn't know about "missing" bytes and treats an empty byte array as `int(0)`. "Extraneous" bytes should never happen, because all `read` calls are limited in this file. If it is important to keep this error behavior, a helper `int_from_stream(stream, num_bytes, bytes, byteorder, *, **kwargs)` can be added, which checks the number of bytes read from the stream.
* For `struct.pack` and `int.to_bytes` the error behavior is the same, although the error messages are not identical.
ACKs for top commit:
stickies-v:
ACK 55556a64a8e4e6238f990cf66295c3b9594c2c3d
theStack:
re-ACK 55556a64a8e4e6238f990cf66295c3b9594c2c3d
Tree-SHA512: 1cef8cdfd763fb424ed4b8be850a834b83fd0ef47fbea626a29784eb4f4832d44e42c4fe05b298b6070a933ef278b0222289a9955a97c86707e091e20bbb247a
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/test_framework/messages.py | 218 |
1 files changed, 107 insertions, 111 deletions
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index cc30424653..60641f66cf 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -25,7 +25,6 @@ from io import BytesIO import math import random import socket -import struct import time import unittest @@ -97,24 +96,24 @@ def hash256(s): def ser_compact_size(l): r = b"" if l < 253: - r = struct.pack("B", l) + r = l.to_bytes(1, "little") elif l < 0x10000: - r = struct.pack("<BH", 253, l) + r = (253).to_bytes(1, "little") + l.to_bytes(2, "little") elif l < 0x100000000: - r = struct.pack("<BI", 254, l) + r = (254).to_bytes(1, "little") + l.to_bytes(4, "little") else: - r = struct.pack("<BQ", 255, l) + r = (255).to_bytes(1, "little") + l.to_bytes(8, "little") return r def deser_compact_size(f): - nit = struct.unpack("<B", f.read(1))[0] + nit = int.from_bytes(f.read(1), "little") if nit == 253: - nit = struct.unpack("<H", f.read(2))[0] + nit = int.from_bytes(f.read(2), "little") elif nit == 254: - nit = struct.unpack("<I", f.read(4))[0] + nit = int.from_bytes(f.read(4), "little") elif nit == 255: - nit = struct.unpack("<Q", f.read(8))[0] + nit = int.from_bytes(f.read(8), "little") return nit @@ -279,13 +278,13 @@ class CAddress: """Deserialize from addrv1 format (pre-BIP155)""" if with_time: # VERSION messages serialize CAddress objects without time - self.time = struct.unpack("<I", f.read(4))[0] - self.nServices = struct.unpack("<Q", f.read(8))[0] + self.time = int.from_bytes(f.read(4), "little") + self.nServices = int.from_bytes(f.read(8), "little") # We only support IPv4 which means skip 12 bytes and read the next 4 as IPv4 address. f.read(12) self.net = self.NET_IPV4 self.ip = socket.inet_ntoa(f.read(4)) - self.port = struct.unpack(">H", f.read(2))[0] + self.port = int.from_bytes(f.read(2), "big") def serialize(self, *, with_time=True): """Serialize in addrv1 format (pre-BIP155)""" @@ -293,20 +292,20 @@ class CAddress: r = b"" if with_time: # VERSION messages serialize CAddress objects without time - r += struct.pack("<I", self.time) - r += struct.pack("<Q", self.nServices) + r += self.time.to_bytes(4, "little") + r += self.nServices.to_bytes(8, "little") r += b"\x00" * 10 + b"\xff" * 2 r += socket.inet_aton(self.ip) - r += struct.pack(">H", self.port) + r += self.port.to_bytes(2, "big") return r def deserialize_v2(self, f): """Deserialize from addrv2 format (BIP155)""" - self.time = struct.unpack("<I", f.read(4))[0] + self.time = int.from_bytes(f.read(4), "little") self.nServices = deser_compact_size(f) - self.net = struct.unpack("B", f.read(1))[0] + self.net = int.from_bytes(f.read(1), "little") assert self.net in self.ADDRV2_NET_NAME address_length = deser_compact_size(f) @@ -329,15 +328,15 @@ class CAddress: else: raise Exception(f"Address type not supported") - self.port = struct.unpack(">H", f.read(2))[0] + self.port = int.from_bytes(f.read(2), "big") def serialize_v2(self): """Serialize in addrv2 format (BIP155)""" assert self.net in self.ADDRV2_NET_NAME r = b"" - r += struct.pack("<I", self.time) + r += self.time.to_bytes(4, "little") r += ser_compact_size(self.nServices) - r += struct.pack("B", self.net) + r += self.net.to_bytes(1, "little") r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net]) if self.net == self.NET_IPV4: r += socket.inet_aton(self.ip) @@ -355,7 +354,7 @@ class CAddress: r += socket.inet_pton(socket.AF_INET6, self.ip) else: raise Exception(f"Address type not supported") - r += struct.pack(">H", self.port) + r += self.port.to_bytes(2, "big") return r def __repr__(self): @@ -382,12 +381,12 @@ class CInv: self.hash = h def deserialize(self, f): - self.type = struct.unpack("<I", f.read(4))[0] + self.type = int.from_bytes(f.read(4), "little") self.hash = deser_uint256(f) def serialize(self): r = b"" - r += struct.pack("<I", self.type) + r += self.type.to_bytes(4, "little") r += ser_uint256(self.hash) return r @@ -406,12 +405,12 @@ class CBlockLocator: self.vHave = [] def deserialize(self, f): - struct.unpack("<i", f.read(4))[0] # Ignore version field. + int.from_bytes(f.read(4), "little", signed=True) # Ignore version field. self.vHave = deser_uint256_vector(f) def serialize(self): r = b"" - r += struct.pack("<i", 0) # Bitcoin Core ignores version field. Set it to 0. + r += (0).to_bytes(4, "little", signed=True) # Bitcoin Core ignores the version field. Set it to 0. r += ser_uint256_vector(self.vHave) return r @@ -428,12 +427,12 @@ class COutPoint: def deserialize(self, f): self.hash = deser_uint256(f) - self.n = struct.unpack("<I", f.read(4))[0] + self.n = int.from_bytes(f.read(4), "little") def serialize(self): r = b"" r += ser_uint256(self.hash) - r += struct.pack("<I", self.n) + r += self.n.to_bytes(4, "little") return r def __repr__(self): @@ -455,13 +454,13 @@ class CTxIn: self.prevout = COutPoint() self.prevout.deserialize(f) self.scriptSig = deser_string(f) - self.nSequence = struct.unpack("<I", f.read(4))[0] + self.nSequence = int.from_bytes(f.read(4), "little") def serialize(self): r = b"" r += self.prevout.serialize() r += ser_string(self.scriptSig) - r += struct.pack("<I", self.nSequence) + r += self.nSequence.to_bytes(4, "little") return r def __repr__(self): @@ -478,12 +477,12 @@ class CTxOut: self.scriptPubKey = scriptPubKey def deserialize(self, f): - self.nValue = struct.unpack("<q", f.read(8))[0] + self.nValue = int.from_bytes(f.read(8), "little", signed=True) self.scriptPubKey = deser_string(f) def serialize(self): r = b"" - r += struct.pack("<q", self.nValue) + r += self.nValue.to_bytes(8, "little", signed=True) r += ser_string(self.scriptPubKey) return r @@ -582,11 +581,11 @@ class CTransaction: self.wit = copy.deepcopy(tx.wit) def deserialize(self, f): - self.nVersion = struct.unpack("<i", f.read(4))[0] + self.nVersion = int.from_bytes(f.read(4), "little", signed=True) self.vin = deser_vector(f, CTxIn) flags = 0 if len(self.vin) == 0: - flags = struct.unpack("<B", f.read(1))[0] + flags = int.from_bytes(f.read(1), "little") # Not sure why flags can't be zero, but this # matches the implementation in bitcoind if (flags != 0): @@ -599,16 +598,16 @@ class CTransaction: self.wit.deserialize(f) else: self.wit = CTxWitness() - self.nLockTime = struct.unpack("<I", f.read(4))[0] + self.nLockTime = int.from_bytes(f.read(4), "little") self.sha256 = None self.hash = None def serialize_without_witness(self): r = b"" - r += struct.pack("<i", self.nVersion) + r += self.nVersion.to_bytes(4, "little", signed=True) r += ser_vector(self.vin) r += ser_vector(self.vout) - r += struct.pack("<I", self.nLockTime) + r += self.nLockTime.to_bytes(4, "little") return r # Only serialize with witness when explicitly called for @@ -617,11 +616,11 @@ class CTransaction: if not self.wit.is_null(): flags |= 1 r = b"" - r += struct.pack("<i", self.nVersion) + r += self.nVersion.to_bytes(4, "little", signed=True) if flags: dummy = [] r += ser_vector(dummy) - r += struct.pack("<B", flags) + r += flags.to_bytes(1, "little") r += ser_vector(self.vin) r += ser_vector(self.vout) if flags & 1: @@ -631,7 +630,7 @@ class CTransaction: for _ in range(len(self.wit.vtxinwit), len(self.vin)): self.wit.vtxinwit.append(CTxInWitness()) r += self.wit.serialize() - r += struct.pack("<I", self.nLockTime) + r += self.nLockTime.to_bytes(4, "little") return r # Regular serialization is with witness -- must explicitly @@ -710,34 +709,34 @@ class CBlockHeader: self.hash = None def deserialize(self, f): - self.nVersion = struct.unpack("<i", f.read(4))[0] + self.nVersion = int.from_bytes(f.read(4), "little", signed=True) self.hashPrevBlock = deser_uint256(f) self.hashMerkleRoot = deser_uint256(f) - self.nTime = struct.unpack("<I", f.read(4))[0] - self.nBits = struct.unpack("<I", f.read(4))[0] - self.nNonce = struct.unpack("<I", f.read(4))[0] + self.nTime = int.from_bytes(f.read(4), "little") + self.nBits = int.from_bytes(f.read(4), "little") + self.nNonce = int.from_bytes(f.read(4), "little") self.sha256 = None self.hash = None def serialize(self): r = b"" - r += struct.pack("<i", self.nVersion) + r += self.nVersion.to_bytes(4, "little", signed=True) r += ser_uint256(self.hashPrevBlock) r += ser_uint256(self.hashMerkleRoot) - r += struct.pack("<I", self.nTime) - r += struct.pack("<I", self.nBits) - r += struct.pack("<I", self.nNonce) + r += self.nTime.to_bytes(4, "little") + r += self.nBits.to_bytes(4, "little") + r += self.nNonce.to_bytes(4, "little") return r def calc_sha256(self): if self.sha256 is None: r = b"" - r += struct.pack("<i", self.nVersion) + r += self.nVersion.to_bytes(4, "little", signed=True) r += ser_uint256(self.hashPrevBlock) r += ser_uint256(self.hashMerkleRoot) - r += struct.pack("<I", self.nTime) - r += struct.pack("<I", self.nBits) - r += struct.pack("<I", self.nNonce) + r += self.nTime.to_bytes(4, "little") + r += self.nBits.to_bytes(4, "little") + r += self.nNonce.to_bytes(4, "little") self.sha256 = uint256_from_str(hash256(r)) self.hash = hash256(r)[::-1].hex() @@ -881,12 +880,12 @@ class P2PHeaderAndShortIDs: def deserialize(self, f): self.header.deserialize(f) - self.nonce = struct.unpack("<Q", f.read(8))[0] + self.nonce = int.from_bytes(f.read(8), "little") self.shortids_length = deser_compact_size(f) for _ in range(self.shortids_length): # shortids are defined to be 6 bytes in the spec, so append # two zero bytes and read it in as an 8-byte number - self.shortids.append(struct.unpack("<Q", f.read(6) + b'\x00\x00')[0]) + self.shortids.append(int.from_bytes(f.read(6) + b'\x00\x00', "little")) self.prefilled_txn = deser_vector(f, PrefilledTransaction) self.prefilled_txn_length = len(self.prefilled_txn) @@ -894,11 +893,11 @@ class P2PHeaderAndShortIDs: def serialize(self, with_witness=False): r = b"" r += self.header.serialize() - r += struct.pack("<Q", self.nonce) + r += self.nonce.to_bytes(8, "little") r += ser_compact_size(self.shortids_length) for x in self.shortids: # We only want the first 6 bytes - r += struct.pack("<Q", x)[0:6] + r += x.to_bytes(8, "little")[0:6] if with_witness: r += ser_vector(self.prefilled_txn, "serialize_with_witness") else: @@ -963,10 +962,10 @@ class HeaderAndShortIDs: def get_siphash_keys(self): header_nonce = self.header.serialize() - header_nonce += struct.pack("<Q", self.nonce) + header_nonce += self.nonce.to_bytes(8, "little") hash_header_nonce_as_str = sha256(header_nonce) - key0 = struct.unpack("<Q", hash_header_nonce_as_str[0:8])[0] - key1 = struct.unpack("<Q", hash_header_nonce_as_str[8:16])[0] + key0 = int.from_bytes(hash_header_nonce_as_str[0:8], "little") + key1 = int.from_bytes(hash_header_nonce_as_str[8:16], "little") return [ key0, key1 ] # Version 2 compact blocks use wtxid in shortids (rather than txid) @@ -1064,7 +1063,7 @@ class CPartialMerkleTree: self.vBits = [] def deserialize(self, f): - self.nTransactions = struct.unpack("<i", f.read(4))[0] + self.nTransactions = int.from_bytes(f.read(4), "little", signed=True) self.vHash = deser_uint256_vector(f) vBytes = deser_string(f) self.vBits = [] @@ -1073,7 +1072,7 @@ class CPartialMerkleTree: def serialize(self): r = b"" - r += struct.pack("<i", self.nTransactions) + r += self.nTransactions.to_bytes(4, "little", signed=True) r += ser_uint256_vector(self.vHash) vBytesArray = bytearray([0x00] * ((len(self.vBits) + 7)//8)) for i in range(len(self.vBits)): @@ -1124,37 +1123,34 @@ class msg_version: self.relay = 0 def deserialize(self, f): - self.nVersion = struct.unpack("<i", f.read(4))[0] - self.nServices = struct.unpack("<Q", f.read(8))[0] - self.nTime = struct.unpack("<q", f.read(8))[0] + self.nVersion = int.from_bytes(f.read(4), "little", signed=True) + self.nServices = int.from_bytes(f.read(8), "little") + self.nTime = int.from_bytes(f.read(8), "little", signed=True) self.addrTo = CAddress() self.addrTo.deserialize(f, with_time=False) self.addrFrom = CAddress() self.addrFrom.deserialize(f, with_time=False) - self.nNonce = struct.unpack("<Q", f.read(8))[0] + self.nNonce = int.from_bytes(f.read(8), "little") self.strSubVer = deser_string(f).decode('utf-8') - self.nStartingHeight = struct.unpack("<i", f.read(4))[0] + self.nStartingHeight = int.from_bytes(f.read(4), "little", signed=True) # Relay field is optional for version 70001 onwards # But, unconditionally check it to match behaviour in bitcoind - try: - self.relay = struct.unpack("<b", f.read(1))[0] - except struct.error: - self.relay = 0 + self.relay = int.from_bytes(f.read(1), "little") # f.read(1) may return an empty b'' def serialize(self): r = b"" - r += struct.pack("<i", self.nVersion) - r += struct.pack("<Q", self.nServices) - r += struct.pack("<q", self.nTime) + r += self.nVersion.to_bytes(4, "little", signed=True) + r += self.nServices.to_bytes(8, "little") + r += self.nTime.to_bytes(8, "little", signed=True) r += self.addrTo.serialize(with_time=False) r += self.addrFrom.serialize(with_time=False) - r += struct.pack("<Q", self.nNonce) + r += self.nNonce.to_bytes(8, "little") r += ser_string(self.strSubVer.encode('utf-8')) - r += struct.pack("<i", self.nStartingHeight) - r += struct.pack("<b", self.relay) + r += self.nStartingHeight.to_bytes(4, "little", signed=True) + r += self.relay.to_bytes(1, "little") return r def __repr__(self): @@ -1400,11 +1396,11 @@ class msg_ping: self.nonce = nonce def deserialize(self, f): - self.nonce = struct.unpack("<Q", f.read(8))[0] + self.nonce = int.from_bytes(f.read(8), "little") def serialize(self): r = b"" - r += struct.pack("<Q", self.nonce) + r += self.nonce.to_bytes(8, "little") return r def __repr__(self): @@ -1419,11 +1415,11 @@ class msg_pong: self.nonce = nonce def deserialize(self, f): - self.nonce = struct.unpack("<Q", f.read(8))[0] + self.nonce = int.from_bytes(f.read(8), "little") def serialize(self): r = b"" - r += struct.pack("<Q", self.nonce) + r += self.nonce.to_bytes(8, "little") return r def __repr__(self): @@ -1564,16 +1560,16 @@ class msg_filterload: def deserialize(self, f): self.data = deser_string(f) - self.nHashFuncs = struct.unpack("<I", f.read(4))[0] - self.nTweak = struct.unpack("<I", f.read(4))[0] - self.nFlags = struct.unpack("<B", f.read(1))[0] + self.nHashFuncs = int.from_bytes(f.read(4), "little") + self.nTweak = int.from_bytes(f.read(4), "little") + self.nFlags = int.from_bytes(f.read(1), "little") def serialize(self): r = b"" r += ser_string(self.data) - r += struct.pack("<I", self.nHashFuncs) - r += struct.pack("<I", self.nTweak) - r += struct.pack("<B", self.nFlags) + r += self.nHashFuncs.to_bytes(4, "little") + r += self.nTweak.to_bytes(4, "little") + r += self.nFlags.to_bytes(1, "little") return r def __repr__(self): @@ -1625,11 +1621,11 @@ class msg_feefilter: self.feerate = feerate def deserialize(self, f): - self.feerate = struct.unpack("<Q", f.read(8))[0] + self.feerate = int.from_bytes(f.read(8), "little") def serialize(self): r = b"" - r += struct.pack("<Q", self.feerate) + r += self.feerate.to_bytes(8, "little") return r def __repr__(self): @@ -1645,13 +1641,13 @@ class msg_sendcmpct: self.version = version def deserialize(self, f): - self.announce = struct.unpack("<?", f.read(1))[0] - self.version = struct.unpack("<Q", f.read(8))[0] + self.announce = bool(int.from_bytes(f.read(1), "little")) + self.version = int.from_bytes(f.read(8), "little") def serialize(self): r = b"" - r += struct.pack("<?", self.announce) - r += struct.pack("<Q", self.version) + r += int(self.announce).to_bytes(1, "little") + r += self.version.to_bytes(8, "little") return r def __repr__(self): @@ -1734,14 +1730,14 @@ class msg_getcfilters: self.stop_hash = stop_hash def deserialize(self, f): - self.filter_type = struct.unpack("<B", f.read(1))[0] - self.start_height = struct.unpack("<I", f.read(4))[0] + self.filter_type = int.from_bytes(f.read(1), "little") + self.start_height = int.from_bytes(f.read(4), "little") self.stop_hash = deser_uint256(f) def serialize(self): r = b"" - r += struct.pack("<B", self.filter_type) - r += struct.pack("<I", self.start_height) + r += self.filter_type.to_bytes(1, "little") + r += self.start_height.to_bytes(4, "little") r += ser_uint256(self.stop_hash) return r @@ -1759,13 +1755,13 @@ class msg_cfilter: self.filter_data = filter_data def deserialize(self, f): - self.filter_type = struct.unpack("<B", f.read(1))[0] + self.filter_type = int.from_bytes(f.read(1), "little") self.block_hash = deser_uint256(f) self.filter_data = deser_string(f) def serialize(self): r = b"" - r += struct.pack("<B", self.filter_type) + r += self.filter_type.to_bytes(1, "little") r += ser_uint256(self.block_hash) r += ser_string(self.filter_data) return r @@ -1784,14 +1780,14 @@ class msg_getcfheaders: self.stop_hash = stop_hash def deserialize(self, f): - self.filter_type = struct.unpack("<B", f.read(1))[0] - self.start_height = struct.unpack("<I", f.read(4))[0] + self.filter_type = int.from_bytes(f.read(1), "little") + self.start_height = int.from_bytes(f.read(4), "little") self.stop_hash = deser_uint256(f) def serialize(self): r = b"" - r += struct.pack("<B", self.filter_type) - r += struct.pack("<I", self.start_height) + r += self.filter_type.to_bytes(1, "little") + r += self.start_height.to_bytes(4, "little") r += ser_uint256(self.stop_hash) return r @@ -1810,14 +1806,14 @@ class msg_cfheaders: self.hashes = hashes def deserialize(self, f): - self.filter_type = struct.unpack("<B", f.read(1))[0] + self.filter_type = int.from_bytes(f.read(1), "little") self.stop_hash = deser_uint256(f) self.prev_header = deser_uint256(f) self.hashes = deser_uint256_vector(f) def serialize(self): r = b"" - r += struct.pack("<B", self.filter_type) + r += self.filter_type.to_bytes(1, "little") r += ser_uint256(self.stop_hash) r += ser_uint256(self.prev_header) r += ser_uint256_vector(self.hashes) @@ -1836,12 +1832,12 @@ class msg_getcfcheckpt: self.stop_hash = stop_hash def deserialize(self, f): - self.filter_type = struct.unpack("<B", f.read(1))[0] + self.filter_type = int.from_bytes(f.read(1), "little") self.stop_hash = deser_uint256(f) def serialize(self): r = b"" - r += struct.pack("<B", self.filter_type) + r += self.filter_type.to_bytes(1, "little") r += ser_uint256(self.stop_hash) return r @@ -1859,13 +1855,13 @@ class msg_cfcheckpt: self.headers = headers def deserialize(self, f): - self.filter_type = struct.unpack("<B", f.read(1))[0] + self.filter_type = int.from_bytes(f.read(1), "little") self.stop_hash = deser_uint256(f) self.headers = deser_uint256_vector(f) def serialize(self): r = b"" - r += struct.pack("<B", self.filter_type) + r += self.filter_type.to_bytes(1, "little") r += ser_uint256(self.stop_hash) r += ser_uint256_vector(self.headers) return r @@ -1883,13 +1879,13 @@ class msg_sendtxrcncl: self.salt = 0 def deserialize(self, f): - self.version = struct.unpack("<I", f.read(4))[0] - self.salt = struct.unpack("<Q", f.read(8))[0] + self.version = int.from_bytes(f.read(4), "little") + self.salt = int.from_bytes(f.read(8), "little") def serialize(self): r = b"" - r += struct.pack("<I", self.version) - r += struct.pack("<Q", self.salt) + r += self.version.to_bytes(4, "little") + r += self.salt.to_bytes(8, "little") return r def __repr__(self): |