remove dead code

This commit is contained in:
Jude Nelson
2016-06-14 11:31:04 -04:00
parent 33329b373d
commit 144d1dbe1d
10 changed files with 5 additions and 1899 deletions

View File

@@ -21,176 +21,6 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 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 *
# consensus hash fields (none for announcements)
FIELDS = []
'''
def build(message_hash, testset=False):
"""
Record format:
0 2 3 23
|----|--|-----------------------------|
magic op message hash (160-bit)
"""
if len(message_hash) != 40:
raise Exception("Invalid hash: not 20 bytes")
if not is_hex(message_hash):
raise Exception("Invalid hash: not hex")
readable_script = "ANNOUNCE 0x%s" % (message_hash)
hex_script = blockstack_script_to_hex(readable_script)
packaged_script = add_magic_bytes(hex_script, testset=testset)
return packaged_script
def make_outputs( data, inputs, change_address, pay_fee=True ):
"""
Make outputs for an announcement.
"""
dust_fee = None
op_fee = None
dust_value = None
if pay_fee:
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = DEFAULT_DUST_FEE
dust_value = DEFAULT_DUST_FEE
else:
# will be subsidized
dust_fee = 0
op_fee = 0
dust_value = 0
return [
# main output
{"script_hex": make_op_return_script(data, format='hex'),
"value": 0},
# change output
{"script_hex": make_pay_to_address_script(change_address),
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
]
def broadcast(message_hash, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, user_public_key=None, subsidize=False):
# sanity check
tx_only = False
pay_fee = True
if user_public_key is not None:
tx_only = True
if subsidize:
pay_fee = False
if user_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
if not tx_only and private_key is None:
raise Exception("Need private key for broadcasting")
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")
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
if user_public_key is not None:
pubk = BitcoinPublicKey( user_public_key )
from_address = pubk.address()
inputs = get_unspents( from_address, blockchain_client )
elif private_key is not None:
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 )
return {'unsigned_tx': unsigned_tx}
else:
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
response.update({'data': nulldata})
return response
def parse(bin_payload):
"""
Interpret a block's nulldata back into a SHA256. The first three bytes (2 magic + 1 opcode)
will not be present in bin_payload.
"""
message_hash = hexlify(bin_payload)
if not is_hex( message_hash ):
log.error("Not a message hash")
return None
if len(message_hash) != 40:
log.error("Not a 160-bit hash")
return None
return {
'opcode': 'ANNOUNCE',
'message_hash': message_hash
}
def get_fees( inputs, outputs ):
"""
Given a transaction's outputs, look up its fees:
* there should be two outputs: the OP_RETURN and change address
Return (dust fees, operation fees) on success
Return (None, None) on invalid output listing
"""
if len(outputs) != 2:
return (None, None)
# 0: op_return
if not tx_output_is_op_return( outputs[0] ):
return (None, None)
if outputs[0]["value"] != 0:
return (None, None)
# 1: change address
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
return (None, None)
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = 0
return (dust_fee, op_fee)
'''

View File

@@ -21,18 +21,11 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 utilitybelt import is_hex
from pybitcoin import b58check_decode
from binascii import hexlify, unhexlify
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
@@ -75,120 +68,3 @@ def get_import_update_hash_from_outputs( outputs, recipient ):
return ret
'''
def build(name, testset=False):
"""
Takes in a name to import. Name must include the namespace ID.
Record format:
0 2 3 39
|----|--|-----------------------------|
magic op name.ns_id (37 bytes)
The transaction itself will have two outputs:
* the recipient
* the hash of the name's associated data
"""
if not is_name_valid( name ):
raise Exception("Invalid name '%s'" % name)
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)
return packaged_script
def make_outputs( data, inputs, recipient_address, sender_address, update_hash_b58, format='bin'):
"""
Builds the outputs for a name import:
* [0] is the OP_RETURN
* [1] is the new owner (recipient)
* [2] is the update hash
* [3] is the change sent to the original owner
"""
dust_fee = DEFAULT_OP_RETURN_FEE + (len(inputs) + 3) * DEFAULT_DUST_FEE
op_fee = 2 * DEFAULT_DUST_FEE
dust_value = DEFAULT_DUST_FEE
return [
# main output
{"script_hex": make_op_return_script(data, format=format),
"value": 0},
# recipient output
{"script_hex": make_pay_to_address_script(recipient_address),
"value": dust_value},
# update hash output
{"script_hex": make_pay_to_address_script(update_hash_b58),
"value": dust_value},
# change output
{"script_hex": make_pay_to_address_script(sender_address),
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
]
def broadcast(name, recipient_address, update_hash, private_key, blockchain_client, blockchain_broadcaster=None, user_public_key=None, testset=False):
tx_only = False
# TODO: support tx_only
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)
# NAME_IMPORT outputs
outputs = make_outputs(nulldata, inputs, recipient_address, from_address, update_hash_b58, format='hex')
if tx_only:
unsigned_tx = serialize_transaction( 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)
# response = {'success': True }
response.update({'data': nulldata})
# return the response
return response
def parse(bin_payload, recipient, update_hash ):
"""
# NOTE: first three bytes were stripped
"""
fqn = bin_payload
if not is_name_valid( fqn ):
return None
return {
'opcode': 'NAME_IMPORT',
'name': fqn,
'recipient': recipient,
'update_hash': update_hash
}
def get_fees( inputs, outputs ):
"""
Blockstack currently does not allow
the subsidization of namespaces.
"""
return (None, None)
'''

