def test_verify_transaction(self): tx_input = Spendable(200, '18eKkAWyU9kvRNHPKxnZb6wwtPMrNmRRRA', h2b('8443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b154'), 0) tx_outs = [tx_utils.create_transaction_output('mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', 0.0000275)] op_return_val = h2b('e9cee71ab932fde863338d08be4de9dfe39ea049bdafb342ce659ec5450b69ae') tx = tx_utils.create_trx(op_return_val, 3, 'mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', tx_outs, tx_input) hextx = hexlify(tx.serialize()) tx_utils.verify_transaction(hextx, hexlify(op_return_val))
def test_verify_transaction(self): cost = TransactionCosts(0.0001, 0.0000275, 3) tx_input = Spendable(200, '18eKkAWyU9kvRNHPKxnZb6wwtPMrNmRRRA', h2b('8443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b154'), 0) tx_outs = [trx_utils.create_transaction_output('mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', 0.0000275)] op_return_val = h2b('e9cee71ab932fde863338d08be4de9dfe39ea049bdafb342ce659ec5450b69ae') tx = trx_utils.create_trx(op_return_val, cost, 'mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', tx_outs, tx_input) hextx = hexlify(tx.serialize()) trx_utils.verify_transaction(hextx, hexlify(op_return_val))
def issue_on_blockchain(self, wallet, revocation_address, split_input_trxs, allowable_wif_prefixes, broadcast_function, issuing_transaction_cost): trxs = self.create_transactions(wallet, revocation_address, issuing_transaction_cost, split_input_trxs) for td in trxs: # persist tx hextx = hexlify(td.tx.serialize()) with open(td.unsigned_tx_file_name, 'w') as out_file: out_file.write(hextx) # sign transaction and persist result signed_hextx = trx_utils.sign_tx(hextx, td.tx_input, allowable_wif_prefixes) with open(td.signed_tx_file_name, 'w') as out_file: out_file.write(signed_hextx) # verify cert_utils.verify_transaction(td.op_return_value, signed_hextx) # send tx and persist txid txid = trx_utils.send_tx(broadcast_function, signed_hextx) self.finish_tx(td.sent_tx_file_name, txid)
def create_transactions(self, wallet, revocation_address, issuing_transaction_cost, split_input_trxs): # finish tree self.tree.make_tree() op_return_value = unhexlify(self.tree.get_merkle_root()) unspent_outputs = wallet.get_unspent_outputs(self.issuing_address) last_output = unspent_outputs[-1] txouts = self.build_txouts(issuing_transaction_cost) txouts = txouts + [ trx_utils.create_transaction_output( revocation_address, issuing_transaction_cost.min_per_output) ] tx = trx_utils.create_trx(op_return_value, issuing_transaction_cost, self.issuing_address, txouts, last_output) unsigned_tx_file_name = convert_file_name( self.config.unsigned_txs_file_pattern, self.batch_id) unsent_tx_file_name = convert_file_name( self.config.signed_txs_file_pattern, self.batch_id) sent_tx_file_name = convert_file_name( self.config.sent_txs_file_pattern, self.batch_id) td = TransactionData(uid=self.batch_id, tx=tx, tx_input=last_output, op_return_value=hexlify(op_return_value), unsigned_tx_file_name=unsigned_tx_file_name, signed_tx_file_name=unsent_tx_file_name, sent_tx_file_name=sent_tx_file_name) return [td]
def test_create_trx(self): tx_input = Spendable(200, '18eKkAWyU9kvRNHPKxnZb6wwtPMrNmRRRA', h2b('8443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b154'), 0) tx_outs = [tx_utils.create_transaction_output('mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', 0.0000275)] tx = tx_utils.create_trx('TEST'.encode('utf-8'), 3, 'mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', tx_outs, tx_input) hextx = hexlify(tx.serialize()) self.assertEquals(hextx, '01000000018443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b1540000000000ffffffff0300000000000000001976a914072a22e5913cd939904c46bbd0bc56755543384b88acc5000000000000001976a914072a22e5913cd939904c46bbd0bc56755543384b88ac0000000000000000066a045445535400000000')
def issue_on_blockchain(self): """ Issue the certificates on the Bitcoin blockchain :param revocation_address: :return: """ self.tree.make_tree() op_return_value_bytes = unhexlify(self.tree.get_merkle_root()) op_return_value = hexlify(op_return_value_bytes) spendables = self.connector.get_unspent_outputs(self.issuing_address) if not spendables: error_message = 'No money to spend at address {}'.format(self.issuing_address) logging.error(error_message) raise InsufficientFundsError(error_message) last_input = spendables[-1] tx = self.transaction_handler.create_transaction(last_input, op_return_value_bytes) hex_tx = hexlify(tx.serialize()) logging.info('Unsigned hextx=%s', hex_tx) prepared_tx = tx_utils.prepare_tx_for_signing(hex_tx, [last_input]) with FinalizableSigner(self.secure_signer) as signer: signed_tx = signer.sign_transaction(prepared_tx) # log the actual byte count tx_byte_count = tx_utils.get_byte_count(signed_tx) logging.info('The actual transaction size is %d bytes', tx_byte_count) signed_hextx = signed_tx.as_hex() logging.info('Signed hextx=%s', signed_hextx) # verify transaction before broadcasting tx_utils.verify_transaction(signed_hextx, op_return_value) # send tx and persist txid tx_id = self.connector.broadcast_tx(signed_tx) if tx_id: logging.info('Broadcast transaction with txid %s', tx_id) else: logging.warning( 'could not broadcast transaction but you can manually do it! signed hextx=%s', signed_hextx) return tx_id
def test_bitcoind_connector_spendables(self): bc = BitcoindConnector('XTN') spendables = bc.spendables_for_address('mz7poFND7hVGRtPWjiZizcCnjf6wEDWjjT') self.assertEquals(len(spendables), 3) self.assertEquals(hexlify(spendables[0].tx_hash), '08f6528ac70c828e1633babc8f0d49ecb11649fd7451f76923821a0dbc81eb34') self.assertEquals(spendables[0].coin_value, 49000000) self.assertEquals(spendables[1].coin_value, 2750) self.assertEquals(spendables[2].coin_value, 2750)
def test_create_trx(self): cost = TransactionCosts(0.0001, 0.0000275, 3) tx_input = Spendable(200, '18eKkAWyU9kvRNHPKxnZb6wwtPMrNmRRRA', h2b('8443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b154'), 0) tx_outs = [trx_utils.create_transaction_output('mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', 0.0000275)] tx = trx_utils.create_trx('TEST'.encode('utf-8'), cost, 'mgAqW5ZCnEp7fjvpj8RUL3WxsBy8rcDcCi', tx_outs, tx_input) hextx = hexlify(tx.serialize()) self.assertEquals(hextx, '01000000018443b07464c762d7fb404ea918a5ac9b3618d5cd6a0c5ea6e4dd5d7bbe28b1540000000000ffffffff0300000000000000001976a914072a22e5913cd939904c46bbd0bc56755543384b88acc5000000000000001976a914072a22e5913cd939904c46bbd0bc56755543384b88ac0000000000000000066a045445535400000000')
def test_bitcoind_connector_spendables(self): bitcoin.SelectParams('testnet') bc = BitcoindConnector('XTN') spendables = bc.spendables_for_address('mz7poFND7hVGRtPWjiZizcCnjf6wEDWjjT') self.assertEquals(len(spendables), 3) self.assertEquals(hexlify(spendables[0].tx_hash), '08f6528ac70c828e1633babc8f0d49ecb11649fd7451f76923821a0dbc81eb34') self.assertEquals(spendables[0].coin_value, 49000000) self.assertEquals(spendables[1].coin_value, 2750) self.assertEquals(spendables[2].coin_value, 2750)
def create_transactions(self, revocation_address, issuing_transaction_cost): """ Create the batch Bitcoin transaction :param revocation_address: :param issuing_transaction_cost: :return: """ self.tree.make_tree() spendables = get_unspent_outputs(self.issuing_address) if not spendables: error_message = 'No money to spend at address {}'.format( self.issuing_address) logging.error(error_message) raise InsufficientFundsError(error_message) last_input = spendables[-1] op_return_value = unhexlify(self.tree.get_merkle_root()) tx_outs = self.build_recipient_tx_outs() tx_outs.append( trx_utils.create_transaction_output( revocation_address, issuing_transaction_cost.min_per_output)) transaction = trx_utils.create_trx(op_return_value, issuing_transaction_cost, self.issuing_address, tx_outs, last_input) unsigned_tx_file_name = convert_file_name( self.config.unsigned_txs_file_pattern, self.batch_id) unsent_tx_file_name = convert_file_name( self.config.signed_txs_file_pattern, self.batch_id) sent_tx_file_name = convert_file_name( self.config.sent_txs_file_pattern, self.batch_id) transaction_data = TransactionData( uid=self.batch_id, tx=transaction, tx_input=last_input, op_return_value=hexlify(op_return_value), unsigned_tx_file_name=unsigned_tx_file_name, signed_tx_file_name=unsent_tx_file_name, sent_tx_file_name=sent_tx_file_name) return [transaction_data]
def issue_on_blockchain(self, revocation_address, issuing_transaction_cost): """ Issue the certificates on the Bitcoin blockchain :param revocation_address: :param issuing_transaction_cost: :return: """ transactions_data = self.create_transactions(revocation_address, issuing_transaction_cost) for transaction_data in transactions_data: unsigned_tx_file_name = transaction_data.batch_metadata.unsigned_tx_file_name signed_tx_file_name = transaction_data.batch_metadata.unsent_tx_file_name sent_tx_file_name = transaction_data.batch_metadata.sent_tx_file_name # persist the transaction in case broadcasting fails hex_tx = hexlify(transaction_data.tx.serialize()) with open(unsigned_tx_file_name, 'w') as out_file: out_file.write(hex_tx) # sign transaction and persist result signed_tx = self.signer.sign_tx(hex_tx, transaction_data.tx_input, self.netcode) # log the actual byte count tx_byte_count = trx_utils.get_byte_count(signed_tx) logging.info('The actual transaction size is %d bytes', tx_byte_count) signed_hextx = signed_tx.as_hex() with open(signed_tx_file_name, 'w') as out_file: out_file.write(signed_hextx) # verify transaction before broadcasting trx_utils.verify_transaction(signed_hextx, transaction_data.op_return_value) # send tx and persist txid tx_id = self.connector.broadcast_tx(signed_tx) if tx_id: logging.info('Broadcast transaction with txid %s', tx_id) else: logging.warning( 'could not broadcast transaction but you can manually do it! signed hextx=%s', signed_hextx) self.persist_tx(sent_tx_file_name, tx_id)
def _build_certificate_transactions(wallet, issuing_address, revocation_address, certificate_metadata, fees, tail): """Make transactions for the certificates. """ logging.info('Creating tx of certificate for recipient uid: %s ...', certificate_metadata.uid) with open(certificate_metadata.certificate_hash_file_name, 'rb') as in_file: # this is the recipient-specific hash that will be recorded on the # blockchain hashed_certificate = in_file.read() cert_out = CMutableTxOut(0, CScript([OP_RETURN, hashed_certificate])) # send a transaction to the recipient's public key, and to a revocation # address txouts = create_recipient_outputs(certificate_metadata.pubkey, revocation_address, fees.min_per_output) # define transaction inputs unspent_outputs = wallet.get_unspent_outputs(issuing_address) last_input = unspent_outputs[tail] txins = [CTxIn(last_input.outpoint)] value_in = last_input.amount # very important! If we don't send the excess change back to ourselves, # some lucky miner gets it! amount = value_in - fees.cost_per_transaction if amount > 0: change_out = create_transaction_output(issuing_address, amount) txouts = txouts + [change_out] txouts = txouts + [cert_out] tx = CMutableTransaction(txins, txouts) # this is the transaction for a recipient, unsigned hextx = hexlify(tx.serialize()) with open(certificate_metadata.unsigned_tx_file_name, 'wb') as out_file: out_file.write(bytes(hextx, 'utf-8')) logging.info('Created unsigned tx for recipient; last_input=%s', last_input) return last_input
def _build_certificate_transactions(wallet, issuing_address, revocation_address, certificate_metadata, fees, tail): """Make transactions for the certificates. """ logging.info('Creating tx of certificate for recipient uid: %s ...', certificate_metadata.uid) with open(certificate_metadata.certificate_hash_file_name, 'rb') as in_file: # this is the recipient-specific hash that will be recorded on the # blockchain hashed_certificate = in_file.read() cert_out = CMutableTxOut(0, CScript([OP_RETURN, hashed_certificate])) # send a transaction to the recipient's public key, and to a revocation # address txouts = create_recipient_outputs( certificate_metadata.pubkey, revocation_address, fees.min_per_output) # define transaction inputs unspent_outputs = wallet.get_unspent_outputs(issuing_address) last_input = unspent_outputs[tail] txins = [CTxIn(last_input.outpoint)] value_in = last_input.amount # very important! If we don't send the excess change back to ourselves, # some lucky miner gets it! amount = value_in - fees.cost_per_transaction if amount > 0: change_out = create_transaction_output(issuing_address, amount) txouts = txouts + [change_out] txouts = txouts + [cert_out] tx = CMutableTransaction(txins, txouts) # this is the transaction for a recipient, unsigned hextx = hexlify(tx.serialize()) with open(certificate_metadata.unsigned_tx_file_name, 'wb') as out_file: out_file.write(bytes(hextx, 'utf-8')) logging.info( 'Created unsigned tx for recipient; last_input=%s', last_input) return last_input
def create_transactions(self, revocation_address): """ Create the batch Bitcoin transaction :param revocation_address: :return: """ self.tree.make_tree() spendables = self.connector.get_unspent_outputs(self.issuing_address) if not spendables: error_message = 'No money to spend at address {}'.format( self.issuing_address) logging.error(error_message) raise InsufficientFundsError(error_message) last_input = spendables[-1] op_return_value = unhexlify(self.tree.get_merkle_root()) tx_outs = self.build_recipient_tx_outs() tx_outs.append( tx_utils.create_transaction_output( revocation_address, self.tx_cost_constants.get_minimum_output_coin())) transaction = tx_utils.create_trx(op_return_value, self.total, self.issuing_address, tx_outs, last_input) transaction_data = TransactionData( uid=self.batch_id, tx=transaction, tx_input=last_input, op_return_value=hexlify(op_return_value), batch_metadata=self.batch_metadata) return [transaction_data]
def issue_on_blockchain(self, revocation_address, issuing_transaction_cost): """ Issue the certificates on the Bitcoin blockchain :param revocation_address: :param issuing_transaction_cost: :return: """ transactions_data = self.create_transactions(revocation_address, issuing_transaction_cost) for transaction_data in transactions_data: # persist the transaction in case broadcasting fails hex_tx = hexlify(transaction_data.tx.serialize()) with open(transaction_data.unsigned_tx_file_name, 'w') as out_file: out_file.write(hex_tx) # sign transaction and persist result signed_tx = trx_utils.sign_tx(hex_tx, transaction_data.tx_input) # log the actual byte count tx_byte_count = trx_utils.get_byte_count(signed_tx) logging.info('The actual transaction size is %d bytes', tx_byte_count) signed_hextx = signed_tx.as_hex() with open(transaction_data.signed_tx_file_name, 'w') as out_file: out_file.write(signed_hextx) # verify transaction before broadcasting trx_utils.verify_transaction(signed_hextx, transaction_data.op_return_value) # send tx and persist txid tx_id = broadcast_tx(signed_tx) if tx_id: logging.info('Broadcast transaction with txid %s', tx_id) else: logging.warning( 'could not broadcast transaction but you can manually do it! signed hextx=%s', signed_hextx) self.finish_tx(transaction_data.sent_tx_file_name, tx_id)
def bitcoind_broadcast(hextx): tx = CTransaction.deserialize(unhexlify(hextx)) txid = bitcoin.rpc.Proxy().sendrawtransaction(tx) return hexlify(txid)
def broadcast_tx(self, transaction): as_hex = transaction.as_hex() transaction = CTransaction.deserialize(unhexlify(as_hex)) tx_id = bitcoin.rpc.Proxy().sendrawtransaction(transaction) # reverse endianness for bitcoind return hexlify(bytearray(tx_id)[::-1])
def bitcoind_broadcast(hextx): tx = CTransaction.deserialize(unhexlify(hextx)) txid = bitcoin.rpc.Proxy().sendrawtransaction(tx) # reverse endianness for bitcoind return hexlify(bytearray(txid)[::-1])
def issue_on_blockchain(self): """ Issue the certificates on the Bitcoin blockchain :param revocation_address: :return: """ self.tree.make_tree() op_return_value_bytes = unhexlify(self.tree.get_merkle_root()) op_return_value = hexlify(op_return_value_bytes) for attempt_number in range(0, self.max_retry): try: if self.prepared_inputs: inputs = self.prepared_inputs else: spendables = self.connector.get_unspent_outputs( self.secure_signer.issuing_address) if not spendables: error_message = 'No money to spend at address {}'.format( self.secure_signer.issuing_address) logging.error(error_message) raise InsufficientFundsError(error_message) cost = self.transaction_handler.estimate_cost_for_certificate_batch( ) current_total = 0 inputs = [] random.shuffle(spendables) for s in spendables: inputs.append(s) current_total += s.coin_value if current_total > cost: break tx = self.transaction_handler.create_transaction( inputs, op_return_value_bytes) hex_tx = hexlify(tx.serialize()) logging.info('Unsigned hextx=%s', hex_tx) prepared_tx = tx_utils.prepare_tx_for_signing(hex_tx, inputs) with FinalizableSigner(self.secure_signer) as signer: signed_tx = signer.sign_transaction(prepared_tx) # log the actual byte count tx_byte_count = tx_utils.get_byte_count(signed_tx) logging.info('The actual transaction size is %d bytes', tx_byte_count) signed_hextx = signed_tx.as_hex() logging.info('Signed hextx=%s', signed_hextx) # verify transaction before broadcasting tx_utils.verify_transaction(signed_hextx, op_return_value) # send tx and persist txid tx_id = self.connector.broadcast_tx(signed_tx) logging.info('Broadcast transaction with txid %s', tx_id) return tx_id except BroadcastError: logging.warning( 'Failed broadcast reattempts. Trying to recreate transaction. This is attempt number %d', attempt_number) logging.error( 'All attempts to broadcast failed. Try rerunning issuer.') raise BroadcastError( 'All attempts to broadcast failed. Try rerunning issuer.')