From e12b699dc145af7749edeb4013dc8f0e2fd002e5 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Mon, 6 Jun 2016 15:24:25 -0400 Subject: [PATCH] WIP: move to a state-transition API that doesn't force the state logic to interface with the blockchain or blockchain services. At the same time, deprecate support for pybitcoin in favor of primitives that now live in virtualchain. --- blockstack/lib/operations/announce.py | 75 ++++++++++----- blockstack/lib/operations/nameimport.py | 66 +++++++++---- .../lib/operations/namespacepreorder.py | 63 ++++++++++--- blockstack/lib/operations/namespaceready.py | 29 +++++- blockstack/lib/operations/namespacereveal.py | 57 +++++++++--- blockstack/lib/operations/preorder.py | 84 +++++++++++------ blockstack/lib/operations/register.py | 92 +++++++++++++------ blockstack/lib/operations/revoke.py | 73 ++++++++++----- blockstack/lib/operations/transfer.py | 88 +++++++++++++----- blockstack/lib/operations/update.py | 78 +++++++++++----- 10 files changed, 503 insertions(+), 202 deletions(-) diff --git a/blockstack/lib/operations/announce.py b/blockstack/lib/operations/announce.py index 0c509b703..cea580c6c 100644 --- a/blockstack/lib/operations/announce.py +++ b/blockstack/lib/operations/announce.py @@ -21,20 +21,24 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, make_op_return_tx, make_op_return_outputs, \ - make_op_return_script, broadcast_transaction, serialize_transaction, \ - script_hex_to_address, get_unspents +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key +import virtualchain +from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \ + calculate_change_amount, make_pay_to_address_script + +from keylib import ECPrivateKey, ECPublicKey from utilitybelt import is_hex from binascii import hexlify, unhexlify from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import * +from ..blockchain import get_tx_inputs # consensus hash fields (none for announcements) FIELDS = [] -def build(message_hash, testset=False): +def build(message_hash): """ Record format: @@ -53,7 +57,7 @@ def build(message_hash, testset=False): readable_script = "ANNOUNCE 0x%s" % (message_hash) hex_script = blockstack_script_to_hex(readable_script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script @@ -89,6 +93,40 @@ def make_outputs( data, inputs, change_address, pay_fee=True ): ] +def state_transition(blockchain_name, message_hash, private_key, user_public_key=None): + + # sanity check + pay_fee = True + if user_public_key is not None: + pay_fee = False + + if user_public_key is None and private_key is None: + raise Exception("Missing both public and private key") + + if len(message_hash) != 40: + raise Exception("Invalid message hash: not 20 bytes") + + if not is_hex( message_hash ): + raise Exception("Invalid message hash: not hex") + + pubk = None + + if user_public_key is not None: + # subsidizing + pubk = ECPublicKey( user_public_key ) + + else: + # ordering directly + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + inputs = get_tx_inputs( blockchain_name, from_address ) + + nulldata = build(message_hash) + outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee ) + return inputs, outputs + + def broadcast(message_hash, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, user_public_key=None, tx_only=False): # sanity check @@ -112,35 +150,30 @@ def broadcast(message_hash, private_key, blockchain_client, testset=False, block if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - from_address = None - inputs = None - private_key_obj = None - + pubk = None + if user_public_key is not None: # subsidizing - pubk = BitcoinPublicKey( user_public_key ) + pubk = ECPublicKey( user_public_key ) - from_address = pubk.address() - inputs = get_unspents( from_address, blockchain_client ) + else: + # ordering directly + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) - elif private_key is not None: - # ordering directly - pubk = BitcoinPrivateKey( private_key ).public_key() - public_key = pubk.to_hex() - - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) - nulldata = build(message_hash, testset=testset) outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee ) if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {'unsigned_tx': unsigned_tx} else: - signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj ) + signed_tx = tx_serialize_sign( inputs, outputs, private_key ) response = broadcast_transaction( signed_tx, blockchain_broadcaster ) response.update({'data': nulldata}) return response diff --git a/blockstack/lib/operations/nameimport.py b/blockstack/lib/operations/nameimport.py index 6fbd2c41f..4b14019ca 100644 --- a/blockstack/lib/operations/nameimport.py +++ b/blockstack/lib/operations/nameimport.py @@ -21,11 +21,13 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, \ - analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \ - make_pay_to_address_script, b58check_encode, b58check_decode, serialize_transaction - -from pybitcoin.transactions.outputs import calculate_change_amount +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key +import virtualchain +from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \ + calculate_change_amount, make_pay_to_address_script + +from keylib import b58check_encode + from utilitybelt import is_hex from binascii import hexlify, unhexlify @@ -33,8 +35,8 @@ from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import * from ..hashing import hash256_trunc128 - -from ..nameset import NAMEREC_FIELDS +from ..blockchain import get_tx_inputs +from ..nameset import NAMEREC_FIELDS, get_namespace_from_name # consensus hash fields (ORDER MATTERS!) FIELDS = NAMEREC_FIELDS + [ @@ -59,7 +61,8 @@ def get_import_update_hash_from_outputs( outputs, recipient ): ret = None count = 0 for output in outputs: - + + """ output_script = output['scriptPubKey'] output_asm = output_script.get('asm') output_hex = output_script.get('hex') @@ -69,6 +72,13 @@ def get_import_update_hash_from_outputs( outputs, recipient ): ret = hexlify( b58check_decode( str(output_addresses[0]) ) ) break + """ + if output.type() != "data": + continue + + if output.sender_id() != recipient: + ret = hexlify(b58check_decode( output.addresses()[0] )) + break if ret is None: raise Exception("No update hash found") @@ -76,7 +86,7 @@ def get_import_update_hash_from_outputs( outputs, recipient ): return ret -def build(name, testset=False): +def build(name): """ Takes in a name to import. Name must include the namespace ID. @@ -96,7 +106,7 @@ def build(name, testset=False): readable_script = "NAME_IMPORT 0x%s" % (hexlify(name)) hex_script = blockstack_script_to_hex(readable_script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script @@ -133,30 +143,46 @@ def make_outputs( data, inputs, recipient_address, sender_address, update_hash_b ] +def state_transition(name, recipient_address, update_hash, private_key): + + blockchain_name = namespace_to_blockchain( get_namespace_from_name( name ) ) + + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_tx_inputs( blockchain_name, from_address ) + + # NAME_IMPORT outputs + nulldata = build(name) + update_hash_b58 = b58check_encode( unhexlify(update_hash) ) + outputs = make_outputs(nulldata, inputs, recipient_address, from_address, update_hash_b58, format='hex') + + return inputs, outputs + + def broadcast(name, recipient_address, update_hash, private_key, blockchain_client, blockchain_broadcaster=None, tx_only=False, testset=False): if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - nulldata = build(name, testset=testset) - - # convert update_hash from a hex string so it looks like an address - update_hash_b58 = b58check_encode( unhexlify(update_hash) ) - - # get inputs and from address - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) - + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + # NAME_IMPORT outputs + nulldata = build(name, testset=testset) + update_hash_b58 = b58check_encode( unhexlify(update_hash) ) outputs = make_outputs(nulldata, inputs, recipient_address, from_address, update_hash_b58, format='hex') if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {"unsigned_tx": unsigned_tx} else: # serialize, sign, and broadcast the tx - response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) + signed_tx = tx_serialize_sign( inputs, outputs, private_key ) + response = broadcast_transaction( signed_tx, blockchain_broadcaster ) + # response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) # response = {'success': True } response.update({'data': nulldata}) diff --git a/blockstack/lib/operations/namespacepreorder.py b/blockstack/lib/operations/namespacepreorder.py index ff9ec7805..a6143a111 100644 --- a/blockstack/lib/operations/namespacepreorder.py +++ b/blockstack/lib/operations/namespacepreorder.py @@ -21,11 +21,12 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, serialize_transaction, \ - analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \ - make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, hex_hash160 +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key, serialize_sign_and_broadcast +import virtualchain +from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \ + calculate_change_amount, make_pay_to_address_script -from pybitcoin.transactions.outputs import calculate_change_amount +from keylib import ECPrivateKey from utilitybelt import is_hex from binascii import hexlify, unhexlify @@ -34,6 +35,7 @@ from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import * from ..hashing import hash_name +from ..blockchain import get_tx_inputs # consensus hash fields (ORDER MATTERS!) FIELDS = [ @@ -117,11 +119,40 @@ def make_outputs( data, inputs, change_addr, fee, pay_fee=True, format='bin' ): "value": calculate_change_amount(inputs, bill, dust_fee)}, # burn address - {"script_hex": make_pay_to_address_script(BLOCKSTORE_BURN_ADDRESS), + {"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS), "value": op_fee} ] +def state_transition( namespace_id, register_addr, consensus_hash, private_key, fee ): + """ + Propagate a namespace. + + Arguments: + namespace_id human-readable (i.e. base-40) name of the namespace + register_addr the addr of the key that will reveal the namespace (mixed into the preorder to prevent name preimage attack races) + private_key the Bitcoin address that created this namespace, and can populate it. + """ + + blockchain_name = virtualchain.namespace_to_blocokchain( namespace_id ) + + pk = ECPrivateKey( private_key ) + pubk = pk.public_key() + from_address = pubk.address() + pubkey_hex = pubk.to_hex() + + inputs = get_tx_outputs( blockchain_name, from_address ) + + # build custom outputs here + pubkey_hex = pk.public_key().to_hex() + script_pubkey = get_script_pubkey( pubkey_hex ) + + nulldata = build( namespace_id, script_pubkey, register_addr, consensus_hash ) + outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex') + + return inputs, outputs + + def broadcast( namespace_id, register_addr, consensus_hash, private_key, blockchain_client, fee, pay_fee=True, tx_only=False, testset=False, blockchain_broadcaster=None ): """ Propagate a namespace. @@ -134,26 +165,28 @@ def broadcast( namespace_id, register_addr, consensus_hash, private_key, blockch if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - - pubkey_hex = BitcoinPrivateKey( private_key ).public_key().to_hex() - + + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + + # build custom outputs here + + pubkey_hex = ECPrivateKey( private_key ).public_key().to_hex() script_pubkey = get_script_pubkey( pubkey_hex ) nulldata = build( namespace_id, script_pubkey, register_addr, consensus_hash, testset=testset ) - - # get inputs and from address - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) - - # build custom outputs here outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex') if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {"unsigned_tx": unsigned_tx} else: # serialize, sign, and broadcast the tx - response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) + signed_tx = tx_serialize_sign( inputs, outputs, private_key ) + response = broadcast_transaction( signed_tx, blockchain_broadcaster ) + # response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) # response = {'success': True } response.update({'data': nulldata}) diff --git a/blockstack/lib/operations/namespaceready.py b/blockstack/lib/operations/namespaceready.py index f4dd3b204..bdf92ff0f 100644 --- a/blockstack/lib/operations/namespaceready.py +++ b/blockstack/lib/operations/namespaceready.py @@ -21,15 +21,19 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, BlockchainInfoClient, hex_hash160, make_op_return_tx, serialize_transaction, broadcast_transaction, make_op_return_outputs +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key +import virtualchain +from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \ + calculate_change_amount, make_pay_to_address_script + from utilitybelt import is_hex from binascii import hexlify, unhexlify from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import * +from ..blockchain import get_tx_inputs -import virtualchain log = virtualchain.get_logger("blockstack-server") from namespacereveal import FIELDS as NAMESPACE_REVEAL_FIELDS @@ -66,6 +70,20 @@ def build( namespace_id, testset=False ): return packaged_script +def state_transition( namespace_id, private_key ): + + blockchain_name = namespace_to_blockchain( namespace_id ) + nulldata = build( namespace_id ) + + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_tx_inputs( blockchain_name, from_address ) + + # OP_RETURN outputs + outputs = make_op_return_outputs( nulldata, inputs, from_address, fee=DEFAULT_OP_RETURN_FEE, format='hex' ) + return inputs, outputs + + def broadcast( namespace_id, private_key, blockchain_client, testset=False, tx_only=False, blockchain_broadcaster=None ): if blockchain_broadcaster is None: @@ -73,15 +91,16 @@ def broadcast( namespace_id, private_key, blockchain_client, testset=False, tx_o nulldata = build( namespace_id, testset=testset ) - # get inputs and from address - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) # OP_RETURN outputs outputs = make_op_return_outputs( nulldata, inputs, from_address, fee=DEFAULT_OP_RETURN_FEE, format='hex' ) if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {'unsigned_tx': signed_tx} else: diff --git a/blockstack/lib/operations/namespacereveal.py b/blockstack/lib/operations/namespacereveal.py index fbb6e7452..42fcf0de3 100644 --- a/blockstack/lib/operations/namespacereveal.py +++ b/blockstack/lib/operations/namespacereveal.py @@ -21,11 +21,10 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, serialize_transaction, \ - analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \ - make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, hex_hash160 - -from pybitcoin.transactions.outputs import calculate_change_amount +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key +import virtualchain +from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \ + calculate_change_amount, make_pay_to_address_script from utilitybelt import is_hex from binascii import hexlify, unhexlify @@ -36,8 +35,8 @@ from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import blockstack_script_to_hex, add_magic_bytes from ..hashing import hash_name +from ..blockchain import get_tx_inputs -import virtualchain log = virtualchain.get_logger("blockstack-log") # consensus hash fields (ORDER MATTERS!) @@ -145,7 +144,7 @@ def namespacereveal_sanity_check( namespace_id, version, lifetime, coeff, base, # version: 2 bytes # namespace ID: up to 19 bytes -def build( namespace_id, version, reveal_addr, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount, testset=False ): +def build( namespace_id, version, reveal_addr, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount ): """ Record to mark the beginning of a namespace import in the blockchain. This reveals the namespace ID, and encodes the preorder's namespace rules. @@ -196,12 +195,12 @@ def build( namespace_id, version, reveal_addr, lifetime, coeff, base, bucket_exp readable_script = "NAMESPACE_REVEAL 0x%s 0x%s 0x%s 0x%s 0x%s 0x%s 0x%s" % (life_hex, coeff_hex, base_hex, bucket_hex, discount_hex, version_hex, namespace_id_hex) hex_script = blockstack_script_to_hex(readable_script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script -def make_outputs( data, inputs, reveal_addr, change_addr, format='bin', testset=False ): +def make_outputs( data, inputs, reveal_addr, change_addr, format='bin' ): """ Make outputs for a register: [0] OP_RETURN with the name @@ -227,6 +226,33 @@ def make_outputs( data, inputs, reveal_addr, change_addr, format='bin', testset= +def state_transition( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, private_key ): + """ + Propagate a namespace. + + Arguments: + namespace_id human-readable (i.e. base-40) name of the namespace + reveal_addr address to own this namespace until it is ready + lifetime: the number of blocks for which names will be valid (pass a negative value for "infinite") + coeff: cost multipler + base_cost: the base cost (i.e. cost of a 1-character name), in satoshis + bucket_exponents: bucket cost exponents to which to raise the base cost + nonalpha_discount: discount multipler for non-alpha-character names + no_vowel_discount: discount multipler for no-vowel names + """ + + blockchain_name = virtualchain.namespace_to_blockchain(namespace_id) + nulldata = build( namespace_id, BLOCKSTACK_VERSION, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount ) + + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_tx_inputs( blockchain_name, from_address ) + + # build custom outputs here + outputs = make_outputs(nulldata, inputs, reveal_addr, from_address, format='hex') + return inputs, outputs + + def broadcast( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, private_key, blockchain_client, tx_only=False, blockchain_broadcaster=None, testset=False ): """ Propagate a namespace. @@ -247,19 +273,22 @@ def broadcast( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exp nulldata = build( namespace_id, BLOCKSTACK_VERSION, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, testset=testset ) - # get inputs and from address - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) - + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + # build custom outputs here outputs = make_outputs(nulldata, inputs, reveal_addr, from_address, format='hex') if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {"unsigned_tx": unsigned_tx} else: # serialize, sign, and broadcast the tx - response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) + signed_tx = tx_serialize_sign( inputs, outputs, private_key ) + response = broadcast_transaction( signed_tx, blockchain_broadcaster ) + # response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) # response = {'success': True } response.update({'data': nulldata}) diff --git a/blockstack/lib/operations/preorder.py b/blockstack/lib/operations/preorder.py index 0032519bd..1d6c928a3 100644 --- a/blockstack/lib/operations/preorder.py +++ b/blockstack/lib/operations/preorder.py @@ -21,14 +21,12 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, serialize_transaction, \ - analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \ - make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, \ - hex_hash160, bin_hash160, BitcoinPrivateKey, BitcoinPublicKey, script_hex_to_address, get_unspents, \ - make_op_return_outputs +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key, serialize_sign_and_broadcast +import virtualchain +from virtualchain.lib.blockchain.bitcoin import tx_serialize, script_hex_to_address, make_op_return_script, \ + calculate_change_amount, make_pay_to_address_script - -from pybitcoin.transactions.outputs import calculate_change_amount +from keylib import ECPrivateKey, ECPublicKey from utilitybelt import is_hex from binascii import hexlify, unhexlify @@ -36,6 +34,8 @@ from ..b40 import b40_to_hex, is_b40 from ..config import * from ..scripts import * from ..hashing import hash_name +from ..blockchain import get_tx_inputs +from ..nameset import get_namespace_from_name # consensus hash fields (ORDER MATTERS!) FIELDS = [ @@ -52,7 +52,7 @@ FIELDS = [ 'op_fee', # blockstack fee (sent to burn address) ] -def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None, testset=False): +def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None): """ Takes a name, including the namespace ID (but not the id: scheme), a script_publickey to prove ownership of the subsequent NAME_REGISTER operation, and the current consensus hash for this block (to prove that the @@ -82,7 +82,7 @@ def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None, te script = 'NAME_PREORDER 0x%s 0x%s' % (name_hash, consensus_hash) hex_script = blockstack_script_to_hex(script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script @@ -111,11 +111,46 @@ def make_outputs( data, inputs, sender_addr, fee, format='bin' ): "value": calculate_change_amount(inputs, bill, dust_fee)}, # burn address - {"script_hex": make_pay_to_address_script(BLOCKSTORE_BURN_ADDRESS), + {"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS), "value": op_fee} ] +def state_transition(name, private_key, register_addr, consensus_hash, fee, subsidy_public_key=None): + """ + Builds and broadcasts a preorder transaction. + + @subsidy_public_key: if given, the public part of the subsidy key + """ + + namespace_id = get_namespace_from_name( name ) + blockchain_name = namespace_to_blockchain( namespace_id ) + + # sanity check + if subsidy_public_key is None and private_key is None: + raise Exception("Missing both client public and private key") + + pubk = None + script_pubkey = None # to be mixed into preorder hash + + if subsidy_public_key is not None: + # subsidizing + pubk = ECPublicKey( subsidy_public_key ) + script_pubkey = get_script_pubkey( subsidy_public_key ) + + else: + # ordering directly + pubk = ECPrivateKey( private_key ).public_key() + script_pubkey = get_script_pubkey( public_key ) + + from_address = pubk.address() + inputs = get_tx_inputs( blockchain_name, from_address ) + + nulldata = build( name, script_pubkey, register_addr, consensus_hash, testset=testset) + outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex') + return inputs, outputs + + def broadcast(name, private_key, register_addr, consensus_hash, blockchain_client, fee, blockchain_broadcaster=None, subsidy_public_key=None, tx_only=False, testset=False): """ Builds and broadcasts a preorder transaction. @@ -134,40 +169,35 @@ def broadcast(name, private_key, register_addr, consensus_hash, blockchain_clien if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - from_address = None # change address - inputs = None - private_key_obj = None + pubk = None script_pubkey = None # to be mixed into preorder hash if subsidy_public_key is not None: # subsidizing - pubk = BitcoinPublicKey( subsidy_public_key ) - - from_address = BitcoinPublicKey( subsidy_public_key ).address() - - inputs = get_unspents( from_address, blockchain_client ) + pubk = ECPublicKey( subsidy_public_key ) script_pubkey = get_script_pubkey( subsidy_public_key ) else: # ordering directly - pubk = BitcoinPrivateKey( private_key ).public_key() - public_key = pubk.to_hex() + pubk = ECPrivateKey( private_key ).public_key() script_pubkey = get_script_pubkey( public_key ) - - # get inputs and from address using private key - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) - + + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + nulldata = build( name, script_pubkey, register_addr, consensus_hash, testset=testset) outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex') if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {"unsigned_tx": unsigned_tx} else: # serialize, sign, and broadcast the tx - response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) + signed_tx = tx_serialize_sign( inputs, outputs, private_key ) + response = broadcast_transaction( inputs, outputs, blockchain_broadcaster ) + # response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) response.update({'data': nulldata}) return response @@ -225,7 +255,7 @@ def get_fees( inputs, outputs ): log.error("outputs[2] has no decipherable burn address") return (None, None) - if addr_hash != BLOCKSTORE_BURN_ADDRESS: + if addr_hash != BLOCKSTACK_BURN_ADDRESS: log.error("outputs[2] is not the burn address") return (None, None) diff --git a/blockstack/lib/operations/register.py b/blockstack/lib/operations/register.py index 906ab93b1..1073aa790 100644 --- a/blockstack/lib/operations/register.py +++ b/blockstack/lib/operations/register.py @@ -21,12 +21,12 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, serialize_transaction, \ - analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \ - make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, hex_hash160, \ - BitcoinPrivateKey, get_unspents, script_hex_to_address +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key +import virtualchain +from virtualchain.lib.blockchain.bitcoin import tx_serialize, script_hex_to_address, make_op_return_script, \ + calculate_change_amount, make_pay_to_address_script -from pybitcoin.transactions.outputs import calculate_change_amount +from keylib import ECPrivateKey, ECPublicKey from utilitybelt import is_hex from binascii import hexlify, unhexlify @@ -34,9 +34,9 @@ from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import * from ..hashing import hash256_trunc128 -from ..nameset import NAMEREC_FIELDS +from ..nameset import NAMEREC_FIELDS, get_namespace_from_name +from ..blockchain import get_tx_inputs -import virtualchain log = virtualchain.get_logger("blockstack-server") # consensus hash fields (ORDER MATTERS!) @@ -61,6 +61,7 @@ def get_registration_recipient_from_outputs( outputs ): ret = None for output in outputs: + """ output_script = output['scriptPubKey'] output_asm = output_script.get('asm') output_hex = output_script.get('hex') @@ -72,6 +73,12 @@ def get_registration_recipient_from_outputs( outputs ): # ret = (output_hex, output_addresses[0]) ret = output_hex break + """ + if output.type() != "data": + continue + + ret = output.sender_id() + break if ret is None: raise Exception("No registration address found") @@ -79,7 +86,7 @@ def get_registration_recipient_from_outputs( outputs ): return ret -def build(name, testset=False): +def build(name): """ Takes in the name that was preordered, including the namespace ID (but not the id: scheme) Returns a hex string representing up to LENGTHS['blockchain_id_name'] bytes. @@ -97,7 +104,7 @@ def build(name, testset=False): readable_script = "NAME_REGISTRATION 0x%s" % (hexlify(name)) hex_script = blockstack_script_to_hex(readable_script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script @@ -168,12 +175,46 @@ def make_outputs( data, change_inputs, register_addr, change_addr, renewal_fee=N outputs.append( # burn address (when renewing) - {"script_hex": make_pay_to_address_script(BLOCKSTORE_BURN_ADDRESS), + {"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS), "value": op_fee} ) return outputs + +def state_transition(name, private_key, register_addr, renewal_fee=None, user_public_key=None, subsidy_public_key=None): + + namespace_id = get_namespace_from_name( name ) + blockchain_name = namespace_to_blockchain( namespace_id ) + + if subsidy_public_key is None and private_key is None: + raise Exception("Missing both public and private key") + + subsidized_renewal = False + + if subsidy_public_key is not None: + # subsidizing + + if user_public_key is not None and renewal_fee is not None: + # renewing, and subsidizing the renewal + pubk = ECPublicKey( user_public_key ) + subsidized_renewal = True + + else: + # registering or renewing under the subsidy key + pubk = ECPublicKey( subsidy_public_key ) + + elif private_key is not None: + # ordering directly + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + unspents = get_tx_inputs( blockchain_name, from_address ) + + nulldata = build(name) + outputs = make_outputs(nulldata, change_inputs, register_addr, from_address, renewal_fee=renewal_fee, pay_fee=(not subsidized_renewal), format='hex') + return inputs, outputs + def broadcast(name, private_key, register_addr, blockchain_client, renewal_fee=None, blockchain_broadcaster=None, tx_only=False, user_public_key=None, subsidy_public_key=None, testset=False): @@ -190,47 +231,42 @@ def broadcast(name, private_key, register_addr, blockchain_client, renewal_fee=N if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - - from_address = None - change_inputs = None - private_key_obj = None + subsidized_renewal = False if subsidy_public_key is not None: # subsidizing - pubk = BitcoinPublicKey( subsidy_public_key ) if user_public_key is not None and renewal_fee is not None: # renewing, and subsidizing the renewal - from_address = BitcoinPublicKey( user_public_key ).address() + pubk = ECPublicKey( user_public_key ) subsidized_renewal = True else: # registering or renewing under the subsidy key - from_address = pubk.address() - - change_inputs = get_unspents( from_address, blockchain_client ) + pubk = ECPublicKey( subsidy_public_key ) elif private_key is not None: # ordering directly - pubk = BitcoinPrivateKey( private_key ).public_key() - public_key = pubk.to_hex() - - # get inputs and from address using private key - private_key_obj, from_address, change_inputs = analyze_private_key(private_key, blockchain_client) - + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + unspents = get_unspents( from_ddress, blockchain_client ) + nulldata = build(name, testset=testset) outputs = make_outputs(nulldata, change_inputs, register_addr, from_address, renewal_fee=renewal_fee, pay_fee=(not subsidized_renewal), format='hex') if tx_only: - unsigned_tx = serialize_transaction( change_inputs, outputs ) + unsigned_tx = tx_serialize( change_inputs, outputs ) return {"unsigned_tx": unsigned_tx} else: # serialize, sign, and broadcast the tx - response = serialize_sign_and_broadcast(change_inputs, outputs, private_key_obj, blockchain_broadcaster) + signed_tx = tx_serialize_sign( change_inputs, outputs, private_key ) + response = broadcast_transaction( signed_tx, blockchain_broadcaster ) + # response = serialize_sign_and_broadcast(change_inputs, outputs, private_key_obj, blockchain_broadcaster) response.update({'data': nulldata}) return response @@ -295,7 +331,7 @@ def get_fees( inputs, outputs ): if addr_hash is None: return (None, None) - if addr_hash != BLOCKSTORE_BURN_PUBKEY_HASH: + if addr_hash != BLOCKSTACK_BURN_PUBKEY_HASH: return (None, None) dust_fee = (len(inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE diff --git a/blockstack/lib/operations/revoke.py b/blockstack/lib/operations/revoke.py index 2312c93d8..270d69008 100644 --- a/blockstack/lib/operations/revoke.py +++ b/blockstack/lib/operations/revoke.py @@ -21,23 +21,25 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, make_op_return_tx, make_op_return_outputs, \ - make_op_return_script, broadcast_transaction, serialize_transaction, \ - script_hex_to_address, get_unspents +# from blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key +import virtualchain +from virtualchain.lib.blockchain.bitcoin import tx_serialize, script_hex_to_address, make_op_return_script from utilitybelt import is_hex from binascii import hexlify, unhexlify +from keylib import ECPublicKey, ECPrivateKey from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import * -from ..nameset import NAMEREC_FIELDS +from ..blockchain import get_tx_inputs +from ..nameset import NAMEREC_FIELDS, get_namespace_from_name # consensus hash fields (ORDER MATTERS!) FIELDS = NAMEREC_FIELDS -def build(name, testset=False): +def build(name): """ Takes in the name, including the namespace ID (but not the id: scheme) Returns a hex string representing up to LENGTHS['blockchain_id_name'] bytes. @@ -55,7 +57,7 @@ def build(name, testset=False): readable_script = "NAME_REVOKE 0x%s" % (hexlify(name)) hex_script = blockstack_script_to_hex(readable_script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script @@ -91,8 +93,40 @@ def make_outputs( data, inputs, change_address, pay_fee=True ): ] -def broadcast(name, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, user_public_key=None, tx_only=False): +def state_transition(name, private_key, user_public_key=None): + namespace_id = get_namespace_from_name( name ) + blockchain_name = namespace_to_blockchain( namespace_id ) + + # sanity check + pay_fee = True + if user_public_key is not None: + pay_fee = False + + pubk = None + if user_public_key is not None: + # subsidizing + pubk = ECPublicKey( user_public_key ) + + else: + # ordering directly + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + + nulldata = build(name, testset=testset) + outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee ) + + return inputs, outputs + + + +def broadcast(name, private_key, user_public_key=None): + + namespace_id = get_namespace_from_name( name ) + blockchain_name = namespace_to_blockchain( namespace_id ) + # sanity check pay_fee = True if user_public_key is not None: @@ -108,30 +142,25 @@ def broadcast(name, private_key, blockchain_client, testset=False, blockchain_br if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - from_address = None - inputs = None - private_key_obj = None - + + pubk = None if user_public_key is not None: # subsidizing - pubk = BitcoinPublicKey( user_public_key ) + pubk = ECPublicKey( user_public_key ) - from_address = pubk.address() - inputs = get_unspents( from_address, blockchain_client ) - - elif private_key is not None: + else: # ordering directly - pubk = BitcoinPrivateKey( private_key ).public_key() - public_key = pubk.to_hex() + pubk = ECPrivateKey( private_key ).public_key() - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) - + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + nulldata = build(name, testset=testset) outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee ) - + if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {'unsigned_tx': unsigned_tx} else: diff --git a/blockstack/lib/operations/transfer.py b/blockstack/lib/operations/transfer.py index 786d0b61d..0c9aef146 100644 --- a/blockstack/lib/operations/transfer.py +++ b/blockstack/lib/operations/transfer.py @@ -21,20 +21,24 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, serialize_transaction, \ - analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \ - make_pay_to_address_script, BitcoinPrivateKey, BitcoinPublicKey, get_unspents, script_hex_to_address - -from pybitcoin.transactions.outputs import calculate_change_amount +from keylib import ECPrivateKey, ECPublicKey from utilitybelt import is_hex from binascii import hexlify, unhexlify +from blockstack_utxo import analyze_private_key, get_unspents, broadcast_transaction + +import virtualchain.lib.blockchain.bitcoin as virtualchain_bitcoin +from virtualchain_bitcoin import make_pay_to_address_script, \ + make_op_return_script, script_hex_to_address, calculate_change_amount, \ + calculate_change_amount, tx_serialize + from ..b40 import b40_to_hex, bin_to_b40, is_b40 from ..config import * from ..scripts import * from ..hashing import hash256_trunc128 -from ..nameset import NAMEREC_FIELDS +from ..nameset import NAMEREC_FIELDS, get_namespace_from_name +from ..blockchain import get_tx_inputs # consensus hash fields (ORDER MATTERS!) FIELDS = NAMEREC_FIELDS + [ @@ -56,7 +60,8 @@ def get_transfer_recipient_from_outputs( outputs ): ret = None for output in outputs: - + + """ output_script = output['scriptPubKey'] output_asm = output_script.get('asm') output_hex = output_script.get('hex') @@ -66,6 +71,12 @@ def get_transfer_recipient_from_outputs( outputs ): ret = output_hex break + """ + if output.type() != "data": + break + + ret = output.sender_id() + break if ret is None: raise Exception("No recipients found") @@ -90,7 +101,7 @@ def transfer_sanity_check( name, consensus_hash ): return True -def build(name, keepdata, consensus_hash, testset=False): +def build(name, keepdata, consensus_hash): """ Takes in a name to transfer. Name must include the namespace ID, but not the scheme. @@ -118,7 +129,7 @@ def build(name, keepdata, consensus_hash, testset=False): readable_script = 'NAME_TRANSFER 0x%s 0x%s 0x%s' % (disposition_hex, name_hash, consensus_hash) hex_script = blockstack_script_to_hex(readable_script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script @@ -153,6 +164,37 @@ def make_outputs( data, inputs, new_name_owner_address, change_address, pay_fee= ] +def state_transition(name, destination_address, keepdata, consensus_hash, private_key, user_public_key=None): + + namespace_id = get_namespace_from_name(name) + blockchain_name = namespace_to_blockchain( namespace_id ) + + # sanity check + pay_fee = True + if user_public_key is not None: + pay_fee = False + + if user_public_key is None and private_key is None: + raise Exception("Missing both public and private key") + + pubk = None + + if user_public_key is not None: + # subsidizing + pubk = ECPublicKey( user_public_key ) + + else: + # ordering directly + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + inputs = get_tx_inputs( blockchain_name, from_address ) + + nulldata = build(name, keepdata, consensus_hash ) + outputs = make_outputs(nulldata, inputs, destination_address, from_address, pay_fee=pay_fee, format='hex') + return inputs, outputs + + def broadcast(name, destination_address, keepdata, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, tx_only=False, user_public_key=None, testset=False): # sanity check @@ -169,37 +211,33 @@ def broadcast(name, destination_address, keepdata, consensus_hash, private_key, if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - - from_address = None - inputs = None - private_key_obj = None + + pubk = None if user_public_key is not None: # subsidizing - pubk = BitcoinPublicKey( user_public_key ) + pubk = ECPublicKey( user_public_key ) - from_address = pubk.address() - inputs = get_unspents( from_address, blockchain_client ) - - elif private_key is not None: + else: # ordering directly - pubk = BitcoinPrivateKey( private_key ).public_key() - public_key = pubk.to_hex() - - # get inputs and from address using private key - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) + pubk = ECPrivateKey( private_key ).public_key() + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + nulldata = build(name, keepdata, consensus_hash, testset=testset) outputs = make_outputs(nulldata, inputs, destination_address, from_address, pay_fee=pay_fee, format='hex') if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {"unsigned_tx": unsigned_tx} else: # serialize, sign, and broadcast the tx - response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) + signed_tx = tx_serialize_sign( inputs, outputs, private_key ) + response = broadcast_transaction( signed_tx, blockchain_broadcaster ) + # response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster) response.update({'data': nulldata}) return response diff --git a/blockstack/lib/operations/update.py b/blockstack/lib/operations/update.py index 31bac2613..ddc374f35 100644 --- a/blockstack/lib/operations/update.py +++ b/blockstack/lib/operations/update.py @@ -21,10 +21,8 @@ along with Blockstack. If not, see . """ -from pybitcoin import embed_data_in_blockchain, make_op_return_tx, BlockchainInfoClient, BitcoinPrivateKey, \ - BitcoinPublicKey, get_unspents, script_hex_to_address, hex_hash160, broadcast_transaction, serialize_transaction, \ - make_op_return_outputs, make_op_return_script - +from blockstack_utxo import get_unspents, broadcast_transaction +from keylib import ECPrivateKey, ECPublicKey from utilitybelt import is_hex from binascii import hexlify, unhexlify @@ -33,9 +31,13 @@ from ..config import * from ..scripts import * from ..hashing import hash256_trunc128 -from ..nameset import NAMEREC_FIELDS +from ..nameset import NAMEREC_FIELDS, get_namespace_from_name +from ..blockchain import get_tx_inputs import virtualchain +from virtualchain import hex_hash160 +from virtualchain.lib.blockchain.bitcoin import script_hex_to_address, make_op_return_script, tx_serialize, tx_serialize_and_sign, tx_output_is_op_return + log = virtualchain.get_logger("blockstack-server") # consensus hash fields (ORDER MATTERS!) @@ -64,7 +66,7 @@ def update_sanity_test( name, consensus_hash, data_hash ): return True -def build(name, consensus_hash, data_hash=None, testset=False): +def build(name, consensus_hash, data_hash=None): """ Takes in the name to update the data for and the data update itself. Name must include the namespace ID, but not the scheme. @@ -84,7 +86,7 @@ def build(name, consensus_hash, data_hash=None, testset=False): readable_script = 'NAME_UPDATE 0x%s 0x%s' % (hex_name, data_hash) hex_script = blockstack_script_to_hex(readable_script) - packaged_script = add_magic_bytes(hex_script, testset=testset) + packaged_script = add_magic_bytes(hex_script) return packaged_script @@ -120,6 +122,40 @@ def make_outputs( data, inputs, change_address, pay_fee=True ): ] +def state_transition(name, data_hash, consensus_hash, private_key, user_public_key=None): + """ + Write a name update into the blockchain. + Returns a JSON object with 'data' set to the nulldata and 'transaction_hash' set to the transaction hash on success. + """ + + namespace_id = get_namespace_from_name(name) + blockchain_name = namespace_to_blockchain( namespace_id ) + + # sanity check + pay_fee = True + if user_public_key is not None: + pay_fee = False + + if user_public_key is None and private_key is None: + raise Exception("Missing both public and private key") + + pubk = None + if user_public_key is not None: + # subsidizing + pubk = ECPublicKey( user_public_key ) + + else: + # ordering directly + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + inputs = get_tx_inputs( blockchain_name, from_address ) + + nulldata = build(name, consensus_hash, data_hash=data_hash) + outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee ) + return inputs, outputs + + def broadcast(name, data_hash, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, tx_only=False, user_public_key=None, testset=False): """ Write a name update into the blockchain. @@ -141,37 +177,29 @@ def broadcast(name, data_hash, consensus_hash, private_key, blockchain_client, b if blockchain_broadcaster is None: blockchain_broadcaster = blockchain_client - from_address = None - inputs = None - private_key_obj = None - + pubk = None if user_public_key is not None: # subsidizing - pubk = BitcoinPublicKey( user_public_key ) - from_address = pubk.address() - - # get inputs from utxo provider - inputs = get_unspents( from_address, blockchain_client ) + pubk = ECPublicKey( user_public_key ) - elif private_key is not None: + else: # ordering directly - pubk = BitcoinPrivateKey( private_key ).public_key() - public_key = pubk.to_hex() - - # get inputs and from address using private key - private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) - + pubk = ECPrivateKey( private_key ).public_key() + + from_address = pubk.address() + inputs = get_unspents( from_address, blockchain_client ) + nulldata = build(name, consensus_hash, data_hash=data_hash, testset=testset) outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee ) if tx_only: - unsigned_tx = serialize_transaction( inputs, outputs ) + unsigned_tx = tx_serialize( inputs, outputs ) return {'unsigned_tx': unsigned_tx} else: - signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj ) + signed_tx = tx_serialize_sign( inputs, outputs, private_key ) response = broadcast_transaction( signed_tx, blockchain_broadcaster ) response.update({'data': nulldata}) return response