def test_little_endian_to_int(self): h = bytes.fromhex("99c3980000000000") want = 10011545 self.assertEqual(little_endian_to_int(h), want) h = bytes.fromhex("a135ef0100000000") want = 32454049 self.assertEqual(little_endian_to_int(h), want)
def parse(cls, stream=None, raw=None): if stream and raw: raise ValueError("provide exactly one of stream/raw, not both") if raw is None: if stream is None: raise ValueError("provide one of stream/raw") raw = read_varstr(stream) length = len(raw) s = BytesIO(raw) # initialize the commands array commands = [] # initialize the number of bytes we've read to 0 count = 0 # loop until we've read length bytes while count < length: # get the current byte current = s.read(1) # increment the bytes we've read count += 1 # convert the current byte to an integer current_byte = current[0] # if the current byte is between 1 and 75 inclusive if current_byte >= 1 and current_byte <= 75: # we have an command set n to be the current byte n = current_byte # add the next n bytes as an command commands.append(s.read(n)) # increase the count by n count += n elif current_byte == 76: # op_pushdata1 data_length = little_endian_to_int(s.read(1)) commands.append(s.read(data_length)) count += data_length + 1 elif current_byte == 77: # op_pushdata2 data_length = little_endian_to_int(s.read(2)) commands.append(s.read(data_length)) count += data_length + 2 elif current_byte == 78: # op_pushdata4 data_length = little_endian_to_int(s.read(4)) commands.append(s.read(data_length)) count += data_length + 4 else: # we have an op code. set the current byte to op_code op_code = current_byte # add the op_code to the list of commands commands.append(op_code) obj = cls(commands) if count != length: # Would throw error, but Bitcoin Core will read the number of bytes that are there (not what was promised) print(f"mismatch between length and consumed bytes {count} vs {length}") obj.raw = raw return obj
def parse_segwit(cls, s, network="mainnet"): """Takes a byte stream and parses a segwit transaction""" # s.read(n) will return n bytes # version has 4 bytes, little-endian, interpret as int version = little_endian_to_int(s.read(4)) # next two bytes need to be 0x00 and 0x01, otherwise raise RuntimeError marker = s.read(2) if marker != b"\x00\x01": raise RuntimeError(f"Not a segwit transaction {marker}") # num_inputs is a varint, use read_varint(s) num_inputs = read_varint(s) # each input needs parsing, create inputs array inputs = [] # parse each input and add to the inputs array for _ in range(num_inputs): inputs.append(TxIn.parse(s)) # num_outputs is a varint, use read_varint(s) num_outputs = read_varint(s) # each output needs parsing, create outputs array outputs = [] # parse each output and add to the outputs array for _ in range(num_outputs): outputs.append(TxOut.parse(s)) # there is a witness for each input for tx_in in inputs: tx_in.witness = Witness.parse(s) # locktime is 4 bytes, little-endian locktime = Locktime.parse(s) # return an instance of the class (cls(...)) return cls(version, inputs, outputs, locktime, network=network, segwit=True)
def parse_legacy(cls, s, network="mainnet"): """Takes a byte stream and parses a legacy transaction""" # s.read(n) will return n bytes # version has 4 bytes, little-endian, interpret as int version = little_endian_to_int(s.read(4)) # num_inputs is a varint, use read_varint(s) num_inputs = read_varint(s) # each input needs parsing inputs = [] for _ in range(num_inputs): inputs.append(TxIn.parse(s)) # num_outputs is a varint, use read_varint(s) num_outputs = read_varint(s) # each output needs parsing outputs = [] for _ in range(num_outputs): outputs.append(TxOut.parse(s)) # locktime is 4 bytes, little-endian locktime = Locktime.parse(s) # return an instance of the class (cls(...)) return cls(version, inputs, outputs, locktime, network=network, segwit=False)
def check_pow(self): """Returns whether this block satisfies proof of work""" # get the hash256 of the serialization of this block h256 = hash256(self.serialize()) # interpret this hash as a little-endian number proof = little_endian_to_int(h256) # return whether this integer is less than the target return proof < self.target()
def parse(cls, s): """Takes a byte stream and parses the tx_output at the start return a TxOut object """ # s.read(n) will return n bytes # amount is 8 bytes, little endian, interpret as int amount = little_endian_to_int(s.read(8)) # script_pubkey is a variable field (length followed by the data) # you can use Script.parse to get the actual script script_pubkey = ScriptPubKey.parse(s) # return an instance of the class (cls(...)) return cls(amount, script_pubkey)
def parse_header(cls, stream=None, hex=None): """Takes a byte stream and parses block headers. Returns a Block object""" if hex: if stream: raise RuntimeError("One of stream or hex should be defined") stream = BytesIO(bytes.fromhex(hex)) # stream.read(n) will read n bytes from the stream # version - 4 bytes, little endian, interpret as int version = little_endian_to_int(stream.read(4)) # prev_block - 32 bytes, little endian (use [::-1] to reverse) prev_block = stream.read(32)[::-1] # merkle_root - 32 bytes, little endian (use [::-1] to reverse) merkle_root = stream.read(32)[::-1] # timestamp - 4 bytes, little endian, interpret as int timestamp = little_endian_to_int(stream.read(4)) # bits - 4 bytes bits = stream.read(4) # nonce - 4 bytes nonce = stream.read(4) # initialize class return cls(version, prev_block, merkle_root, timestamp, bits, nonce)
def coinbase_height(self): """Returns the height of the block this coinbase transaction is in Returns None if this transaction is not a coinbase transaction """ # if this is NOT a coinbase transaction, return None if not self.is_coinbase(): return None # grab the first input script_sig = self.tx_ins[0].script_sig # get the next length bytes command = script_sig.commands[0] # convert the command from little endian to int return little_endian_to_int(command)
def test_parse(self): hex_merkle_block = "00000020df3b053dc46f162a9b00c7f0d5124e2676d47bbe7c5d0793a500000000000000ef445fef2ed495c275892206ca533e7411907971013ab83e3b47bd0d692d14d4dc7c835b67d8001ac157e670bf0d00000aba412a0d1480e370173072c9562becffe87aa661c1e4a6dbc305d38ec5dc088a7cf92e6458aca7b32edae818f9c2c98c37e06bf72ae0ce80649a38655ee1e27d34d9421d940b16732f24b94023e9d572a7f9ab8023434a4feb532d2adfc8c2c2158785d1bd04eb99df2e86c54bc13e139862897217400def5d72c280222c4cbaee7261831e1550dbb8fa82853e9fe506fc5fda3f7b919d8fe74b6282f92763cef8e625f977af7c8619c32a369b832bc2d051ecd9c73c51e76370ceabd4f25097c256597fa898d404ed53425de608ac6bfe426f6e2bb457f1c554866eb69dcb8d6bf6f880e9a59b3cd053e6c7060eeacaacf4dac6697dac20e4bd3f38a2ea2543d1ab7953e3430790a9f81e1c67f5b58c825acf46bd02848384eebe9af917274cdfbb1a28a5d58a23a17977def0de10d644258d9c54f886d47d293a411cb6226103b55635" mb = MerkleBlock.parse(BytesIO(bytes.fromhex(hex_merkle_block))) version = 0x20000000 self.assertEqual(mb.header.version, version) merkle_root_hex = ( "ef445fef2ed495c275892206ca533e7411907971013ab83e3b47bd0d692d14d4") merkle_root = bytes.fromhex(merkle_root_hex)[::-1] self.assertEqual(mb.header.merkle_root, merkle_root) prev_block_hex = ( "df3b053dc46f162a9b00c7f0d5124e2676d47bbe7c5d0793a500000000000000") prev_block = bytes.fromhex(prev_block_hex)[::-1] self.assertEqual(mb.header.prev_block, prev_block) timestamp = little_endian_to_int(bytes.fromhex("dc7c835b")) self.assertEqual(mb.header.timestamp, timestamp) bits = bytes.fromhex("67d8001a") self.assertEqual(mb.header.bits, bits) nonce = bytes.fromhex("c157e670") self.assertEqual(mb.header.nonce, nonce) total = little_endian_to_int(bytes.fromhex("bf0d0000")) self.assertEqual(mb.total, total) hex_hashes = [ "ba412a0d1480e370173072c9562becffe87aa661c1e4a6dbc305d38ec5dc088a", "7cf92e6458aca7b32edae818f9c2c98c37e06bf72ae0ce80649a38655ee1e27d", "34d9421d940b16732f24b94023e9d572a7f9ab8023434a4feb532d2adfc8c2c2", "158785d1bd04eb99df2e86c54bc13e139862897217400def5d72c280222c4cba", "ee7261831e1550dbb8fa82853e9fe506fc5fda3f7b919d8fe74b6282f92763ce", "f8e625f977af7c8619c32a369b832bc2d051ecd9c73c51e76370ceabd4f25097", "c256597fa898d404ed53425de608ac6bfe426f6e2bb457f1c554866eb69dcb8d", "6bf6f880e9a59b3cd053e6c7060eeacaacf4dac6697dac20e4bd3f38a2ea2543", "d1ab7953e3430790a9f81e1c67f5b58c825acf46bd02848384eebe9af917274c", "dfbb1a28a5d58a23a17977def0de10d644258d9c54f886d47d293a411cb62261", ] hashes = [bytes.fromhex(h)[::-1] for h in hex_hashes] self.assertEqual(mb.hashes, hashes) flags = bytes.fromhex("b55635") self.assertEqual(mb.flags, flags)
def parse(cls, s): """Takes a byte stream and parses the tx_input at the start return a TxIn object """ # s.read(n) will return n bytes # prev_tx is 32 bytes, little endian prev_tx = s.read(32)[::-1] # prev_index is 4 bytes, little endian, interpret as int prev_index = little_endian_to_int(s.read(4)) # script_sig is a variable field (length followed by the data) # you can use Script.parse to get the actual script script_sig = Script.parse(s) # sequence is 4 bytes, little-endian, interpret as int sequence = Sequence.parse(s) # return an instance of the class (cls(...)) return cls(prev_tx, prev_index, script_sig, sequence)
def parse(cls, s): """Takes a byte stream and parses a merkle block. Returns a Merkle Block object""" # s.read(n) will read n bytes from the stream # header - use Block.parse_header with the stream header = Block.parse_header(s) # total number of transactions (4 bytes, little endian) total = little_endian_to_int(s.read(4)) # number of hashes is a varint num_txs = read_varint(s) # initialize the hashes array hashes = [] # loop through the number of hashes times for _ in range(num_txs): # each hash is 32 bytes, little endian hashes.append(s.read(32)[::-1]) # get the length of the flags field as a varint flags_length = read_varint(s) # read the flags field flags = s.read(flags_length) # initialize class return cls(header, total, hashes, flags)
def parse(cls, s, network="mainnet"): """Takes a stream and creates a NetworkEnvelope""" # check the network magic magic = s.read(4) if magic == b"": raise RuntimeError("Connection reset!") expected_magic = MAGIC[network] if magic != expected_magic: raise RuntimeError("magic is not right {} vs {}".format( magic.hex(), expected_magic.hex())) # command 12 bytes, strip the trailing 0's using .strip(b'\x00') command = s.read(12).strip(b"\x00") # payload length 4 bytes, little endian payload_length = little_endian_to_int(s.read(4)) # checksum 4 bytes, first four of hash256 of payload checksum = s.read(4) # payload is of length payload_length payload = s.read(payload_length) # verify checksum calculated_checksum = hash256(payload)[:4] if calculated_checksum != checksum: raise RuntimeError("checksum does not match") return cls(command, payload, network=network)
def parse(cls, s): return cls(little_endian_to_int(s.read(4)))