View File

@@ -21,20 +21,6 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 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 ..hashing import hash_name
# consensus hash fields (ORDER MATTERS!)
FIELDS = [
'namespace_id_hash', # hash(namespace_id,sender,reveal_addr)
@@ -49,149 +35,3 @@ FIELDS = [
'address' # address from the scriptPubKey
]
'''
def build( namespace_id, script_pubkey, register_addr, consensus_hash, namespace_id_hash=None, testset=False ):
"""
Preorder a namespace with the given consensus hash. This records that someone has begun to create
a namespace, while blinding all other peers to its ID. This operation additionally records the
consensus hash in order to ensure that all peers will recognize that this sender has begun the creation.
Takes an ASCII-encoded namespace ID.
NOTE: "namespace_id" must not start with ., but can contain anything else we want
We put the hash of the namespace ID instead of the namespace ID itself to avoid races with squatters (akin to pre-ordering)
Format:
0 2 3 23 39
|-----|---|--------------------------------------|----------------|
magic op hash(ns_id,script_pubkey,reveal_addr) consensus hash
"""
# sanity check
if namespace_id_hash is None:
# expect inputs to the hash...
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
raise Exception("Namespace identifier '%s' has non-base-38 characters" % namespace_id)
if len(namespace_id) == 0 or len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
raise Exception("Invalid namespace ID length '%s (expected length between 1 and %s)" % (namespace_id, LENGTHS['blockchain_id_namespace_id']))
namespace_id_hash = hash_name(namespace_id, script_pubkey, register_addr=register_addr)
readable_script = "NAMESPACE_PREORDER 0x%s 0x%s" % (namespace_id_hash, consensus_hash)
hex_script = blockstack_script_to_hex(readable_script)
packaged_script = add_magic_bytes(hex_script, testset=testset)
return packaged_script
def make_outputs( data, inputs, change_addr, fee, pay_fee=True, format='bin' ):
"""
Make outputs for a namespace preorder:
[0] OP_RETURN with the name
[1] change address with the NAME_PREORDER sender's address
[2] pay-to-address with the *burn address* with the fee
"""
dust_fee = DEFAULT_OP_RETURN_FEE + (len(inputs) + 2) * DEFAULT_DUST_FEE
op_fee = max(fee, DEFAULT_DUST_FEE)
dust_value = DEFAULT_DUST_FEE
bill = op_fee
if not pay_fee:
# subsidized
dust_fee = 0
op_fee = 0
dust_value = 0
bill = 0
return [
# main output
{"script_hex": make_op_return_script(data, format=format),
"value": 0},
# change address
{"script_hex": make_pay_to_address_script(change_addr),
"value": calculate_change_amount(inputs, bill, dust_fee)},
# burn address
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
"value": op_fee}
]
def broadcast( namespace_id, register_addr, consensus_hash, private_key, blockchain_client, fee, pay_fee=True, user_public_key=None, testset=False, blockchain_broadcaster=None ):
"""
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.
"""
# TODO: support tx_only
tx_only = False
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
pubkey_hex = BitcoinPrivateKey( 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 )
return {"unsigned_tx": unsigned_tx}
else:
# serialize, sign, and broadcast the tx
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
# response = {'success': True }
response.update({'data': nulldata})
return response
def parse( bin_payload ):
"""
NOTE: the first three bytes will be missing
"""
if len(bin_payload) != LENGTHS['preorder_name_hash'] + LENGTHS['consensus_hash']:
log.error("Invalid namespace preorder payload length %s" % len(bin_payload))
return None
namespace_id_hash = bin_payload[ :LENGTHS['preorder_name_hash'] ]
consensus_hash = bin_payload[ LENGTHS['preorder_name_hash']: LENGTHS['preorder_name_hash'] + LENGTHS['consensus_hash'] ]
namespace_id_hash = hexlify( namespace_id_hash )
consensus_hash = hexlify( consensus_hash )
return {
'opcode': 'NAMESPACE_PREORDER',
'namespace_id_hash': namespace_id_hash,
'consensus_hash': consensus_hash
}
def get_fees( inputs, outputs ):
"""
Blockstack currently does not allow
the subsidization of namespaces.
"""
return (None, None)
'''

View File

@@ -21,14 +21,6 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
from pybitcoin import embed_data_in_blockchain, BlockchainInfoClient, hex_hash160, make_op_return_tx, serialize_transaction, broadcast_transaction, make_op_return_outputs
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 *
import virtualchain
log = virtualchain.get_logger("blockstack-server")
@@ -39,94 +31,3 @@ FIELDS = NAMESPACE_REVEAL_FIELDS + [
'ready_block', # block number at which the namespace was readied
]
'''
def build( namespace_id, testset=False ):
"""
Record to mark the end of a namespace import in the blockchain.
Takes an base40-encoded namespace ID to mark the end.
Format:
0 2 3 4 23
|-----|--|--|------------|
magic op . ns_id
"""
# sanity check
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
raise Exception("Namespace ID '%s' has non-base-38 characters" % namespace_id)
if len(namespace_id) == 0 or len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
raise Exception("Invalid namespace ID '%s (expected length between 1 and %s)" % (namespace_id, LENGTHS['blockchain_id_namespace_id']))
readable_script = "NAMESPACE_READY 0x%s" % (hexlify("." + namespace_id))
hex_script = blockstack_script_to_hex(readable_script)
packaged_script = add_magic_bytes(hex_script, testset=testset)
return packaged_script
def broadcast( namespace_id, private_key, blockchain_client, user_public_key=None, testset=False, blockchain_broadcaster=None ):
tx_only = False
# TODO: support tx_only
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
nulldata = build( namespace_id, testset=testset )
# get inputs and from address
private_key_obj, from_address, inputs = analyze_private_key(private_key, 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 )
return {'unsigned_tx': signed_tx}
else:
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
response.update({'data': nulldata})
return response
def parse( bin_payload ):
"""
NOTE: the first three bytes will be missing
NOTE: the first byte in bin_payload is a '.'
"""
if bin_payload[0] != '.':
log.error("Missing namespace delimiter .")
return None
namespace_id = bin_payload[ 1: ]
# sanity check
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
log.error("Invalid namespace ID '%s'" % namespace_id)
return None
if len(namespace_id) <= 0 or len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
log.error("Invalid namespace of length %s" % len(namespace_id))
return None
return {
'opcode': 'NAMESPACE_READY',
'namespace_id': namespace_id
}
def get_fees( inputs, outputs ):
"""
Blockstack currently does not allow
the subsidization of namespaces.
"""
return (None, None)
'''

View File

@@ -21,22 +21,6 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 utilitybelt import is_hex
from binascii import hexlify, unhexlify
import types
import json
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
import virtualchain
log = virtualchain.get_logger("blockstack-log")
@@ -66,295 +50,3 @@ FIELDS = [
'no_vowel_discount', # multiplicative coefficient that drops a name's price if it has no vowels
]
'''
def serialize_int( int_field, numbytes ):
"""
Serialize an integer to a hex string that is padlen characters long.
Raise an exception on overflow.
"""
if int_field >= 2**(numbytes*8) or int_field < -(2**(numbytes*8)):
raise Exception("Integer overflow (%s bytes)" % (numbytes) )
format_str = "%%0.%sx" % (numbytes*2)
hex_str = format_str % int_field
if len(hex_str) % 2 != 0:
# sometimes python cuts off the leading zero
hex_str = '0' + hex_str
return hex_str
def serialize_buckets( bucket_exponents ):
"""
Serialize the list of bucket exponents.
There should be 16 buckets, and each one should have an integer between 0 and 15.
"""
ret = ""
for i in xrange(0, len(bucket_exponents)):
ret += "%X" % bucket_exponents[i]
return ret
def serialize_discounts( nonalpha_discount, no_vowel_discount ):
"""
Serialize the non-alpha and no-vowel discounts.
They must be between 0 and 15
"""
return "%X%X" % (nonalpha_discount, no_vowel_discount)
def namespacereveal_sanity_check( namespace_id, version, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount ):
"""
Verify the validity of a namespace reveal.
Return True if valid
Raise an Exception if not valid.
"""
# sanity check
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
raise Exception("Namespace ID '%s' has non-base-38 characters" % namespace_id)
if len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
raise Exception("Invalid namespace ID length for '%s' (expected length between 1 and %s)" % (namespace_id, LENGTHS['blockchain_id_namespace_id']))
if lifetime < 0 or lifetime > (2**32 - 1):
lifetime = NAMESPACE_LIFE_INFINITE
if coeff < 0 or coeff > 255:
raise Exception("Invalid cost multiplier %s: must be in range [0, 256)" % coeff)
if base < 0 or base > 255:
raise Exception("Invalid base price %s: must be in range [0, 256)" % base)
if len(bucket_exponents) != 16:
raise Exception("Exactly 16 buckets required")
for i in xrange(0, len(bucket_exponents)):
if bucket_exponents[i] < 0 or bucket_exponents[i] > 15:
raise Exception("Invalid bucket exponent %s (must be in range [0, 16)" % bucket_exponents[i])
if nonalpha_discount <= 0 or nonalpha_discount > 15:
raise Exception("Invalid non-alpha discount %s: must be in range [0, 16)" % nonalpha_discount)
if no_vowel_discount <= 0 or no_vowel_discount > 15:
raise Exception("Invalid no-vowel discount %s: must be in range [0, 16)" % no_vowel_discount)
return True
# 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 ):
"""
Record to mark the beginning of a namespace import in the blockchain.
This reveals the namespace ID, and encodes the preorder's namespace rules.
The rules for a namespace are as follows:
* a name can fall into one of 16 buckets, measured by length. Bucket 16 incorporates all names at least 16 characters long.
* the pricing structure applies a multiplicative penalty for having numeric characters, or punctuation characters.
* the price of a name in a bucket is ((coeff) * (base) ^ (bucket exponent)) / ((numeric discount multiplier) * (punctuation discount multiplier))
Example:
base = 10
coeff = 2
nonalpha discount: 10
no-vowel discount: 10
buckets 1, 2: 9
buckets 3, 4, 5, 6: 8
buckets 7, 8, 9, 10, 11, 12, 13, 14: 7
buckets 15, 16+:
The price of "john" would be 2 * 10^8, since "john" falls into bucket 4 and has no punctuation or numerics.
The price of "john1" would be 2 * 10^6, since "john1" falls into bucket 5 but has a number (and thus receives a 10x discount)
The price of "john_1" would be 2 * 10^6, since "john_1" falls into bucket 6 but has a number and puncuation (and thus receives a 10x discount)
The price of "j0hn_1" would be 2 * 10^5, since "j0hn_1" falls into bucket 6 but has a number and punctuation and lacks vowels (and thus receives a 100x discount)
Namespace ID must be base38.
Format:
0 2 3 7 8 9 10 11 12 13 14 15 16 17 18 20 39
|-----|---|--------|-----|-----|----|----|----|----|----|-----|-----|-----|--------|----------|-------------------------|
magic op life coeff. base 1-2 3-4 5-6 7-8 9-10 11-12 13-14 15-16 nonalpha version namespace ID
bucket exponents no-vowel
discounts
"""
rc = namespacereveal_sanity_check( namespace_id, version, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount )
if not rc:
raise Exception("Invalid namespace parameters")
# good to go!
life_hex = serialize_int( lifetime, 4 )
coeff_hex = serialize_int( coeff, 1 )
base_hex = serialize_int( base, 1 )
bucket_hex = serialize_buckets( bucket_exponents )
discount_hex = serialize_discounts( nonalpha_discount, no_vowel_discount )
version_hex = serialize_int( version, 2 )
namespace_id_hex = hexlify( namespace_id )
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)
return packaged_script
def make_outputs( data, inputs, reveal_addr, change_addr, format='bin', testset=False ):
"""
Make outputs for a register:
[0] OP_RETURN with the name
[1] pay-to-address with the *reveal_addr*, not the sender's address.
[2] change address with the NAMESPACE_PREORDER sender's address
"""
total_to_send = DEFAULT_OP_RETURN_FEE + DEFAULT_DUST_FEE
return [
# main output
{"script_hex": make_op_return_script(data, format=format),
"value": 0},
# register address
{"script_hex": make_pay_to_address_script(reveal_addr),
"value": DEFAULT_DUST_FEE},
# change address
{"script_hex": make_pay_to_address_script(change_addr),
"value": calculate_change_amount(inputs, total_to_send, DEFAULT_DUST_FEE * (len(inputs) + 3))},
]
def broadcast( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, private_key, blockchain_client, user_public_key=None, blockchain_broadcaster=None, testset=False ):
"""
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
"""
tx_only = False
# TODO: support tx_only
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
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)
# build custom outputs here
outputs = make_outputs(nulldata, inputs, reveal_addr, from_address, format='hex')
if tx_only:
unsigned_tx = serialize_transaction( 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)
# response = {'success': True }
response.update({'data': nulldata})
return response
def parse( bin_payload, sender, recipient_address ):
"""
NOTE: the first three bytes will be missing
"""
off = 0
life = None
coeff = None
base = None
bucket_hex = None
buckets = []
discount_hex = None
nonalpha_discount = None
no_vowel_discount = None
version = None
namespace_id = None
namespace_id_hash = None
life = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_life']]), 16 )
off += LENGTHS['blockchain_id_namespace_life']
coeff = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_coeff']]), 16 )
off += LENGTHS['blockchain_id_namespace_coeff']
base = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_base']]), 16 )
off += LENGTHS['blockchain_id_namespace_base']
bucket_hex = hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_buckets']])
off += LENGTHS['blockchain_id_namespace_buckets']
discount_hex = hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_discounts']])
off += LENGTHS['blockchain_id_namespace_discounts']
version = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_version']]), 16)
off += LENGTHS['blockchain_id_namespace_version']
namespace_id = bin_payload[off:]
namespace_id_hash = None
try:
namespace_id_hash = hash_name( namespace_id, sender, register_addr=recipient_address )
except:
log.error("Invalid namespace ID and/or sender")
return None
# extract buckets
buckets = [int(x, 16) for x in list(bucket_hex)]
# extract discounts
nonalpha_discount = int( list(discount_hex)[0], 16 )
no_vowel_discount = int( list(discount_hex)[1], 16 )
try:
rc = namespacereveal_sanity_check( namespace_id, version, life, coeff, base, buckets, nonalpha_discount, no_vowel_discount )
if not rc:
raise Exception("Invalid namespace parameters")
except Exception, e:
log.error("Invalid namespace parameters")
return None
return {
'opcode': 'NAMESPACE_REVEAL',
'lifetime': life,
'coeff': coeff,
'base': base,
'buckets': buckets,
'version': version,
'nonalpha_discount': nonalpha_discount,
'no_vowel_discount': no_vowel_discount,
'namespace_id': namespace_id,
'namespace_id_hash': namespace_id_hash
}
def get_fees( inputs, outputs ):
"""
Blockstack currently does not allow
the subsidization of namespaces.
"""
return (None, None)
'''

View File

@@ -21,22 +21,6 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 pybitcoin.transactions.outputs import calculate_change_amount
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
from ..b40 import b40_to_hex, is_b40
from ..config import *
from ..scripts import *
from ..hashing import hash_name
# consensus hash fields (ORDER MATTERS!)
FIELDS = [
'preorder_name_hash', # hash(name,sender,register_addr)
@@ -52,184 +36,3 @@ FIELDS = [
'op_fee', # blockstack fee (sent to burn address)
]
'''
def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None, testset=False):
"""
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
caller is not on a shorter fork).
Returns a NAME_PREORDER script.
Record format:
0 2 3 23 39
|-----|--|----------------------------------------------|--------------|
magic op hash(name.ns_id,script_pubkey,register_addr) consensus hash
"""
if name_hash is None:
# expect inputs to the hash
if not is_b40( name ) or "+" in name or name.count(".") > 1:
raise Exception("Name '%s' has non-base-38 characters" % name)
# name itself cannot exceed LENGTHS['blockchain_id_name']
if len(NAME_SCHEME) + len(name) > LENGTHS['blockchain_id_name']:
raise Exception("Name '%s' is too long; exceeds %s bytes" % (name, LENGTHS['blockchain_id_name'] - len(NAME_SCHEME)))
name_hash = hash_name(name, script_pubkey, register_addr=register_addr)
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)
return packaged_script
def make_outputs( data, inputs, sender_addr, fee, format='bin' ):
"""
Make outputs for a name preorder:
[0] OP_RETURN with the name
[1] address with the NAME_PREORDER sender's address
[2] pay-to-address with the *burn address* with the fee
"""
op_fee = max(fee, DEFAULT_DUST_FEE)
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
dust_value = DEFAULT_DUST_FEE
bill = op_fee
return [
# main output
{"script_hex": make_op_return_script(data, format=format),
"value": 0},
# change address (can be subsidy key)
{"script_hex": make_pay_to_address_script(sender_addr),
"value": calculate_change_amount(inputs, bill, dust_fee)},
# burn address
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
"value": op_fee}
]
def broadcast(name, private_key, register_addr, consensus_hash, blockchain_client, fee, blockchain_broadcaster=None, user_public_key=None, testset=False):
"""
Builds and broadcasts a preorder transaction.
"""
tx_only = False
if user_public_key is not None:
# subsidizing, and only want the tx
tx_only = True
# sanity check
if user_public_key is None and private_key is None:
raise Exception("Missing both client public and private key")
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None # change address
inputs = None
private_key_obj = None
script_pubkey = None # to be mixed into preorder hash
if user_public_key is not None:
# tx only
pubk = BitcoinPublicKey( user_public_key )
from_address = BitcoinPublicKey( user_public_key ).address()
inputs = get_unspents( from_address, blockchain_client )
script_pubkey = get_script_pubkey( user_public_key )
else:
# ordering directly
pubk = BitcoinPrivateKey( private_key ).public_key()
public_key = pubk.to_hex()
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)
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 )
return {"unsigned_tx": unsigned_tx}
else:
# serialize, sign, and broadcast the tx
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
response.update({'data': nulldata})
return response
def parse(bin_payload):
"""
Parse a name preorder.
NOTE: bin_payload *excludes* the leading 3 bytes (magic + op) returned by build.
"""
if len(bin_payload) != LENGTHS['preorder_name_hash'] + LENGTHS['consensus_hash']:
return None
name_hash = hexlify( bin_payload[0:LENGTHS['preorder_name_hash']] )
consensus_hash = hexlify( bin_payload[LENGTHS['preorder_name_hash']:] )
return {
'opcode': 'NAME_PREORDER',
'preorder_name_hash': name_hash,
'consensus_hash': consensus_hash
}
def get_fees( inputs, outputs ):
"""
Given a transaction's outputs, look up its fees:
* the first output must be an OP_RETURN, and it must have a fee of 0.
# the second must be the change address
* the third must be a burn fee to the burn address.
Return (dust fees, operation fees) on success
Return (None, None) on invalid output listing
"""
if len(outputs) != 3:
log.debug("Expected 3 outputs; got %s" % len(outputs))
return (None, None)
# 0: op_return
if not tx_output_is_op_return( outputs[0] ):
log.debug("outputs[0] is not an OP_RETURN")
return (None, None)
if outputs[0]["value"] != 0:
log.debug("outputs[0] has value %s'" % outputs[0]["value"])
return (None, None)
# 1: change address
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
log.error("outputs[1] has no decipherable change address")
return (None, None)
# 2: burn address
addr_hash = script_hex_to_address( outputs[2]["script_hex"] )
if addr_hash is None:
log.error("outputs[2] has no decipherable burn address")
return (None, None)
if addr_hash != BLOCKSTACK_BURN_ADDRESS:
log.error("outputs[2] is not the burn address")
return (None, None)
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = outputs[2]["value"]
return (dust_fee, op_fee)
'''

View File

@@ -21,24 +21,11 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 pybitcoin.transactions.outputs import calculate_change_amount
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 ..hashing import hash256_trunc128
from ..nameset import NAMEREC_FIELDS
import virtualchain
log = virtualchain.get_logger("blockstack-server")
from ..nameset import NAMEREC_FIELDS
# consensus hash fields (ORDER MATTERS!)
FIELDS = NAMEREC_FIELDS + [
'recipient', # scriptPubKey hex script that identifies the principal to own this name
@@ -78,234 +65,3 @@ def get_registration_recipient_from_outputs( outputs ):
return ret
'''
def build(name, testset=False):
"""
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.
Record format:
0 2 3 39
|----|--|-----------------------------|
magic op name.ns_id (37 bytes)
"""
if not is_name_valid( name ):
raise Exception("Invalid name '%s'" % name)
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)
return packaged_script
def make_outputs( data, change_inputs, register_addr, change_addr, renewal_fee=None, pay_fee=True, format='bin' ):
"""
Make outputs for a register:
[0] OP_RETURN with the name
[1] pay-to-address with the *register_addr*, not the sender's address.
[2] change address with the NAME_PREORDER sender's address
[3] (OPTIONAL) renewal fee, sent to the burn address
"""
dust_fee = None
dust_value = None
op_fee = None
bill = None
if pay_fee:
# sender pays
if renewal_fee is not None:
# renewing
dust_fee = (len(change_inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
dust_value = DEFAULT_DUST_FEE
op_fee = max(renewal_fee, DEFAULT_DUST_FEE)
bill = op_fee
else:
# registering
dust_fee = (len(change_inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
dust_value = DEFAULT_DUST_FEE
op_fee = 0
bill = DEFAULT_DUST_FEE * 2
else:
# subsidized by another address
if renewal_fee is not None:
# renewing
dust_fee = 0
dust_value = 0
op_fee = max(renewal_fee, DEFAULT_DUST_FEE)
bill = 0
else:
# registering
dust_fee = 0
dust_value = 0
op_fee = 0
bill = 0
outputs = [
# main output
{"script_hex": make_op_return_script(data, format=format),
"value": 0},
# register address
{"script_hex": make_pay_to_address_script(register_addr),
"value": dust_fee},
# change address (can be the subsidy address)
{"script_hex": make_pay_to_address_script(change_addr),
"value": calculate_change_amount(change_inputs, bill, dust_fee)},
]
if renewal_fee is not None:
outputs.append(
# burn address (when renewing)
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
"value": op_fee}
)
return outputs
def broadcast(name, private_key, register_addr, blockchain_client, renewal_fee=None, blockchain_broadcaster=None, user_public_key=None, subsidy_public_key=None, testset=False):
# sanity check
tx_only = False
if subsidy_public_key is not None or user_public_key is not None:
tx_only = True
if subsidy_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
if not tx_only and private_key is None:
raise Exception("Need private key for broadcasting")
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()
subsidized_renewal = True
else:
# registering or renewing under the subsidy key
from_address = pubk.address()
change_inputs = get_unspents( from_address, blockchain_client )
elif user_public_key is not None:
pubk = BitcoinPublicKey( user_public_key )
from_address = pubk.address()
change_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, change_inputs = analyze_private_key(private_key, 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 )
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)
response.update({'data': nulldata})
return response
def parse(bin_payload):
"""
Interpret a block's nulldata back into a name. The first three bytes (2 magic + 1 opcode)
will not be present in bin_payload.
The name will be directly represented by the bytes given.
"""
fqn = bin_payload
if not is_name_valid( fqn ):
return None
return {
'opcode': 'NAME_REGISTRATION',
'name': fqn
}
def get_fees( inputs, outputs ):
"""
Given a transaction's outputs, look up its fees:
* the first output must be an OP_RETURN, and it must have a fee of 0.
* the second output must be the reveal address, and it must have a dust fee
* the third must be the change address
* the fourth, if given, must be a burned fee sent to the burn address
Return (dust fees, operation fees) on success
Return (None, None) on invalid output listing
"""
dust_fee = 0
op_fee = 0
if len(outputs) != 3 and len(outputs) != 4:
return (None, None)
# 0: op_return
if not tx_output_is_op_return( outputs[0] ):
return (None, None)
if outputs[0]["value"] != 0:
return (None, None)
# 1: reveal address
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
return (None, None)
# 2: change address
if script_hex_to_address( outputs[2]["script_hex"] ) is None:
return (None, None)
# 3: burn address, if given
if len(outputs) == 4:
addr_hash = script_hex_to_address( outputs[3]["script_hex"] )
if addr_hash is None:
return (None, None)
if addr_hash != BLOCKSTACK_BURN_PUBKEY_HASH:
return (None, None)
dust_fee = (len(inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = outputs[3]["value"]
else:
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
return (dust_fee, op_fee)
'''

View File

@@ -21,170 +21,8 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 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 ..nameset import NAMEREC_FIELDS
# consensus hash fields (ORDER MATTERS!)
FIELDS = NAMEREC_FIELDS
'''
def build(name, testset=False):
"""
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.
Record format:
0 2 3 39
|----|--|-----------------------------|
magic op name.ns_id (37 bytes)
"""
if not is_name_valid( name ):
raise Exception("Invalid name '%s'" % name)
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)
return packaged_script
def make_outputs( data, inputs, change_address, pay_fee=True ):
"""
Make outputs for a revoke.
"""
dust_fee = None
op_fee = None
dust_value = None
if pay_fee:
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = DEFAULT_DUST_FEE
dust_value = DEFAULT_DUST_FEE
else:
# will be subsidized
dust_fee = 0
op_fee = 0
dust_value = 0
return [
# main output
{"script_hex": make_op_return_script(data, format='hex'),
"value": 0},
# change output
{"script_hex": make_pay_to_address_script(change_address),
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
]
def broadcast(name, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, subsidize=False, user_public_key=None ):
# sanity check
tx_only = False
pay_fee = True
if user_public_key is not None:
tx_only = True
if subsidize:
pay_fee = False
if user_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
if not tx_only and private_key is None:
raise Exception("Need private key for broadcasting")
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
if user_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( user_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(name, testset=testset)
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
if tx_only:
unsigned_tx = serialize_transaction( inputs, outputs )
return {'unsigned_tx': unsigned_tx}
else:
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
response.update({'data': nulldata})
return response
def parse(bin_payload):
"""
Interpret a block's nulldata back into a name. The first three bytes (2 magic + 1 opcode)
will not be present in bin_payload.
The name will be directly represented by the bytes given.
"""
fqn = bin_payload
if not is_name_valid( fqn ):
return None
return {
'opcode': 'NAME_REVOKE',
'name': fqn
}
def get_fees( inputs, outputs ):
"""
Given a transaction's outputs, look up its fees:
* there should be two outputs: the OP_RETURN and change address
Return (dust fees, operation fees) on success
Return (None, None) on invalid output listing
"""
if len(outputs) != 2:
return (None, None)
# 0: op_return
if not tx_output_is_op_return( outputs[0] ):
return (None, None)
if outputs[0]["value"] != 0:
return (None, None)
# 1: change address
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
return (None, None)
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = 0
return (dust_fee, op_fee)
'''

View File

@@ -21,19 +21,6 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 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 ..hashing import hash256_trunc128
from ..nameset import NAMEREC_FIELDS
# consensus hash fields (ORDER MATTERS!)
@@ -72,209 +59,3 @@ def get_transfer_recipient_from_outputs( outputs ):
return ret
'''
def transfer_sanity_check( name, consensus_hash ):
"""
Verify that data for a transfer is valid.
Return True on success
Raise Exception on error
"""
if name is not None and (not is_b40( name ) or "+" in name or name.count(".") > 1):
raise Exception("Name '%s' has non-base-38 characters" % name)
# without the scheme, name must be 37 bytes
if name is not None and (len(name) > LENGTHS['blockchain_id_name']):
raise Exception("Name '%s' is too long; expected %s bytes" % (name, LENGTHS['blockchain_id_name']))
return True
def build(name, keepdata, consensus_hash, testset=False):
"""
Takes in a name to transfer. Name must include the namespace ID, but not the scheme.
Record format:
0 2 3 4 20 36
|-----|--|----|-------------------|---------------|
magic op keep hash128(name.ns_id) consensus hash
data?
"""
rc = transfer_sanity_check( name, consensus_hash )
if not rc:
raise Exception("Invalid transfer data")
data_disposition = None
if keepdata:
data_disposition = TRANSFER_KEEP_DATA
else:
data_disposition = TRANSFER_REMOVE_DATA
name_hash = hash256_trunc128( name )
disposition_hex = hexlify(data_disposition)
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)
return packaged_script
def make_outputs( data, inputs, new_name_owner_address, change_address, pay_fee=True, format='bin' ):
"""
Builds the outputs for a name transfer operation.
"""
dust_fee = None
op_fee = None
dust_value = DEFAULT_DUST_FEE
if pay_fee:
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = DEFAULT_DUST_FEE
else:
dust_fee = 0
op_fee = 0
return [
# main output
{"script_hex": make_op_return_script(data, format=format),
"value": 0},
# new name owner output
{"script_hex": make_pay_to_address_script(new_name_owner_address),
"value": dust_value},
# change output
{"script_hex": make_pay_to_address_script(change_address),
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
]
def broadcast(name, destination_address, keepdata, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, subsidize=False, user_public_key=None, testset=False):
# sanity check
tx_only = False
if user_public_key is not None:
tx_only = True
pay_fee = True
if subsidize:
pay_fee = False
if user_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
if not tx_only and private_key is None:
raise Exception("Need private key for broadcasting")
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
if user_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( user_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(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 )
return {"unsigned_tx": unsigned_tx}
else:
# serialize, sign, and broadcast the tx
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
response.update({'data': nulldata})
return response
def parse(bin_payload, recipient):
"""
# NOTE: first three bytes were stripped
"""
if len(bin_payload) != 1 + LENGTHS['name_hash'] + LENGTHS['consensus_hash']:
log.error("Invalid transfer payload length %s" % len(bin_payload))
return None
disposition_char = bin_payload[0:1]
name_hash = bin_payload[1:1+LENGTHS['name_hash']]
consensus_hash = bin_payload[1+LENGTHS['name_hash']:]
if disposition_char not in [TRANSFER_REMOVE_DATA, TRANSFER_KEEP_DATA]:
log.error("Invalid disposition character")
return None
# keep data by default
disposition = True
if disposition_char == TRANSFER_REMOVE_DATA:
disposition = False
try:
rc = transfer_sanity_check( None, consensus_hash )
if not rc:
raise Exception("Invalid transfer data")
except Exception, e:
log.error("Invalid transfer data")
return None
return {
'opcode': 'NAME_TRANSFER',
'name_hash': hexlify( name_hash ),
'consensus_hash': hexlify( consensus_hash ),
'recipient': recipient,
'keep_data': disposition
}
def get_fees( inputs, outputs ):
"""
Given a transaction's outputs, look up its fees:
* the first output should be an OP_RETURN with the transfer info
* the second output should be the new owner's address, with a DEFAULT_DUST_FEE
* the third output should be the change address
Return (dust fees, operation fees) on success
Return (None, None) on invalid output listing
"""
if len(outputs) != 3:
return (None, None)
# 0: op_return
if not tx_output_is_op_return( outputs[0] ):
return (None, None)
if outputs[0]["value"] != 0:
return (None, None)
# 1: transfer address
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
return (None, None)
# 2: change address
if script_hex_to_address( outputs[2]["script_hex"] ) is None:
return (None, None)
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = DEFAULT_DUST_FEE
return (dust_fee, op_fee)
'''

View File

@@ -21,225 +21,14 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
"""
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 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 ..hashing import hash256_trunc128
from ..nameset import NAMEREC_FIELDS
import virtualchain
log = virtualchain.get_logger("blockstack-server")
from ..nameset import NAMEREC_FIELDS
# consensus hash fields (ORDER MATTERS!)
FIELDS = NAMEREC_FIELDS + [
'name_hash', # hash(name,consensus_hash)
'consensus_hash' # consensus hash when this update was sent
]
'''
def update_sanity_test( name, consensus_hash, data_hash ):
"""
Verify the validity of an update's data
Return True if valid
Raise exception if not
"""
if name is not None and (not is_b40( name ) or "+" in name or name.count(".") > 1):
raise Exception("Name '%s' has non-base-38 characters" % name)
if data_hash is not None and not is_hex( data_hash ):
raise Exception("Invalid hex string '%s': not hex" % (data_hash))
if len(data_hash) != 2 * LENGTHS['update_hash']:
raise Exception("Invalid hex string '%s': bad length" % (data_hash))
return True
def build(name, consensus_hash, data_hash=None, testset=False):
"""
Takes in the name to update the data for and the data update itself.
Name must include the namespace ID, but not the scheme.
Record format:
0 2 3 19 39
|-----|--|-----------------------------------|-----------------------|
magic op hash128(name.ns_id,consensus hash) hash160(data)
"""
rc = update_sanity_test( name, consensus_hash, data_hash )
if not rc:
raise Exception("Invalid update data")
hex_name = hash256_trunc128( name + consensus_hash )
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)
return packaged_script
def make_outputs( data, inputs, change_address, pay_fee=True ):
"""
Make outputs for an update.
"""
dust_fee = None
op_fee = None
dust_value = None
if pay_fee:
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = DEFAULT_DUST_FEE
dust_value = DEFAULT_DUST_FEE
else:
# will be subsidized
dust_fee = 0
op_fee = 0
dust_value = 0
return [
# main output
{"script_hex": make_op_return_script(data, format='hex'),
"value": 0},
# change output
{"script_hex": make_pay_to_address_script(change_address),
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
]
def broadcast(name, data_hash, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, subsidize=False, user_public_key=None, testset=False):
"""
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.
"""
# sanity check
pay_fee = True
tx_only = False
if user_public_key is not None:
tx_only = True
if subsidize:
pay_fee = False
if user_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
if not tx_only and private_key is None:
raise Exception("Need private key for broadcasting")
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
if user_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( user_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(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 )
return {'unsigned_tx': unsigned_tx}
else:
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
response.update({'data': nulldata})
return response
def parse(bin_payload):
"""
Parse a payload to get back the name and update hash.
NOTE: bin_payload excludes the leading three bytes.
"""
if len(bin_payload) != LENGTHS['name_hash'] + LENGTHS['data_hash']:
log.error("Invalid update length %s" % len(bin_payload))
return None
name_hash_bin = bin_payload[:LENGTHS['name_hash']]
update_hash_bin = bin_payload[LENGTHS['name_hash']:]
name_hash = hexlify( name_hash_bin )
update_hash = hexlify( update_hash_bin )
try:
rc = update_sanity_test( None, name_hash, update_hash )
if not rc:
raise Exception("Invalid update data")
except Exception, e:
log.error("Invalid update data")
return None
return {
'opcode': 'NAME_UPDATE',
'name_hash': name_hash,
'update_hash': update_hash
}
def get_fees( inputs, outputs ):
"""
Given a transaction's outputs, look up its fees:
* there should be two outputs: the OP_RETURN and change address
Return (dust fees, operation fees) on success
Return (None, None) on invalid output listing
"""
if len(outputs) != 2:
return (None, None)
# 0: op_return
if not tx_output_is_op_return( outputs[0] ):
return (None, None)
if outputs[0]["value"] != 0:
return (None, None)
# 1: change address
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
return (None, None)
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
op_fee = 0
return (dust_fee, op_fee)
def serialize( nameop ):
"""
Convert the set of data obtained from parsing the update into a unique string.
"""
return NAME_UPDATE + ":" + str(nameop['name_hash']) + "," + str(nameop['update_hash'])
'''