|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import json |
| 4 | +import time |
| 5 | +from bitcoinutils.setup import setup |
| 6 | +from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput |
| 7 | +from bitcoinutils.script import Script |
| 8 | +from bitcoinutils.keys import P2trAddress |
| 9 | +from bitcoinutils.utils import to_satoshis |
| 10 | +from bitcoinutils.block import Block, BlockHeader |
| 11 | +import hashlib |
| 12 | + |
| 13 | + |
| 14 | +tx_details = "" |
| 15 | +with open( |
| 16 | + "./examples/mempool/test_tx.json", |
| 17 | + "r", |
| 18 | +) as file: |
| 19 | + tx_details = json.load(file) |
| 20 | + |
| 21 | +setup("mainnet") |
| 22 | + |
| 23 | + |
| 24 | +from_addr = "0000000000000000000000000000000000000000000000000000000000000000" |
| 25 | +to_addr = "bc1pvh7n6s375348q5zjfrde38nnq2lmhhtyaeqe8hv6t8mf398smeyqnug47s" |
| 26 | +to_addr = P2trAddress(to_addr) |
| 27 | + |
| 28 | + |
| 29 | +def calculate_wtxid(tx_hex): |
| 30 | + tx_binary = bytes.fromhex(tx_hex) |
| 31 | + hash_once = hashlib.sha256(tx_binary).digest() |
| 32 | + hash_twice = hashlib.sha256(hash_once).digest() |
| 33 | + return hash_twice[::-1].hex() |
| 34 | + |
| 35 | + |
| 36 | +def calculate_merkle_root(txid_list): |
| 37 | + """ |
| 38 | + Calculates merkel root by hashing pairwise txids till only 1 is left. |
| 39 | +
|
| 40 | + Args: |
| 41 | + txid_list: List of the transaction ids in the block |
| 42 | +
|
| 43 | + Returns: |
| 44 | + (bytes): The merkle root of the block. |
| 45 | + """ |
| 46 | + if len(txid_list) == 1: |
| 47 | + return bytes.fromhex(txid_list[0]) |
| 48 | + |
| 49 | + # Convert each txid from big-endian hex to little-endian bytes. |
| 50 | + current_layer = [bytes.fromhex(txid)[::-1] for txid in txid_list] |
| 51 | + |
| 52 | + while len(current_layer) > 1: |
| 53 | + next_layer = [] |
| 54 | + for i in range(0, len(current_layer), 2): |
| 55 | + # If there's an odd number, duplicate the last one. |
| 56 | + if i + 1 < len(current_layer): |
| 57 | + pair = current_layer[i] + current_layer[i + 1] |
| 58 | + else: |
| 59 | + pair = current_layer[i] + current_layer[i] |
| 60 | + # Perform double SHA-256 on the concatenated pair. |
| 61 | + next_layer.append(hashlib.sha256(hashlib.sha256(pair).digest()).digest()) |
| 62 | + current_layer = next_layer |
| 63 | + |
| 64 | + # Reverse back to big-endian and return the bytes. |
| 65 | + return current_layer[0][::-1] |
| 66 | + |
| 67 | + |
| 68 | +def calculate_witness_root_hash(wtxid_list): |
| 69 | + """ |
| 70 | + Calculates witness root hash by hashing pairwise wtxids till only 1 is left. |
| 71 | +
|
| 72 | + Args: |
| 73 | + wtxid_list: List of the witness transaction ids in the block |
| 74 | +
|
| 75 | + Returns: |
| 76 | + (bytes): The witness root hash of the block. |
| 77 | + """ |
| 78 | + if len(wtxid_list) == 1: |
| 79 | + # For a single txid, return its bytes (big-endian) representation. |
| 80 | + return bytes.fromhex(wtxid_list[0]) |
| 81 | + |
| 82 | + # Convert each txid from big-endian hex to little-endian bytes. |
| 83 | + current_layer = [bytes.fromhex(wtxid)[::-1] for wtxid in wtxid_list] |
| 84 | + |
| 85 | + while len(current_layer) > 1: |
| 86 | + next_layer = [] |
| 87 | + for i in range(0, len(current_layer), 2): |
| 88 | + # If there's an odd number, duplicate the last one. |
| 89 | + if i + 1 < len(current_layer): |
| 90 | + pair = current_layer[i] + current_layer[i + 1] |
| 91 | + else: |
| 92 | + pair = current_layer[i] + current_layer[i] |
| 93 | + # Perform double SHA-256 on the concatenated pair. |
| 94 | + next_layer.append(hashlib.sha256(hashlib.sha256(pair).digest()).digest()) |
| 95 | + current_layer = next_layer |
| 96 | + |
| 97 | + return current_layer[0] |
| 98 | + |
| 99 | + |
| 100 | +def calculate_witness_commitment(witness_root_hash, witness_reserved_value): |
| 101 | + """ |
| 102 | + Calculates the witness commitment by performing a double SHA-256 |
| 103 | + on the concatenation of the witness_root_hash and witness_reserved_value. |
| 104 | +
|
| 105 | + Args: |
| 106 | + witness_root_hash(hex): witness root hash of the block |
| 107 | + witness_reserved_value(hex): the reserved value stored in the witness. |
| 108 | +
|
| 109 | + Returns: |
| 110 | + (hex): hex of the witness commitment |
| 111 | +
|
| 112 | + """ |
| 113 | + # Convert both hex strings to bytes and concatenate them |
| 114 | + combined = bytes.fromhex(witness_root_hash + witness_reserved_value) |
| 115 | + |
| 116 | + # Perform double SHA256 hashing |
| 117 | + hash1 = hashlib.sha256(combined).digest() |
| 118 | + hash2 = hashlib.sha256(hash1).digest() |
| 119 | + return hash2.hex() |
| 120 | + |
| 121 | + |
| 122 | +def create_block(coinbase_tx, tx1): |
| 123 | + # Note: Here I have for simplicity taken just the coinbase tx and another transaction (the test_tx the mempool), |
| 124 | + # more generally we just take the list of tx. |
| 125 | + |
| 126 | + txid_list = [coinbase_tx.get_txid(), tx1.get_txid()] |
| 127 | + merkle_root = calculate_merkle_root(txid_list) |
| 128 | + prev_block_hash = bytes.fromhex( |
| 129 | + "0000000000000000000000000000000000000000000000000000000000000000" |
| 130 | + ) |
| 131 | + |
| 132 | + version = "20000000" |
| 133 | + version = int.from_bytes(bytes.fromhex(version), byteorder="little") |
| 134 | + timestamp = int(time.time()) |
| 135 | + # resource: learn me a bitcoin's target to bit converter. |
| 136 | + bits = "1f00ffff" |
| 137 | + bits = int.from_bytes(bytes.fromhex(bits), byteorder="big") |
| 138 | + |
| 139 | + nonce = 0 |
| 140 | + |
| 141 | + block_header = BlockHeader( |
| 142 | + version, |
| 143 | + previous_block_hash=prev_block_hash, |
| 144 | + merkle_root=merkle_root, |
| 145 | + timestamp=timestamp, |
| 146 | + target_bits=bits, |
| 147 | + nonce=nonce, |
| 148 | + ) |
| 149 | + magic = bytes.fromhex("f9beb4d9") |
| 150 | + block_size = None |
| 151 | + transaction_count = len([coinbase_tx, tx1]) |
| 152 | + block = Block( |
| 153 | + magic=magic, |
| 154 | + block_size=block_size, |
| 155 | + header=block_header, |
| 156 | + transactions=[coinbase_tx, tx1], |
| 157 | + transaction_count=transaction_count, |
| 158 | + ) |
| 159 | + return block |
| 160 | + |
| 161 | + |
| 162 | +def mine_block(block_header_bytes, target_hex): |
| 163 | + """ |
| 164 | + Mine a block by iterating through nonce values. |
| 165 | +
|
| 166 | + Args: |
| 167 | + block_header_bytes (bytes): The 80-byte block header with a placeholder nonce. |
| 168 | + target_hex (str): The difficulty target as a hex string (256-bit number). |
| 169 | +
|
| 170 | + Returns: |
| 171 | + (int, bytes): The nonce value and the corresponding block header hash that meets the target. |
| 172 | + """ |
| 173 | + target_int = int(target_hex, 16) |
| 174 | + print("Target (int):", target_int) |
| 175 | + |
| 176 | + for nonce in range(2**32): |
| 177 | + # Replace the last 4 bytes of the header with the current nonce in little-endian format. |
| 178 | + header_with_nonce = block_header_bytes[:-4] + nonce.to_bytes( |
| 179 | + 4, byteorder="little" |
| 180 | + ) |
| 181 | + |
| 182 | + hash_once = hashlib.sha256(header_with_nonce).digest() |
| 183 | + hash_twice = hashlib.sha256(hash_once).digest() |
| 184 | + |
| 185 | + # Bitcoin displays the block hash in big-endian order. |
| 186 | + block_hash = hash_twice[::-1] |
| 187 | + block_hash_int = int.from_bytes(block_hash, byteorder="big") |
| 188 | + |
| 189 | + if block_hash_int < target_int: |
| 190 | + print("Success! Nonce found:", nonce) |
| 191 | + return nonce, block_hash |
| 192 | + |
| 193 | + # Optional: print progress every so often |
| 194 | + if nonce % 1000000 == 0: |
| 195 | + print("Tried nonce:", nonce) |
| 196 | + |
| 197 | + return None, None |
| 198 | + |
| 199 | + |
| 200 | +def main(): |
| 201 | + # create a coinbase transaction |
| 202 | + witness_reserved_value = ( |
| 203 | + "0000000000000000000000000000000000000000000000000000000000000000" |
| 204 | + ) |
| 205 | + witness_root_hash = "" |
| 206 | + |
| 207 | + # The commitment is constructed as: |
| 208 | + # commitment = "6a24aa21a9ed" + doublesha256(witness_root_hash, witness_reserved_value) |
| 209 | + tx1 = Transaction() |
| 210 | + tx1 = tx1.from_raw(tx_details["hex"]) |
| 211 | + txinp = TxInput( |
| 212 | + txid=from_addr, |
| 213 | + txout_index=0, |
| 214 | + script_sig=Script([witness_reserved_value]), |
| 215 | + ) |
| 216 | + witness_stack = [witness_reserved_value] |
| 217 | + |
| 218 | + wtxid = calculate_wtxid(tx_details["hex"]) |
| 219 | + print("Wtx id :", wtxid) |
| 220 | + coinbase_wtxid = "0" * 64 |
| 221 | + |
| 222 | + witness_root_hash = calculate_witness_root_hash([coinbase_wtxid, wtxid]) |
| 223 | + witness_root_hash = witness_root_hash.hex() |
| 224 | + print("Witness root hash ", witness_root_hash) |
| 225 | + |
| 226 | + # Calculating the witness commitment hash (double SHA256 of witness_root_hash and witness_reserved_value) |
| 227 | + witness_commitment_hash = calculate_witness_commitment( |
| 228 | + witness_root_hash, witness_reserved_value |
| 229 | + ) |
| 230 | + |
| 231 | + # Prepending the witness commitment header |
| 232 | + commitment = "6a24aa21a9ed" + witness_commitment_hash |
| 233 | + |
| 234 | + print("Witness Commitment :", commitment) |
| 235 | + # note: to_addr defined at the top. Taken from the first transaction |
| 236 | + txout1 = TxOutput(to_satoshis(0.001), to_addr.to_script_pub_key()) |
| 237 | + witness_commitment_script = Script([]) |
| 238 | + witness_commitment_script = witness_commitment_script.from_raw(commitment) |
| 239 | + print("witness commitment script : ", witness_commitment_script) |
| 240 | + txout2 = TxOutput(to_satoshis(0), witness_commitment_script) |
| 241 | + coinbase_tx = Transaction( |
| 242 | + [txinp], |
| 243 | + [txout1, txout2], |
| 244 | + has_segwit=True, |
| 245 | + witnesses=[TxWitnessInput(witness_stack)], |
| 246 | + ) |
| 247 | + # example difficulty target |
| 248 | + difficulty_target = ( |
| 249 | + "0000ffff00000000000000000000000000000000000000000000000000000000" |
| 250 | + ) |
| 251 | + # Creating a block that includes the coinbase transaction and tx1. |
| 252 | + block = create_block(coinbase_tx, tx1) |
| 253 | + print("block header: ", block.header) |
| 254 | + # getting the block header bytes |
| 255 | + |
| 256 | + serialized_header = block.header.serialize_header() |
| 257 | + print("Serialized header : ", serialized_header) |
| 258 | + |
| 259 | + # mining the block |
| 260 | + nonce, mined_hash = mine_block(serialized_header, difficulty_target) |
| 261 | + print("NONCE: ", nonce) |
| 262 | + serialized_header = serialized_header[:-4] + nonce.to_bytes(4, byteorder="little") |
| 263 | + |
| 264 | + print("hex of serialized header ", serialized_header.hex()) |
| 265 | + print("coinbase transaction :", coinbase_tx.to_hex()) |
| 266 | + print("Block: ", block) |
| 267 | + |
| 268 | + |
| 269 | +if __name__ == "__main__": |
| 270 | + main() |
0 commit comments