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