def blockstore_script_to_hex(script): """ Parse the readable version of a script, return the hex version. """ hex_script = '' parts = script.split(' ') for part in parts: if part.startswith("NAME_") or part.startswith("NAMESPACE_"): try: hex_script += '%0.2x' % ord(eval(part)) except: raise Exception('Invalid opcode: %s' % part) elif part.startswith("0x"): # literal hex string hex_script += part[2:] elif is_valid_int(part): hex_part = '%0.2x' % int(part) if len(hex_part) % 2 != 0: hex_part = '0' + hex_part hex_script += hex_part elif is_hex(part) and len(part) % 2 == 0: hex_script += part else: raise ValueError('Invalid script (at %s), contains invalid characters: %s' % (part, script)) if len(hex_script) % 2 != 0: raise ValueError('Invalid script: must have an even number of chars (got %s).' % hex_script) return hex_script
def parse_nameop_data(data): if not is_hex(data): raise ValueError('Data must be hex') # if not len(data) <= OP_RETURN_MAX_SIZE*2: # raise ValueError('Payload too large') if not len(data) % 2 == 0: # raise ValueError('Data must have an even number of bytes') return None try: bin_data = unhexlify(data) except: raise Exception('Invalid data supplied: %s' % data) magic_bytes, opcode, payload = bin_data[0:2], bin_data[2:3], bin_data[3:] if not magic_bytes == MAGIC_BYTES: # Magic bytes don't match - not an openname operation. return None if opcode == NAME_PREORDER and len(payload) >= MIN_OP_LENGTHS['preorder']: nameop = parse_preorder(payload) elif (opcode == NAME_REGISTRATION and len(payload) >= MIN_OP_LENGTHS['registration']): nameop = parse_registration(payload) elif opcode == NAME_UPDATE and len(payload) >= MIN_OP_LENGTHS['update']: nameop = parse_update(payload) elif (opcode == NAME_TRANSFER and len(payload) >= MIN_OP_LENGTHS['transfer']): nameop = parse_transfer(payload) else: nameop = None return nameop
def get_public_key_format(public_key_string): if not isinstance(public_key_string, str): raise ValueError('Public key must be a string.') if len(public_key_string) == 64: return CharEncoding.bin, PubkeyType.ecdsa if (len(public_key_string) == 65 and public_key_string[0] == PUBKEY_MAGIC_BYTE): return CharEncoding.bin, PubkeyType.uncompressed if len(public_key_string) == 33: return CharEncoding.bin, PubkeyType.compressed if is_hex(public_key_string): if len(public_key_string) == 128: return CharEncoding.hex, PubkeyType.ecdsa if (len(public_key_string) == 130 and public_key_string[0:2] == hexlify(PUBKEY_MAGIC_BYTE)): return CharEncoding.hex, PubkeyType.uncompressed if len(public_key_string) == 66: return CharEncoding.hex, PubkeyType.compressed raise ValueError(_errors['IMPROPER_PUBLIC_KEY_FORMAT'])
def make_transaction(message_hash, payment_addr, blockchain_client, tx_fee=0, subsidize=False, safety=True): message_hash = str(message_hash) payment_addr = str(payment_addr) tx_fee = int(tx_fee) # sanity check 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") inputs = None private_key_obj = None inputs = tx_get_unspents(payment_addr, blockchain_client) if safety: assert len(inputs) > 0 nulldata = build(message_hash) outputs = make_outputs(nulldata, inputs, payment_addr, tx_fee, pay_fee=(not subsidize)) return (inputs, outputs)
def blockstack_script_to_hex(script): """ Parse the readable version of a script, return the hex version. """ hex_script = '' parts = script.split(' ') for part in parts: if part in NAME_OPCODES.keys(): try: hex_script += '%0.2x' % ord(NAME_OPCODES[part]) except: raise Exception('Invalid opcode: %s' % part) elif part.startswith("0x"): # literal hex string hex_script += part[2:] elif is_valid_int(part): hex_part = '%0.2x' % int(part) if len(hex_part) % 2 != 0: hex_part = '0' + hex_part hex_script += hex_part elif is_hex(part) and len(part) % 2 == 0: hex_script += part else: raise ValueError('Invalid script (at %s), contains invalid characters: %s' % (part, script)) if len(hex_script) % 2 != 0: raise ValueError('Invalid script: must have an even number of chars (got %s).' % hex_script) return hex_script
def load_verifying_key(verifying_key, crypto_backend=default_backend()): """ Optional: crypto backend object from the "cryptography" python library """ if not isinstance(crypto_backend, (Backend, MultiBackend)): raise ValueError('backend must be a valid Backend object') if isinstance(verifying_key, EllipticCurvePublicKey): return verifying_key elif isinstance(verifying_key, (str, unicode)): if is_hex(verifying_key): try: public_key_pem = ECPublicKey(verifying_key).to_pem() except: pass else: try: return load_pem_public_key(public_key_pem, backend=crypto_backend) except Exception as e: traceback.print_exc() raise InvalidPublicKeyError() try: return load_der_public_key(verifying_key, backend=crypto_backend) except: raise InvalidPublicKeyError() else: try: return load_pem_public_key(verifying_key, backend=crypto_backend) except Exception as e: raise InvalidPublicKeyError() else: raise ValueError('Verifying key must be in string or unicode format.')
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) """ if not is_b40(name) or "+" in name or name.count(".") > 1: raise Exception("Name '%s' has non-base-38 characters" % name) if 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)) hex_name = hash256_trunc128(name + consensus_hash) readable_script = 'NAME_UPDATE 0x%s 0x%s' % (hex_name, data_hash) hex_script = blockstore_script_to_hex(readable_script) packaged_script = add_magic_bytes(hex_script, testset=testset) return packaged_script
def get_public_key_format(public_key_string): if not isinstance(public_key_string, str): raise ValueError('Public key must be a string.') if len(public_key_string) == 64: return CharEncoding.bin, PubkeyType.ecdsa if (len(public_key_string) == 65 and public_key_string[0] == PUBLIC_KEY_MAGIC_BYTE): return CharEncoding.bin, PubkeyType.uncompressed if len(public_key_string) == 33: return CharEncoding.bin, PubkeyType.compressed if is_hex(public_key_string): if len(public_key_string) == 128: return CharEncoding.hex, PubkeyType.ecdsa if (len(public_key_string) == 130 and public_key_string[0:2] == hexlify(PUBLIC_KEY_MAGIC_BYTE)): return CharEncoding.hex, PubkeyType.uncompressed if len(public_key_string) == 66: return CharEncoding.hex, PubkeyType.compressed raise InvalidPublicKeyError()
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) """ if not is_b40( name ) or "+" in name or name.count(".") > 1: raise Exception("Name '%s' has non-base-38 characters" % name) if 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)) hex_name = hash256_trunc128( name + consensus_hash ) readable_script = 'NAME_UPDATE 0x%s 0x%s' % (hex_name, data_hash) hex_script = blockstore_script_to_hex(readable_script) packaged_script = add_magic_bytes(hex_script, testset=testset) return packaged_script
def zonefilemanage_script_to_hex(script): """ Parse the readable version of a script, return the hex version. """ hex_script = '' parts = script.split(' ') for part in parts: if part in NAME_OPCODES: try: hex_script += '{:02x}'.format(ord(NAME_OPCODES[part])) except: raise Exception('Invalid opcode: {}'.format(part)) elif part.startswith('0x'): # literal hex string hex_script += part[2:] elif is_valid_int(part): hex_part = '{:02x}'.format(int(part)) if len(hex_part) % 2 != 0: hex_part = '0' + hex_part hex_script += hex_part elif is_hex(part) and len(part) % 2 == 0: hex_script += part else: raise ValueError( 'Invalid script (at {}), contains invalid characters: {}'. format(part, script)) if len(hex_script) % 2 != 0: raise ValueError( 'Invalid script: must have an even number of chars (got {}).'. format(hex_script)) return hex_script
def broadcast(message_hash, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, user_public_key=None, tx_only=False): # sanity check pay_fee = True if user_public_key is not None: pay_fee = False tx_only = True 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: # 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(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 serialize_input(input, signature_script_hex=''): """ Serializes a transaction input. """ if not (isinstance(input, dict) and 'transaction_hash' in input \ and 'output_index' in input): raise Exception('Required parameters: transaction_hash, output_index') if is_hex(str(input['transaction_hash'])) and len(str(input['transaction_hash'])) != 64: raise Exception("Transaction hash '%s' must be 32 bytes" % input['transaction_hash']) elif not is_hex(str(input['transaction_hash'])) and len(str(input['transaction_hash'])) != 32: raise Exception("Transaction hash '%s' must be 32 bytes" % hexlify(input['transaction_hash'])) if not 'sequence' in input: input['sequence'] = UINT_MAX return ''.join([ flip_endian(input['transaction_hash']), hexlify(struct.pack('<I', input['output_index'])), hexlify(variable_length_int(len(signature_script_hex)/2)), signature_script_hex, hexlify(struct.pack('<I', input['sequence'])) ])
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 load_signing_key(signing_key, crypto_backend=default_backend()): """ Optional: crypto backend object from the "cryptography" python library """ if not isinstance(crypto_backend, (Backend, MultiBackend)): raise ValueError('backend must be a valid Backend object') if isinstance(signing_key, EllipticCurvePrivateKey): return signing_key elif isinstance(signing_key, (str, unicode)): invalid_strings = [b'-----BEGIN PUBLIC KEY-----'] invalid_string_matches = [ string_value in signing_key for string_value in invalid_strings ] if any(invalid_string_matches): raise ValueError( 'Signing key must be a private key, not a public key.') if is_hex(signing_key): try: private_key_pem = ECPrivateKey(signing_key).to_pem() except: pass else: try: return load_pem_private_key(private_key_pem, password=None, backend=crypto_backend) except: raise InvalidPrivateKeyError() try: return load_der_private_key(signing_key, password=None, backend=crypto_backend) except Exception as e: traceback.print_exc() raise InvalidPrivateKeyError() else: try: return load_pem_private_key(signing_key, password=None, backend=crypto_backend) except: raise InvalidPrivateKeyError() else: raise ValueError('Signing key must be in string or unicode format.')
def make_op_return_script(data, format='bin'): """ Takes in raw ascii data to be embedded and returns a script. """ if format == 'hex': assert(is_hex(data)) hex_data = data elif format == 'bin': hex_data = hexlify(data) else: raise Exception("Format must be either 'hex' or 'bin'") num_bytes = count_bytes(hex_data) if num_bytes > MAX_BYTES_AFTER_OP_RETURN: raise Exception('Data is %i bytes - must not exceed 40.' % num_bytes) script_string = 'OP_RETURN %s' % hex_data return script_to_hex(script_string)
def make_op_return_script(data, format='bin'): """ Takes in raw ascii data to be embedded and returns a script. """ if format == 'hex': assert (is_hex(data)) hex_data = data elif format == 'bin': hex_data = hexlify(data) else: raise Exception("Format must be either 'hex' or 'bin'") num_bytes = count_bytes(hex_data) if num_bytes > MAX_BYTES_AFTER_OP_RETURN: raise Exception('Data is %i bytes - must not exceed 40.' % num_bytes) script_string = 'OP_RETURN %s' % hex_data return script_to_hex(script_string)
def build(name, data_hash=None, data=None, testset=False): """ Takes in the name to update the data for and the data update itself. """ hex_name = b40_to_hex(name) name_len = len(hex_name) / 2 if not data_hash: if not data: raise ValueError('A data hash or data string is required.') data_hash = hex_hash160(data) elif not (is_hex(data_hash) and len(data_hash) == 40): raise ValueError('Data hash must be a 20 byte hex string.') readable_script = 'NAME_UPDATE %i %s %s' % (name_len, hex_name, data_hash) hex_script = name_script_to_hex(readable_script) packaged_script = add_magic_bytes(hex_script, testset=testset) return packaged_script
def build(name, data_hash=None, data=None, testset=False): """ Takes in the name to update the data for and the data update itself. """ hex_name = b40_to_hex(name) name_len = len(hex_name)/2 if not data_hash: if not data: raise ValueError('A data hash or data string is required.') data_hash = hex_hash160(data) elif not (is_hex(data_hash) and len(data_hash) == 40): raise ValueError('Data hash must be a 20 byte hex string.') readable_script = 'NAME_UPDATE %i %s %s' % (name_len, hex_name, data_hash) hex_script = name_script_to_hex(readable_script) packaged_script = add_magic_bytes(hex_script, testset=testset) return packaged_script
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 * LENGTH_VALUE_HASH: raise Exception("Invalid hex string '%s': bad length" % (data_hash)) return True
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 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 script_to_hex(script): """ Parse the string representation of a script and return the hex version. Example: "OP_DUP OP_HASH160 c629...a6db OP_EQUALVERIFY OP_CHECKSIG" """ hex_script = '' parts = script.split(' ') for part in parts: if part[0:3] == 'OP_': try: hex_script += '%0.2x' % eval(part) except: raise Exception('Invalid opcode: %s' % part) elif isinstance(part, (int)): hex_script += '%0.2x' % part elif is_hex(part): hex_script += '%0.2x' % count_bytes(part) + part else: raise Exception('Invalid script - only opcodes and hex characters allowed.') return hex_script
def script_to_hex(script): """ Parse the string representation of a script and return the hex version. Example: "OP_DUP OP_HASH160 c629...a6db OP_EQUALVERIFY OP_CHECKSIG" """ hex_script = '' parts = script.split(' ') for part in parts: if part[0:3] == 'OP_': try: hex_script += '%0.2x' % eval(part) except: raise Exception('Invalid opcode: %s' % part) elif isinstance(part, (int)): hex_script += '%0.2x' % part elif is_hex(part): hex_script += '%0.2x' % count_bytes(part) + part else: raise Exception( 'Invalid script - only opcodes and hex characters allowed.') return hex_script
def make_transaction(message_hash, payment_addr, blockchain_client, tx_fee=0): message_hash = str(message_hash) payment_addr = str(payment_addr) tx_fee = int(tx_fee) # sanity check 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") inputs = None private_key_obj = None inputs = get_unspents( payment_addr, blockchain_client ) nulldata = build(message_hash) outputs = make_outputs( nulldata, inputs, payment_addr, tx_fee ) return (inputs, outputs)
def parse_transaction(self, block_id, tx): """ Given a block ID and OP_RETURN transaction, try to parse it into a virtual chain operation """ op_return_hex = tx['nulldata'] inputs = tx['vin'] outputs = tx['vout'] senders = tx['senders'] if not is_hex(op_return_hex): return None if len(op_return_hex) % 2 != 0: return None try: op_return_bin = binascii.unhexlify(op_return_hex) except Exception, e: log.error("Failed to parse transaction: %s (OP_RETUAN= %s)" % (tx, op_return_hex)) raise e
def name_script_to_hex(script): """ Parse the readable version of a name script, return the hex version. """ hex_script = '' parts = script.split(' ') for part in parts: if part[0:5] == 'NAME_': try: hex_script += '%0.2x' % ord(eval(part)) except: raise Exception('Invalid opcode: %s' % part) elif is_hex(part) and len(part) % 2 == 0: hex_script += part elif is_valid_int(part): hex_script += '%0.2x' % int(part) else: raise ValueError( 'Invalid script, contains invalid characters: %s' % script) if len(hex_script) % 2 != 0: raise ValueError('Invalid script: must have an even number of chars.') return hex_script
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 = blockstore_script_to_hex(readable_script) packaged_script = add_magic_bytes(hex_script, testset=testset) return packaged_script
def parse_transaction(self, block_id, tx): """ Given a block ID and an OP_RETURN transaction, try to parse it into a virtual chain operation. Use the implementation's 'db_parse' method to do so. Set the following fields in op: * virtualchain_opcode: the operation code * virtualchain_outputs: the list of transaction outputs * virtualchain_senders: the list of transaction senders * virtualchain_fee: the total amount of money sent * virtualchain_block_number: the block ID in which this transaction occurred Return a dict representing the data on success. Return None on error """ op_return_hex = tx['nulldata'] inputs = tx['vin'] outputs = tx['vout'] senders = tx['senders'] fee = tx['fee'] if not is_hex(op_return_hex): # not a valid hex string return None if len(op_return_hex) % 2 != 0: # not valid hex string return None try: op_return_bin = binascii.unhexlify(op_return_hex) except Exception, e: log.error("Failed to parse transaction: %s (OP_RETURN = %s)" % (tx, op_return_hex)) raise e
def parse_transaction( self, block_id, tx ): """ Given a block ID and an OP_RETURN transaction, try to parse it into a virtual chain operation. Use the implementation's 'db_parse' method to do so. Set the following fields in op: * virtualchain_opcode: the operation code * virtualchain_outputs: the list of transaction outputs * virtualchain_senders: the list of transaction senders * virtualchain_fee: the total amount of money sent * virtualchain_block_number: the block ID in which this transaction occurred Return a dict representing the data on success. Return None on error """ op_return_hex = tx['nulldata'] inputs = tx['vin'] outputs = tx['vout'] senders = tx['senders'] fee = tx['fee'] if not is_hex(op_return_hex): # not a valid hex string return None if len(op_return_hex) % 2 != 0: # not valid hex string return None try: op_return_bin = binascii.unhexlify( op_return_hex ) except Exception, e: log.error("Failed to parse transaction: %s (OP_RETURN = %s)" % (tx, op_return_hex)) raise e
def make_transaction(message_hash, payment_addr, blockchain_client, tx_fee=0, subsidize=False, safety=True): message_hash = str(message_hash) payment_addr = str(payment_addr) tx_fee = int(tx_fee) # sanity check 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") inputs = None private_key_obj = None inputs = tx_get_unspents( payment_addr, blockchain_client ) if safety: assert len(inputs) > 0 nulldata = build(message_hash) outputs = make_outputs( nulldata, inputs, payment_addr, tx_fee, pay_fee=(not subsidize) ) return (inputs, outputs)
def bin_hash160(s, hex_format=False): """ s is in hex or binary format """ if hex_format and is_hex(s): s = unhexlify(s) return hashlib.new('ripemd160', bin_sha256(s)).digest()
def is_256bit_hex_string(val): return (isinstance(val, str) and len(val) == 64 and is_hex(val))
def hex_hash160(s, hex_format=False): """ s is in hex or binary format """ if hex_format and is_hex(s): s = unhexlify(s) return hexlify(bin_hash160(s))
def count_bytes(hex_s): """ Calculate the number of bytes of a given hex string. """ assert(is_hex(hex_s)) return len(hex_s)/2
def snv_lookup(verify_name, verify_block_id, trusted_serial_number_or_txid_or_consensus_hash, proxy=None, trusted_txid=None): """ High-level call to simple name verification: Given a trusted serial number, txid, or consensus_hash, use it as a trust root to verify that a previously-registered but untrusted name (@verify_name) exists and was processed at a given block (@verify_block_id) Basically, use the trust root to derive a "current" block ID and consensus hash, and use the untrusted (name, block_id) pair to derive an earlier untrusted block ID and consensus hash. Then, use the snv_get_nameops_at() method to verify that the name existed at the given block ID. The Blockstack node is not trusted. This algorithm prevents a malicious Blockstack node from getting the caller to falsely trust @verify_name and @verify_block_id by using SNV to confirm that: * the consensus hash at the trust root's block is consistent with @verify_name's corresponding NAMESPACE_PREORDER or NAME_PREORDER; * the consensus hash at @trusted_serial_number's block is consistent with @verify_name's consensus hash (from @verify_serial_number) The only way a Blockstack node working with a malicious Sybil can trick the caller is if both can create a parallel history of name operations such that the final consensus hash at @trusted_serial_number's block collides. This is necessary, since the client uses the hash over a block's operations and prior consensus hashes to transitively trust prior consensus hashes--if the later consensus hash is assumed out-of-band to be valid, then the transitive closure of all prior consensus hashes will be assumed valid as well. This means that the only way to drive the valid consensus hash from a prior invalid consensus hash is to force a hash collision somewhere in the transitive closure, which is infeasible. NOTE: @trusted_txid is needed for isolating multiple operations in the same name within a single block. Return the list of nameops in the given verify_block_id that match. """ proxy = get_default_proxy() if proxy is None else proxy trusted_serial_number_or_txid_or_consensus_hash = str( trusted_serial_number_or_txid_or_consensus_hash) bitcoind_proxy = get_bitcoind_client(config_path=proxy.conf['path']) trusted_serial_number = None trusted_tx_index = None trusted_consensus_hash = None trusted_block_id = None # what did we get? hash_len_64 = len(trusted_serial_number_or_txid_or_consensus_hash) == 64 hash_len_32 = len(trusted_serial_number_or_txid_or_consensus_hash) == 32 hash_parts_2 = len( trusted_serial_number_or_txid_or_consensus_hash.split('-')) == 2 hash_is_hex = is_hex(trusted_serial_number_or_txid_or_consensus_hash) if hash_len_64 and hash_is_hex: # txid: convert to trusted block ID and consensus hash trusted_txid = trusted_serial_number_or_txid_or_consensus_hash trusted_block_hash, trusted_block_data, trusted_tx = txid_to_block_data( trusted_txid, bitcoind_proxy) if trusted_block_hash is None or trusted_block_data is None or trusted_tx is None: return {'error': 'Unable to look up given transaction ID'} # must have a consensus hash # TOOD: Check why return values are ignored op, payload = parse_tx_op_return(trusted_tx) trusted_consensus_hash = get_consensus_hash_from_tx(trusted_tx) if trusted_consensus_hash is None: return { 'error': 'Tx does not refer to a consensus-bearing transaction' } # find the block for this consensus hash (it's not the same as the serial number's block ID, # but that's okay--if the consensus hash in this tx is inauthentic, it will be unreachable # from the other consensus hash [short of a SHA256 collision]) trusted_block_id = get_block_from_consensus(trusted_consensus_hash, proxy=proxy) elif hash_len_32 and hash_is_hex: # consensus hash trusted_consensus_hash = trusted_serial_number_or_txid_or_consensus_hash trusted_block_id = get_block_from_consensus(trusted_consensus_hash, proxy=proxy) if isinstance(trusted_block_id, dict) and 'error' in trusted_block_id: # got error back return trusted_block_id elif hash_parts_2: # must be a serial number parts = trusted_serial_number_or_txid_or_consensus_hash.split('-') try: trusted_block_id = int(parts[0]) # TODO: Check why this variable is unused trusted_tx_index = int(parts[1]) except: log.error('Malformed serial number "{}"'.format( trusted_serial_number_or_txid_or_consensus_hash)) return {'error': 'Did not receive a valid serial number'} trusted_tx = serial_number_to_tx( trusted_serial_number_or_txid_or_consensus_hash, bitcoind_proxy) if trusted_tx is None: return { 'error': 'Unable to convert given serial number into transaction' } # tx must have a consensus hash # TOOD: Check why return values are ignored op, payload = parse_tx_op_return(trusted_tx) trusted_consensus_hash = get_consensus_hash_from_tx(trusted_tx) if trusted_consensus_hash is None: return { 'error': 'Tx does not refer to a consensus-bearing transaction' } # find the block for this consensus hash (it's not the same as the serial number's block ID, # but that's okay--if the consensus hash in this tx is inauthentic, it will be unreachable # from the other consensus hash [short of a SHA256 collision]) trusted_block_id = get_block_from_consensus(trusted_consensus_hash, proxy=proxy) if isinstance(trusted_block_id, dict) and 'error' in trusted_block_id: # got error back return trusted_block_id else: msg = 'Did not receive a valid txid, consensus hash, or serial number ({})' return { 'error': msg.format(trusted_serial_number_or_txid_or_consensus_hash) } if trusted_block_id < verify_block_id: msg = 'Trusted block/consensus hash came before the untrusted block/consensus hash' return {'error': msg} # go verify the name verify_consensus_hash = get_consensus_at(verify_block_id, proxy=proxy) historic_namerecs = snv_name_verify(verify_name, trusted_block_id, trusted_consensus_hash, verify_block_id, verify_consensus_hash, trusted_txid=trusted_txid, trusted_txindex=trusted_tx_index) if 'error' in historic_namerecs: return historic_namerecs return historic_namerecs['nameops']
def is_hex_ecdsa_pubkey(val): return (is_hex(val) and len(val) == 128)
def test_is_hex_with_64bit_string(self): s = "d9fa02e46cd3867f51279dfae592d3706022ee93c175b49c30c8c962722fc890" self.assertTrue(is_hex(s))
def test_charset_to_hex(self): s = ("9859361987058468004536324485715460737416376519236217359320056" "4555477131708560") s2 = charset_to_hex(s, string.digits) self.assertTrue(is_hex(s2))
def snv_lookup(verify_name, verify_block_id, trusted_serial_number_or_txid_or_consensus_hash, proxy=None): """ High-level call to simple name verification: Given a trusted serial number, txid, or consensus_hash, use it as a trust root to verify that a previously-registered but untrusted name (@verify_name) exists and was processed at a given block (@verify_block_id) Basically, use the trust root to derive a "current" block ID and consensus hash, and use the untrusted (name, block_id) pair to derive an earlier untrusted block ID and consensus hash. Then, use the snv_get_nameops_at() method to verify that the name existed at the given block ID. The Blockstack node is not trusted. This algorithm prevents a malicious Blockstack node from getting the caller to falsely trust @verify_name and @verify_block_id by using SNV to confirm that: * the consensus hash at the trust root's block is consistent with @verify_name's corresponding NAMESPACE_PREORDER or NAME_PREORDER; * the consensus hash at @trusted_serial_number's block is consistent with @verify_name's consensus hash (from @verify_serial_number) The only way a Blockstack node working with a malicious Sybil can trick the caller is if both can create a parallel history of name operations such that the final consensus hash at @trusted_serial_number's block collides. This is necessary, since the client uses the hash over a block's operations and prior consensus hashes to transitively trust prior consensus hashes--if the later consensus hash is assumed out-of-band to be valid, then the transitive closure of all prior consensus hashes will be assumed valid as well. This means that the only way to drive the valid consensus hash from a prior invalid consensus hash is to force a hash collision somewhere in the transitive closure, which is infeasible. """ if proxy is None: proxy = get_default_proxy() trusted_serial_number_or_txid_or_consensus_hash = str(trusted_serial_number_or_txid_or_consensus_hash) bitcoind_proxy = get_bitcoind_client( config_path=proxy.conf['path'] ) trusted_serial_number = None trusted_txid = None trusted_consensus_hash = None trusted_block_id = None # what did we get? if len(trusted_serial_number_or_txid_or_consensus_hash) == 64 and is_hex(trusted_serial_number_or_txid_or_consensus_hash): # txid: convert to trusted block ID and consensus hash trusted_txid = trusted_serial_number_or_txid_or_consensus_hash trusted_block_hash, trusted_block_data, trusted_tx = txid_to_block_data(trusted_txid, bitcoind_proxy) if trusted_block_hash is None or trusted_block_data is None or trusted_tx is None: return {'error': 'Unable to look up given transaction ID'} # must have a consensus hash op, payload = parse_tx_op_return(trusted_tx) trusted_consensus_hash = get_consensus_hash_from_tx(trusted_tx) if trusted_consensus_hash is None: return {'error': 'Tx does not refer to a consensus-bearing transaction'} # find the block for this consensus hash (it's not the same as the serial number's block ID, # but that's okay--if the consensus hash in this tx is inauthentic, it will be unreachable # from the other consensus hash [short of a SHA256 collision]) trusted_block_id = get_block_from_consensus(trusted_consensus_hash, proxy=proxy) elif len(trusted_serial_number_or_txid_or_consensus_hash) == 32 and is_hex(trusted_serial_number_or_txid_or_consensus_hash): # consensus hash trusted_consensus_hash = trusted_serial_number_or_txid_or_consensus_hash trusted_block_id = get_block_from_consensus(trusted_consensus_hash, proxy=proxy) if type(trusted_block_id) == dict and 'error' in trusted_block_id: # got error back return trusted_block_id elif len(trusted_serial_number_or_txid_or_consensus_hash.split("-")) == 2: # must be a serial number parts = trusted_serial_number_or_txid_or_consensus_hash.split("-") try: trusted_block_id = int(parts[0]) trusted_tx_index = int(parts[1]) except: log.error("Malformed serial number '%s'" % trusted_serial_number_or_txid_or_consensus_hash) return {'error': 'Did not receive a valid serial number'} trusted_tx = serial_number_to_tx(trusted_serial_number_or_txid_or_consensus_hash, bitcoind_proxy) if trusted_tx is None: return {'error': 'Unable to convert given serial number into transaction'} # tx must have a consensus hash op, payload = parse_tx_op_return(trusted_tx) trusted_consensus_hash = get_consensus_hash_from_tx(trusted_tx) if trusted_consensus_hash is None: return {'error': 'Tx does not refer to a consensus-bearing transaction'} # find the block for this consensus hash (it's not the same as the serial number's block ID, # but that's okay--if the consensus hash in this tx is inauthentic, it will be unreachable # from the other consensus hash [short of a SHA256 collision]) trusted_block_id = get_block_from_consensus(trusted_consensus_hash, proxy=proxy) if type(trusted_block_id) == dict and 'error' in trusted_block_id: # got error back return trusted_block_id else: return {'error': 'Did not receive a valid txid, consensus hash, or serial number (%s)' % trusted_serial_number_or_txid_or_consensus_hash} if trusted_block_id < verify_block_id: return {'error': 'Trusted block/consensus hash came before the untrusted block/consensus hash'} # go verify the name verify_consensus_hash = get_consensus_at(verify_block_id, proxy=proxy) historic_namerec = snv_name_verify(verify_name, trusted_block_id, trusted_consensus_hash, verify_block_id, verify_consensus_hash) return historic_namerec
def is_256bit_hex_string(val): return isinstance(val, str) and len(val) == 64 and is_hex(val)
def is_hex_ecdsa_pubkey(val): return is_hex(val) and len(val) == 128