def create_payment_tx(self, deposit_tx, redeem_script, merchant_public_key, customer_public_key, amount, fee): # Find P2SH output index in deposit_tx deposit_utxo_index = deposit_tx.output_index_for_address(redeem_script.hash160()) # Look up deposit amount deposit_amount = deposit_tx.outputs[deposit_utxo_index].value - fee # Build unsigned payment transaction script_sig = Script() inp = TransactionInput(deposit_tx.hash, deposit_utxo_index, script_sig, 0xffffffff) out1 = TransactionOutput(amount, Script.build_p2pkh(merchant_public_key.hash160())) out2 = TransactionOutput(deposit_amount - amount, Script.build_p2pkh(customer_public_key.hash160())) payment_tx = Transaction(1, [inp], [out1, out2], 0x0) # Sign payment transaction public_key = redeem_script.customer_public_key private_key = self.get_private_for_public(public_key) sig = payment_tx.get_signature_for_input(0, Transaction.SIG_HASH_ALL, private_key, redeem_script)[0] # Update input script sig script_sig = Script( [sig.to_der() + utils.pack_compact_int(Transaction.SIG_HASH_ALL), 'OP_1', bytes(redeem_script)]) payment_tx.inputs[0].script = script_sig return payment_tx
def lookup(self, deposit_txid=None): """Look up a payment channel entry by deposit txid.""" # Check whether to query a single channel or all if not deposit_txid: query = self.Channel.objects.all() else: try: query = [self.Channel.objects.get(deposit_txid=deposit_txid)] except self.Channel.DoesNotExist: query = [] if not len(query) or not query[0]: return None # Collect all records as a list of Channels records = [] for rec in query: deposit_tx = Transaction.from_hex( rec.deposit_tx) if rec.deposit_tx else None payment_tx = Transaction.from_hex( rec.payment_tx) if rec.payment_tx else None records.append( Channel(rec.deposit_txid, rec.state, deposit_tx, payment_tx, rec.merchant_pubkey, rec.created_at, rec.expires_at, rec.amount, rec.last_payment_amount)) # Return a single record or list of records return records if len(records) > 1 else records[0]
def create_payment_tx(self, deposit_tx, redeem_script, merchant_public_key, customer_public_key, amount, fee): # Find P2SH output index in deposit_tx deposit_utxo_index = deposit_tx.output_index_for_address( redeem_script.hash160()) # Look up deposit amount deposit_amount = deposit_tx.outputs[deposit_utxo_index].value - fee # Build unsigned payment transaction script_sig = Script() inp = TransactionInput(deposit_tx.hash, deposit_utxo_index, script_sig, 0xffffffff) out1 = TransactionOutput( amount, Script.build_p2pkh(merchant_public_key.hash160())) out2 = TransactionOutput( deposit_amount - amount, Script.build_p2pkh(customer_public_key.hash160())) payment_tx = Transaction(1, [inp], [out1, out2], 0x0) # Sign payment transaction public_key = redeem_script.customer_public_key private_key = self.get_private_for_public(public_key) sig = payment_tx.get_signature_for_input(0, Transaction.SIG_HASH_ALL, private_key, redeem_script)[0] # Update input script sig script_sig = Script([ sig.to_der() + utils.pack_compact_int(Transaction.SIG_HASH_ALL), 'OP_1', bytes(redeem_script) ]) payment_tx.inputs[0].script = script_sig return payment_tx
def write_ew_message(msg): """Write a message to the blockchain.""" print("write_ew_message({})" % msg) # Create a bitcoin script object with our message if len(msg) > 72: raise Exception('Message is too long and may not be accepted.') msg = "EW " + msg message_script = Script('OP_RETURN 0x{}'.format(utils.bytes_to_str(msg.encode()))) # Define the fee we're willing to pay for the tx tx_fee = 11000 # Get the first UTXO from our set that can cover the fee utxo = None for utxo_addr, utxos in wallet.get_utxos().items(): for u in utxos: if u.value > tx_fee: utxo = u break if utxo: break if not utxo: raise Exception('No UTXOs available to pay for the transaction.') # Build the transaction inputs (there is only one, but Transaction expects a list) inputs = [TransactionInput(outpoint=utxo.transaction_hash, outpoint_index=utxo.outpoint_index, script=utxo.script, sequence_num=0xffffffff)] outputs = [] # Build one output with our custom message script outputs.append(TransactionOutput(value=0, script=message_script)) # Build another output to pay the UTXO money back to one of our addresses _, change_key_hash = utils.address_to_key_hash(wallet._accounts[0].get_next_address(True)) outputs.append(TransactionOutput(value=utxo.value - tx_fee, script=Script.build_p2pkh(change_key_hash))) # Build an unsigned transaction object txn = Transaction(version=Transaction.DEFAULT_TRANSACTION_VERSION, inputs=inputs, outputs=outputs, lock_time=0 ) # Sign the transaction with the correct private key private_key = wallet.get_private_key(utxo_addr) txn.sign_input(input_index=0, hash_type=Transaction.SIG_HASH_ALL, private_key=private_key, sub_script=utxo.script ) # Broadcast the transaction tx = wallet.broadcast_transaction(txn.to_hex()) return tx
def create_deposit_tx(self, hash160): """Return a mocked deposit transaction.""" utxo_script_sig = Script.build_p2pkh(self._private_key.public_key.hash160()) inp = TransactionInput( outpoint=Hash('0' * 64), outpoint_index=0, script=utxo_script_sig, sequence_num=0xffffffff) out = TransactionOutput(value=120000, script=Script.build_p2sh(hash160)) txn = Transaction(version=Transaction.DEFAULT_TRANSACTION_VERSION, inputs=[inp], outputs=[out], lock_time=0) txn.sign_input( input_index=0, hash_type=Transaction.SIG_HASH_ALL, private_key=self._private_key, sub_script=utxo_script_sig) return txn
def lookup(self, payment_txid): """Look up a payment entry by deposit txid.""" try: rec = self.Payment.objects.get(payment_txid=payment_txid) except self.Payment.DoesNotExist: return None return Payment(rec.payment_txid, Transaction.from_hex(rec.payment_tx), rec.amount, rec.is_redeemed, rec.deposit_txid)
def open(self, deposit_tx, redeem_script): """Open a payment channel. Args: deposit_tx (string): signed deposit transaction which pays to a 2 of 2 multisig script hash. redeem_script (string): the redeem script that comprises the script hash so that the merchant can verify. Returns: (string): deposit transaction id """ # Parse payment channel `open` parameters deposit_tx = Transaction.from_hex(deposit_tx) redeem_script = PaymentChannelRedeemScript.from_bytes( codecs.decode(redeem_script, 'hex_codec')) # Verify that the deposit pays to the redeem script output_index = deposit_tx.output_index_for_address( redeem_script.hash160()) if output_index is None: raise BadTransactionError( 'Deposit does not pay to the provided script hash.') # Parse payment channel data for open deposit_txid = str(deposit_tx.hash) merchant_public_key = codecs.encode( redeem_script.merchant_public_key.compressed_bytes, 'hex_codec').decode() amount = deposit_tx.outputs[output_index].value # Verify that one of the public keys belongs to the merchant valid_merchant_public_key = self._wallet.validate_public_key( redeem_script.merchant_public_key) if not valid_merchant_public_key: raise BadTransactionError( 'Public key does not belong to the merchant.') # Verify that the deposit is not already part of a payment channel if self._db.pc.lookup(deposit_txid): raise BadTransactionError( 'That deposit has already been used to create a channel.') # Verify that the lock time is an allowable amount in the future minimum_locktime = int(time.time()) + self.MIN_EXP_TIME if redeem_script.expiration_time < minimum_locktime: raise TransactionVerificationError( 'Transaction locktime must be further in the future.') # Open and save the payment channel self._db.pc.create(deposit_tx, merchant_public_key, amount, redeem_script.expiration_time) # Set the channel to `ready` if zeroconf is enabled if self.zeroconf: self._db.pc.update_state(deposit_txid, ChannelSQLite3.READY) return str(deposit_tx.hash)
def _create_client_payment(client, num): """Mock client transaction for a payment in a channel.""" customer_public_key = cust_wallet.get_payout_public_key() merchant_public_key = merch_wallet.get_payout_public_key() deposit_tx = Transaction.from_hex(client.deposit_tx) redeem_script = PaymentChannelRedeemScript.from_bytes(codecs.decode(client.redeem_script, 'hex_codec')) return cust_wallet.create_payment_tx( deposit_tx, redeem_script, merchant_public_key, customer_public_key, TEST_PMT_AMOUNT * num, TEST_FEE_AMOUNT).to_hex()
def create_deposit_tx(self, hash160): """Return a mocked deposit transaction.""" utxo_script_sig = Script.build_p2pkh( self._private_key.public_key.hash160()) inp = TransactionInput(outpoint=Hash('0' * 64), outpoint_index=0, script=utxo_script_sig, sequence_num=0xffffffff) out = TransactionOutput(value=120000, script=Script.build_p2sh(hash160)) txn = Transaction(version=Transaction.DEFAULT_TRANSACTION_VERSION, inputs=[inp], outputs=[out], lock_time=0) txn.sign_input(input_index=0, hash_type=Transaction.SIG_HASH_ALL, private_key=self._private_key, sub_script=utxo_script_sig) return txn
def lookup(self, payment_txid): """Look up a payment entry by deposit txid.""" select = 'SELECT * FROM payment_channel_spend WHERE payment_txid=?' self.c.execute(select, (payment_txid,)) rv = self.c.fetchone() if rv is None: return rv channel = list(rv) channel[1] = Transaction.from_hex(channel[1]) channel[3] = channel[3] == PaymentSQLite3.WAS_REDEEMED return Payment(*channel)
def lookup(self, payment_txid): """Look up a payment entry by deposit txid.""" select = 'SELECT * FROM payment_channel_spend WHERE payment_txid=?' self.c.execute(select, (payment_txid, )) rv = self.c.fetchone() if rv is None: return rv channel = list(rv) channel[1] = Transaction.from_hex(channel[1]) channel[3] = channel[3] == PaymentSQLite3.WAS_REDEEMED return Payment(*channel)
def _create_client_payment(client, num): """Mock client transaction for a payment in a channel.""" customer_public_key = cust_wallet.get_payout_public_key() merchant_public_key = merch_wallet.get_payout_public_key() deposit_tx = Transaction.from_hex(client.deposit_tx) redeem_script = PaymentChannelRedeemScript.from_bytes( codecs.decode(client.redeem_script, 'hex_codec')) return cust_wallet.create_payment_tx(deposit_tx, redeem_script, merchant_public_key, customer_public_key, TEST_PMT_AMOUNT * num, TEST_FEE_AMOUNT).to_hex()
def from_bytes(b): """ Deserializes a byte stream into a WalletTransaction. Args: b (bytes): byte stream starting with the version. Returns: tuple: First element of the tuple is the WalletTransaction, second is the remainder of the byte stream. """ t, b1 = Transaction.from_bytes() return WalletTransaction.from_transaction(t), b1
def lookup(self, deposit_txid=None): """Look up a payment channel entry by deposit txid.""" # Check whether to query a single channel or all if not deposit_txid: self.c.execute('SELECT * FROM payment_channel') query = self.c.fetchall() else: select = 'SELECT * FROM payment_channel WHERE deposit_txid=?' self.c.execute(select, (deposit_txid,)) query = [self.c.fetchone()] if not len(query) or not query[0]: return None # Collect all records as a list of Channels records = [] for rec in query: record = list(rec) record[2] = Transaction.from_hex(record[2]) if record[2] else None record[3] = Transaction.from_hex(record[3]) if record[3] else None records.append(Channel(*record)) # Return a single record or list of records return records if len(records) > 1 else records[0]
def lookup(self, deposit_txid=None): """Look up a payment channel entry by deposit txid.""" # Check whether to query a single channel or all if not deposit_txid: self.c.execute('SELECT * FROM payment_channel') query = self.c.fetchall() else: select = 'SELECT * FROM payment_channel WHERE deposit_txid=?' self.c.execute(select, (deposit_txid, )) query = [self.c.fetchone()] if not len(query) or not query[0]: return None # Collect all records as a list of Channels records = [] for rec in query: record = list(rec) record[2] = Transaction.from_hex(record[2]) if record[2] else None record[3] = Transaction.from_hex(record[3]) if record[3] else None records.append(Channel(*record)) # Return a single record or list of records return records if len(records) > 1 else records[0]
def open(self, deposit_tx, redeem_script): """Open a payment channel. Args: deposit_tx (string): signed deposit transaction which pays to a 2 of 2 multisig script hash. redeem_script (string): the redeem script that comprises the script hash so that the merchant can verify. Returns: (string): deposit transaction id """ with self.lock: # Parse payment channel `open` parameters deposit_tx = Transaction.from_hex(deposit_tx) redeem_script = PaymentChannelRedeemScript.from_bytes(codecs.decode(redeem_script, 'hex_codec')) # Verify that the deposit pays to the redeem script output_index = deposit_tx.output_index_for_address(redeem_script.hash160()) if output_index is None: raise BadTransactionError('Deposit does not pay to the provided script hash.') # Parse payment channel data for open deposit_txid = str(deposit_tx.hash) merch_pubkey = codecs.encode(redeem_script.merchant_public_key.compressed_bytes, 'hex_codec').decode() amount = deposit_tx.outputs[output_index].value # Verify that one of the public keys belongs to the merchant valid_merchant_public_key = self._wallet.validate_merchant_public_key(redeem_script.merchant_public_key) if not valid_merchant_public_key: raise BadTransactionError('Public key does not belong to the merchant.') # Verify that the deposit is not already part of a payment channel if self._db.pc.lookup(deposit_txid): raise BadTransactionError('That deposit has already been used to create a channel.') # Verify that the lock time is an allowable amount in the future minimum_locktime = int(time.time()) + self.MIN_EXP_TIME if redeem_script.expiration_time < minimum_locktime: raise TransactionVerificationError('Transaction locktime must be further in the future.') # Open and save the payment channel channel = self._db.pc.create( deposit_tx, merch_pubkey, amount, redeem_script.expiration_time) # Set the channel to `ready` if zeroconf is enabled if self.zeroconf: self._db.pc.update_state(deposit_txid, ChannelSQLite3.READY) return str(deposit_tx.hash)
def lookup(self, deposit_txid=None): """Look up a payment channel entry by deposit txid.""" # Check whether to query a single channel or all if not deposit_txid: query = self.Channel.objects.all() else: try: query = [self.Channel.objects.get(deposit_txid=deposit_txid)] except self.Channel.DoesNotExist: query = [] if not len(query) or not query[0]: return None # Collect all records as a list of Channels records = [] for rec in query: deposit_tx = Transaction.from_hex(rec.deposit_tx) if rec.deposit_tx else None payment_tx = Transaction.from_hex(rec.payment_tx) if rec.payment_tx else None records.append(Channel(rec.deposit_txid, rec.state, deposit_tx, payment_tx, rec.merchant_pubkey, rec.created_at, rec.expires_at, rec.amount, rec.last_payment_amount)) # Return a single record or list of records return records if len(records) > 1 else records[0]
def from_hex(h): return WalletTransaction.from_transaction(Transaction.from_hex(h))
def receive_payment(self, deposit_txid, payment_tx): """Receive and process a payment within the channel. The customer makes a payment in the channel by sending the merchant a half-signed payment transaction. The merchant signs the other half of the transaction and saves it in its records (but does not broadcast it or send it to the customer). The merchant responds with 200 to verify that the payment was handled successfully. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. payment_tx (string): half-signed payment transaction from a customer. Returns: (string): payment transaction id """ # Parse payment channel `payment` parameters payment_tx = Transaction.from_hex(payment_tx) # Get channel and addresses related to the deposit channel = self._db.pc.lookup(deposit_txid) if not channel: raise PaymentChannelNotFoundError('Related channel not found.') # Get merchant public key information from payment channel merchant_public_key = PublicKey.from_hex(channel.merchant_pubkey) # Verify that redeem script contains the merchant public key redeem_script = PaymentChannelRedeemScript.from_bytes(payment_tx.inputs[0].script[-1]) if redeem_script.merchant_public_key.to_hex() != merchant_public_key.to_hex(): raise BadTransactionError('Invalid merchant pubkey.') # Verify that the payment has a valid signature from the customer txn_copy = payment_tx._copy_for_sig(0, Transaction.SIG_HASH_ALL, redeem_script) msg_to_sign = bytes(Hash.dhash(bytes(txn_copy) + pack_u32(Transaction.SIG_HASH_ALL))) sig = Signature.from_der(payment_tx.inputs[0].script[0][:-1]) if not redeem_script.customer_public_key.verify(msg_to_sign, sig, False): raise BadTransactionError('Invalid payment signature.') # Verify the length of the script is what we expect if len(payment_tx.inputs[0].script) != 3: raise BadTransactionError('Invalid payment channel transaction structure.') # Verify the script template is valid for accepting a merchant signature if (not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_1', bytes]) and not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_TRUE', bytes])): raise BadTransactionError('Invalid payment channel transaction structure.') # Verify that the payment channel is ready if channel.state == ChannelSQLite3.CONFIRMING: confirmed = self._blockchain.check_confirmed(channel.deposit_txid) if confirmed: self._db.pc.update_state(channel.deposit_txid, ChannelSQLite3.READY) else: raise ChannelClosedError('Payment channel not ready.') elif channel.state == ChannelSQLite3.CLOSED: raise ChannelClosedError('Payment channel closed.') # Verify that payment is made to the merchant public key index = payment_tx.output_index_for_address(merchant_public_key.hash160()) if index is None: raise BadTransactionError('Payment must pay to merchant pubkey.') # Verify that both payments are not below the dust limit for output_index, output in enumerate(payment_tx.outputs): if output.value < PaymentServer.DUST_LIMIT: # Payment to merchant is less than dust limit if output_index == index: raise BadTransactionError( 'Initial payment must be greater than {}.'.format(PaymentServer.DUST_LIMIT)) # Payment to customer is less than dust limit else: raise BadTransactionError( 'Payment channel balance is not large enough to make payment.') # Validate that the payment is more than the last one new_pmt_amt = payment_tx.outputs[index].value if new_pmt_amt <= channel.last_payment_amount: raise BadTransactionError('Payment must be greater than 0.') # Verify that the transaction has adequate fees net_pmt_amount = sum([d.value for d in payment_tx.outputs]) deposit_amount = channel.amount fee = deposit_amount - net_pmt_amount if fee < PaymentServer.MIN_TX_FEE: raise BadTransactionError('Payment must have adequate fees.') # Recreate redeem script from merchant side redeem_script_copy = PaymentChannelRedeemScript( merchant_public_key, redeem_script.customer_public_key, channel.expires_at) if redeem_script.to_hex() != redeem_script_copy.to_hex(): raise BadTransactionError('Invalid redeem script.') # Recreate customer payment from merchant side payment_tx_copy = self._wallet.create_unsigned_payment_tx( channel.deposit_tx, redeem_script, new_pmt_amt, fee) # Recreate signed input script using signature from customer hash_type = pack_compact_int(Transaction.SIG_HASH_ALL) signed_input_script = Script( [sig.to_der() + hash_type, "OP_1", bytes(redeem_script)]) payment_tx_copy.inputs[0].script = signed_input_script if payment_tx.to_hex() != payment_tx_copy.to_hex(): raise BadTransactionError('Invalid payment channel transaction structure.') # Update the current payment transaction self._db.pc.update_payment(deposit_txid, payment_tx_copy, new_pmt_amt) self._db.pmt.create(deposit_txid, payment_tx_copy, new_pmt_amt - channel.last_payment_amount) return str(payment_tx_copy.hash)
def receive_payment(self, deposit_txid, payment_tx): """Receive and process a payment within the channel. The customer makes a payment in the channel by sending the merchant a half-signed payment transaction. The merchant signs the other half of the transaction and saves it in its records (but does not broadcast it or send it to the customer). The merchant responds with 200 to verify that the payment was handled successfully. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. payment_tx (string): half-signed payment transaction from a customer. Returns: (string): payment transaction id """ # Parse payment channel `payment` parameters payment_tx = Transaction.from_hex(payment_tx) # Get channel and addresses related to the deposit channel = self._db.pc.lookup(deposit_txid) if not channel: raise PaymentChannelNotFoundError('Related channel not found.') # Get merchant public key information from payment channel redeem_script = PaymentChannelRedeemScript.from_bytes( payment_tx.inputs[0].script[-1]) merch_pubkey = redeem_script.merchant_public_key # Verify that the payment has a valid signature from the customer txn_copy = payment_tx._copy_for_sig(0, Transaction.SIG_HASH_ALL, redeem_script) msg_to_sign = bytes( Hash.dhash(bytes(txn_copy) + pack_u32(Transaction.SIG_HASH_ALL))) sig = Signature.from_der(payment_tx.inputs[0].script[0][:-1]) if not redeem_script.customer_public_key.verify( msg_to_sign, sig, False): raise BadTransactionError('Invalid payment signature.') # Verify the length of the script is what we expect if len(payment_tx.inputs[0].script) != 3: raise BadTransactionError( 'Invalid payment channel transaction structure.') # Verify the script template is valid for accepting a merchant signature if (not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_1', bytes]) and not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_TRUE', bytes])): raise BadTransactionError( 'Invalid payment channel transaction structure.') # Verify that the payment channel is ready if channel.state == ChannelSQLite3.CONFIRMING: confirmed = self._blockchain.check_confirmed(channel.deposit_txid) if confirmed: self._db.pc.update_state(channel.deposit_txid, ChannelSQLite3.READY) else: raise ChannelClosedError('Payment channel not ready.') elif channel.state == ChannelSQLite3.CLOSED: raise ChannelClosedError('Payment channel closed.') # Verify that payment is made to the merchant's pubkey index = payment_tx.output_index_for_address(merch_pubkey.hash160()) if index is None: raise BadTransactionError('Payment must pay to merchant pubkey.') # Verify that both payments are not below the dust limit for output_index, output in enumerate(payment_tx.outputs): if output.value < PaymentServer.DUST_LIMIT: # Payment to merchant is less than dust limit if output_index == index: raise BadTransactionError( 'Initial payment must be greater than {}.'.format( PaymentServer.DUST_LIMIT)) # Payment to customer is less than dust limit else: raise BadTransactionError( 'Payment channel balance is not large enough to make payment.' ) # Validate that the payment is more than the last one new_pmt_amt = payment_tx.outputs[index].value if new_pmt_amt <= channel.last_payment_amount: raise BadTransactionError('Payment must be greater than 0.') # Verify that the transaction has adequate fees net_pmt_amount = sum([d.value for d in payment_tx.outputs]) deposit_amount = channel.amount if deposit_amount < net_pmt_amount + PaymentServer.MIN_TX_FEE: raise BadTransactionError('Payment must have adequate fees.') # Update the current payment transaction self._db.pc.update_payment(deposit_txid, payment_tx, new_pmt_amt) self._db.pmt.create(deposit_txid, payment_tx, new_pmt_amt - channel.last_payment_amount) return str(payment_tx.hash)
def receive_payment(self, deposit_txid, payment_tx): """Receive and process a payment within the channel. The customer makes a payment in the channel by sending the merchant a half-signed payment transaction. The merchant signs the other half of the transaction and saves it in its records (but does not broadcast it or send it to the customer). The merchant responds with 200 to verify that the payment was handled successfully. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. payment_tx (string): half-signed payment transaction from a customer. Returns: (string): payment transaction id """ with self.lock: # Parse payment channel `payment` parameters payment_tx = Transaction.from_hex(payment_tx) # Get channel and addresses related to the deposit channel = self._db.pc.lookup(deposit_txid) if not channel: raise PaymentChannelNotFoundError('Related channel not found.') # Get merchant public key information from payment channel redeem_script = PaymentChannelRedeemScript.from_bytes(payment_tx.inputs[0].script[-1]) merch_pubkey = redeem_script.merchant_public_key # Verify that the payment has a valid signature from the customer txn_copy = payment_tx._copy_for_sig(0, Transaction.SIG_HASH_ALL, redeem_script) msg_to_sign = bytes(Hash.dhash(bytes(txn_copy) + pack_u32(Transaction.SIG_HASH_ALL))) sig = Signature.from_der(payment_tx.inputs[0].script[0][:-1]) if not redeem_script.customer_public_key.verify(msg_to_sign, sig, False): raise BadTransactionError('Invalid payment signature.') # Verify the length of the script is what we expect if len(payment_tx.inputs[0].script) != 3: raise BadTransactionError('Invalid payment channel transaction structure.') # Verify the script template is valid for accepting a merchant signature if (not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_1', bytes]) and not Script.validate_template(payment_tx.inputs[0].script, [bytes, 'OP_TRUE', bytes])): raise BadTransactionError('Invalid payment channel transaction structure.') # Verify that the payment channel is ready if channel.state == ChannelSQLite3.CONFIRMING: raise ChannelClosedError('Payment channel not ready.') elif channel.state == ChannelSQLite3.CLOSED: raise ChannelClosedError('Payment channel closed.') # Verify that payment is made to the merchant's pubkey index = payment_tx.output_index_for_address(merch_pubkey.hash160()) if index is None: raise BadTransactionError('Payment must pay to merchant pubkey.') # Verify that both payments are not below the dust limit if any(p.value < PaymentServer.DUST_LIMIT for p in payment_tx.outputs): raise BadTransactionError( 'Final payment must have outputs greater than {}.'.format(PaymentServer.DUST_LIMIT)) # Validate that the payment is more than the last one new_pmt_amt = payment_tx.outputs[index].value if new_pmt_amt <= channel.last_payment_amount: raise BadTransactionError('Payment must be greater than 0.') # Verify that the transaction has adequate fees net_pmt_amount = sum([d.value for d in payment_tx.outputs]) deposit_amount = channel.amount if deposit_amount < net_pmt_amount + PaymentServer.MIN_TX_FEE: raise BadTransactionError('Payment must have adequate fees.') # Update the current payment transaction self._db.pc.update_payment(deposit_txid, payment_tx, new_pmt_amt) self._db.pmt.create(deposit_txid, payment_tx, new_pmt_amt - channel.last_payment_amount) return str(payment_tx.hash)