def transaction_digest(tx_ins: list, tx_outs: list, lock_time: bytes = LOCK_TIME, sighash: int = SIGHASH_ALL) -> list: """Returns the digest of unsigned transaction according to SIGHASH""" # BIP-143 https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki # 1. nVersion of the transaction (4-byte little endian) # 2. hashPrevouts (32-byte hash) # 3. hashSequence (32-byte hash) # 4. outpoint (32-byte hash + 4-byte little endian) # 5. scriptCode of the input (serialized as scripts inside CTxOuts) # 6. value of the output spent by this input (8-byte little endian) # 7. nSequence of the input (4-byte little endian) # 8. hashOutputs (32-byte hash) # 9. nLocktime of the transaction (4-byte little endian) # 10. sighash type of the signature (4-byte little endian) if sighash == SIGHASH_ALL: hash_prevouts = double_sha256(b''.join([tx_in.txid + tx_in.index for tx_in in tx_ins])) hash_sequence = double_sha256(b''.join([tx_in.sequence for tx_in in tx_ins])) hash_outputs = double_sha256(serialize_outputs(tx_outs)) digests = [] for tx_in in tx_ins: digests.append( VERSION + hash_prevouts + hash_sequence + tx_in.txid + tx_in.index + tx_in.locking_script_len + tx_in.locking_script + tx_in.satoshi + tx_in.sequence + hash_outputs + lock_time + sighash.to_bytes(4, byteorder='little') ) return digests raise ValueError(f'Unsupported SIGHASH value {sighash}')
def create_p2pkh_transaction(utxosets, outputs, custom_pushdata=False): version = VERSION_1 lock_time = LOCK_TIME # sequence = SEQUENCE hash_type = HASH_TYPE unspents = [Unspent.from_dict(utxo) for utxo in utxosets] input_count = int_to_varint(len(unspents)) output_count = int_to_varint(len(outputs)) output_block = construct_output_block(outputs, custom_pushdata=custom_pushdata) # Optimize for speed, not memory, by pre-computing values. inputs = [] for unspent in unspents: txid = hex_to_bytes(unspent.txid)[::-1] txindex = unspent.txindex.to_bytes(4, byteorder='little') amount = unspent.amount.to_bytes(8, byteorder='little') inputs.append(TxIn('', 0, txid, txindex, amount)) hashPrevouts = double_sha256(b''.join([i.txid + i.txindex for i in inputs])) hashSequence = double_sha256(b''.join([SEQUENCE for i in inputs])) hashOutputs = double_sha256(output_block) # scriptCode_len is part of the script. for i, txin in enumerate(inputs): private_key = bsv(wif=utxosets[i]['PrivateKey']) public_key = bytes.fromhex(private_key.public_key) public_key_len = len(public_key).to_bytes(1, byteorder='little') scriptCode = (OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash(private_key.address) + OP_EQUALVERIFY + OP_CHECKSIG) scriptCode_len = int_to_varint(len(scriptCode)) to_be_hashed = (version + hashPrevouts + hashSequence + txin.txid + txin.txindex + scriptCode_len + scriptCode + txin.amount + SEQUENCE + hashOutputs + lock_time + hash_type) hashed = sha256(to_be_hashed) # BIP-143: Used for Bitcoin SV # signature = private_key.sign(hashed) + b'\x01' signature = private_key.sign(hashed) + b'\x41' script_sig = (len(signature).to_bytes(1, byteorder='little') + signature + public_key_len + public_key) inputs[i].script = script_sig inputs[i].script_len = int_to_varint(len(script_sig)) return bytes_to_hex(version + input_count + construct_input_block(inputs) + output_count + output_block + lock_time)
def b58c_encode(data, version=b'\x00'): """Converts bytes data into a base 58 check string. WTF is Base 58 Check encoding? See here: https://en.bitcoin.it/wiki/Base58Check_encoding """ data = version + data data_without_zeros = data.lstrip(b'\x00') nb_zeros = len(data) - len(data_without_zeros) double_hash = double_sha256(data) data = data + double_hash[0:4] number = bytes_to_int(data) b58 = int_to_base(number, B58) return (b'1' * nb_zeros) + b58
# Verify the ECDSA signature of a signed transaction # 4674da699de44c9c5d182870207ba89e5ccf395e5101dab6b0900bbf2f3b16cb # tx_inputs = inputs[0:1] tx_outputs = [TxOut(address='1JDZRGf5fPjGTpqLNwjHFFZnagcZbwDsxw', satoshi=800)] tx_digest = transaction_digest(tx_inputs, tx_outputs)[0] serialized_sig = unhexlify('304402207e2c6eb8c4b20e251a71c580373a2836e209c50726e5f8b0f4f59f8af00eee1a022019ae1690e2eb4455add6ca5b86695d65d3261d914bc1d7abb40b188c7f46c9a5') sig = deserialize_signature(serialized_sig) print(verify_signature(pub_key, tx_digest, sig)) # # Sign an unsigned transaction then broadcast # c04bbd007ad3987f9b2ea8534175b5e436e43d64471bf32139b5851adf9f477e # serialized_pub_key = serialize_public_key(pub_key) tx_inputs = inputs[1:] tx_outputs = [TxOut(address='18CgRLx9hFZqDZv75J5kED7ANnDriwvpi1', satoshi=1700)] tx_digests = transaction_digest(tx_inputs, tx_outputs) for i in range(len(tx_digests)): tx_digest = tx_digests[i] sig = sign(priv_key, tx_digest) serialized_sig = serialize_signature(sig) # unlocking_script = LEN + der + sighash + LEN + public_key tx_inputs[i].unlocking_script = bytes([len(serialized_sig) + 1]) + serialized_sig + bytes([SIGHASH_ALL, len(serialized_pub_key)]) + serialized_pub_key print(hexlify(tx_inputs[i].unlocking_script)) tx_inputs[i].unlocking_script_len = int_to_varint(len(tx_inputs[i].unlocking_script)) print(hexlify(tx_inputs[i].unlocking_script_len)) raw = serialize_transaction(tx_inputs, tx_outputs) print(hexlify(raw)) tx_id = double_sha256(raw)[::-1] print(hexlify(tx_id))
def calc_txid(tx_hex): return bytes_to_hex(double_sha256(hex_to_bytes(tx_hex))[::-1])
def generate_sighash_single_rawtx(utxosets, changeaddress, authrized_amount): unspents = [Unspent.from_dict(utxo) for utxo in utxosets] version = VERSION_1 lock_time = LOCK_TIME #sequence = SEQUENCE input_count = int_to_varint(len(unspents)) inputs = [] total_input_amount = 0 for unspent in unspents: txid = hex_to_bytes(unspent.txid)[::-1] txindex = unspent.txindex.to_bytes(4, byteorder='little') amount = unspent.amount.to_bytes(8, byteorder='little') inputs.append(TxIn('', 0, txid, txindex, amount)) total_input_amount += unspent.amount #satoshi output_count = int_to_varint(1) output_block = b'' output_script = (OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash(changeaddress) + OP_EQUALVERIFY + OP_CHECKSIG) output_block += (total_input_amount - authrized_amount).to_bytes( 8, byteorder='little') #satoshi output_block += int_to_varint(len(output_script)) output_block += output_script hashPrevouts = double_sha256(b''.join([i.txid + i.txindex for i in inputs])) hashSequence = bytes.fromhex( '0000000000000000000000000000000000000000000000000000000000000000') # scriptCode_len is part of the script. for i, txin in enumerate(inputs): if i == 0: hashOutputs = double_sha256(output_block) hash_type = 0x43.to_bytes(4, byteorder='little') #sighash single else: hashOutputs = bytes.fromhex( '0000000000000000000000000000000000000000000000000000000000000000' ) hash_type = 0x42.to_bytes(4, byteorder='little') #sighash none private_key = bsv(utxosets[i]['PrivateKey']) public_key = bytes.fromhex(private_key.public_key) public_key_len = len(public_key).to_bytes(1, byteorder='little') scriptCode = (OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash(private_key.address) + OP_EQUALVERIFY + OP_CHECKSIG) scriptCode_len = int_to_varint(len(scriptCode)) to_be_hashed = (version + hashPrevouts + hashSequence + txin.txid + txin.txindex + scriptCode_len + scriptCode + txin.amount + SEQUENCE + hashOutputs + lock_time + hash_type) hashed = sha256(to_be_hashed) # BIP-143: Used for Bitcoin SV # signature = private_key.sign(hashed) + b'\x01' sighash ALL ; single b'\x03' ,NONE b'\x02' if i == 0: signature = private_key.sign(hashed) + b'\x43' else: signature = private_key.sign(hashed) + b'\x42' script_sig = (len(signature).to_bytes(1, byteorder='little') + signature + public_key_len + public_key) inputs[i].script = script_sig inputs[i].script_len = int_to_varint(len(script_sig)) return { "version": bytes_to_hex(version), "input": bytes_to_hex(input_count + construct_input_block(inputs)), "output": bytes_to_hex(output_block), "lock_time": bytes_to_hex(lock_time) }
def hash_to_int(message: bytes) -> int: """Calculate the bitcoin double-sha256 hash of the message, return as an integer""" h = double_sha256(message) return int.from_bytes(h, byteorder='big')