def create_p2pkh_transaction(private_key, unspents, outputs, custom_pushdata=False): public_key = private_key.public_key public_key_len = len(public_key).to_bytes(1, byteorder="little") scriptCode = private_key.scriptcode scriptCode_len = int_to_varint(len(scriptCode)) version = VERSION_1 lock_time = LOCK_TIME # sequence = SEQUENCE hash_type = HASH_TYPE input_count = int_to_unknown_bytes(len(unspents), byteorder="little") output_count = int_to_unknown_bytes(len(outputs), byteorder="little") output_block = construct_output_block(outputs, custom_pushdata=custom_pushdata) # Optimize for speed, not memory, by pre-computing values. inputs = [] for unspent in unspents: script = hex_to_bytes(unspent.script) script_len = int_to_unknown_bytes(len(script), byteorder="little") 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(script, script_len, 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): 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 Cash # 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_unknown_bytes(len(script_sig), byteorder="little") return bytes_to_hex(version + input_count + construct_input_block(inputs) + output_count + output_block + lock_time)
def _tx_out_boilerplate(tx_in, unlocking_script, output_block): tx_in.script = unlocking_script tx_in.script_len = int_to_unknown_bytes(len(unlocking_script), byteorder='little') input_count = int_to_unknown_bytes(1, byteorder='little') output_count = int_to_unknown_bytes(1, byteorder='little') return bytes_to_hex(VERSION_1 + input_count + construct_input_block([tx_in]) + output_count + output_block + LOCK_TIME)
def estimate_tx_fee(n_in, n_out, satoshis, compressed): if not satoshis: return 0 estimated_size = (n_in * (148 if compressed else 180) + len(int_to_unknown_bytes(n_in, byteorder='little')) + n_out * 34 + len(int_to_unknown_bytes(n_out, byteorder='little')) + 8) return estimated_size * satoshis
def estimate_tx_fee(n_in, n_out, satoshis, compressed): if not satoshis: return 0 estimated_size = (n_in * (148 if compressed else 180) + len(int_to_unknown_bytes(n_in, byteorder='little')) + n_out * 34 + len(int_to_unknown_bytes(n_out, byteorder='little')) + 8) estimated_fee = estimated_size * satoshis logging.debug('Estimated fee: {} satoshis for {} bytes'.format( estimated_fee, estimated_size)) return estimated_fee
def construct_output_block(outputs): output_block = b'' for data in outputs: dest, amount = data # Real recipient if amount: script = (OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash(dest) + OP_EQUALVERIFY + OP_CHECKSIG) output_block += amount.to_bytes(8, byteorder='little') # Blockchain storage else: script = (OP_RETURN + len(dest).to_bytes(1, byteorder='little') + dest) output_block += b'\x00\x00\x00\x00\x00\x00\x00\x00' output_block += int_to_unknown_bytes(len(script), byteorder='little') output_block += script return output_block
def arcula_locking_script(cold_storage_key: ecdsa.VerifyingKey, identifier: bytes) -> bytes: """Create an Arcula locking script for `cold_storage_key` and `identifier`.""" cold_storage_key = encode.verification_key_to_bytes_33(cold_storage_key) cold_storage_key = int_to_unknown_bytes( len(cold_storage_key)) + cold_storage_key identifier = int_to_unknown_bytes(len(identifier)) + identifier # Bitcoin OP codes OP_CHECKSIG = b'\xac' OP_DUP = b'\x76' OP_CAT = b'\x7e' OP_TOALTSTACK = b'\x6b' OP_FROMALTSTACK = b'\x6c' OP_CHECKDATASIGVERIFY = b'\xbb' return OP_DUP + OP_TOALTSTACK + identifier + OP_CAT + cold_storage_key + \ OP_CHECKDATASIGVERIFY + OP_FROMALTSTACK + OP_CHECKSIG
def estimate_tx_fee(n_in, n_out, satoshis, compressed, op_return_size=0): if not satoshis: return 0 estimated_size = ( 4 + n_in * (148 if compressed else 180) # version + len(int_to_unknown_bytes(n_in, byteorder="little")) + n_out * 34 # excluding op_return outputs, dealt with separately + len(int_to_unknown_bytes(n_out, byteorder="little")) + op_return_size # grand total size of op_return outputs(s) and related field(s) + 4 # time lock ) estimated_fee = estimated_size * satoshis logging.debug( f"Estimated fee: {estimated_fee} satoshis for {estimated_size} bytes") return estimated_fee
def _create_pkh2apkh_transaction(private_key, unspent, output): """Create a PublicKeyHash2ArculaPublicKeyHash transaction.""" script_code = private_key.scriptcode tx_in, hashed, output_block = _tx_in_boilerplate( unspent, output, script_code, _construct_arcula_output_block) signature = private_key.sign(hashed) + b'\x41' public_key = private_key.public_key public_key_len = len(public_key).to_bytes(1, byteorder='little') unlocking_script = ( int_to_unknown_bytes(len(signature), byteorder='little') + signature + public_key_len + public_key) return _tx_out_boilerplate(tx_in, unlocking_script, output_block)
def _construct_arcula_output_block(outputs): """Construct an Arcula output.""" assert len(outputs) == 1 output_block = b'' for data in outputs: (dest, i), amount = data assert amount script = arcula_locking_script(dest, i) output_block += amount.to_bytes(8, byteorder='little') output_block += int_to_unknown_bytes(len(script), byteorder='little') output_block += script return output_block
def _tx_in_boilerplate(unspent, output, script_code, construct_output_block_f): script = hex_to_bytes(unspent.script) script_len = int_to_unknown_bytes(len(script), byteorder='little') tx_id = hex_to_bytes(unspent.txid)[::-1] tx_index = unspent.txindex.to_bytes(4, byteorder='little') amount = unspent.amount.to_bytes(8, byteorder='little') tx_in = TxIn(script, script_len, tx_id, tx_index, amount) hash_previous_outputs = double_sha256(tx_id + tx_index) hash_sequence = double_sha256(SEQUENCE) output_block = construct_output_block_f([output]) hash_outputs = double_sha256(output_block) to_be_hashed = (VERSION_1 + hash_previous_outputs + hash_sequence + tx_in.txid + tx_in.txindex + int_to_varint(len(script_code)) + script_code + tx_in.amount + SEQUENCE + hash_outputs + LOCK_TIME + HASH_TYPE) hashed = sha256(to_be_hashed) # BIP-143: Used for Bitcoin Cash return tx_in, hashed, output_block
def construct_output_block(outputs): output_block = b'' for data in outputs: dest, amount = data # Real recipient if amount: script = (OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash(dest) + OP_EQUALVERIFY + OP_CHECKSIG) output_block += amount.to_bytes(8, byteorder='little') # Blockchain storage else: values_bytes = b'' split_length = 10 info = [ dest[i:i + split_length] for i in range(0, len(dest), split_length) ] for message in info: messagebytes = message.encode('utf-8') len_message_bytes = len(messagebytes).to_bytes( 1, byteorder='little') _values = (len_message_bytes + messagebytes).hex() values_bytes += bytes.fromhex(_values) data_bytes = values_bytes script = (OP_RETURN + data_bytes) output_block += b'\x00\x00\x00\x00\x00\x00\x00\x00' output_block += int_to_unknown_bytes(len(script), byteorder='little') output_block += script return output_block
def construct_output_block(outputs, custom_pushdata=False): output_block = b"" for data in outputs: dest, amount = data # Real recipient if amount: script = (OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash(dest) + OP_EQUALVERIFY + OP_CHECKSIG) output_block += amount.to_bytes(8, byteorder="little") # Blockchain storage else: if custom_pushdata is False: script = OP_RETURN + get_op_pushdata_code(dest) + dest output_block += b"\x00\x00\x00\x00\x00\x00\x00\x00" elif custom_pushdata is True: # manual control over number of bytes in each batch of pushdata if type(dest) != bytes: raise TypeError("custom pushdata must be of type: bytes") else: script = OP_RETURN + dest output_block += b"\x00\x00\x00\x00\x00\x00\x00\x00" # Script length in wiki is "Var_int" but there's a note of "modern BitcoinQT" using a more compact "CVarInt" # CVarInt is what I believe we have here - No changes made. If incorrect - only breaks if 220 byte limit is increased. output_block += int_to_unknown_bytes(len(script), byteorder="little") output_block += script return output_block
def b58decode(string): alphabet_index = BASE58_ALPHABET_INDEX num = 0 try: for char in string: num *= 58 num += alphabet_index[char] except KeyError: raise ValueError(f'"{char}" is an invalid base58 encoded ' "character.") from None bytestr = int_to_unknown_bytes(num) pad = 0 for char in string: if char == "1": pad += 1 else: break return b"\x00" * pad + bytestr
def test_zero(self): assert int_to_unknown_bytes(0) == b"\x00"
def test_little(self): assert int_to_unknown_bytes(BIG_INT, "little") == BYTES_LITTLE
def test_default(self): assert int_to_unknown_bytes(BIG_INT) == BYTES_BIG