def close(self, deposit_txid, deposit_txid_signature): """Close a payment channel. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. deposit_txid_signature (two1.bitcoin.Signature): a signature consisting solely of the deposit_txid to verify the authenticity of the close request. """ channel = self._db.pc.lookup(deposit_txid) # Verify that the requested channel exists if not channel: raise PaymentChannelNotFoundError('Related channel not found.') # Parse payment channel `close` parameters try: signature_der = codecs.decode(deposit_txid_signature, 'hex_codec') deposit_txid_signature = Signature.from_der(signature_der) except TypeError: raise TransactionVerificationError('Invalid signature provided.') # Verify that there is a valid payment to close if not channel.payment_tx: raise BadTransactionError('No payments made in channel.') # Verify that the user is authorized to close the channel payment_tx = channel.payment_tx redeem_script = PaymentChannelRedeemScript.from_bytes( payment_tx.inputs[0].script[-1]) sig_valid = redeem_script.customer_public_key.verify( deposit_txid.encode(), deposit_txid_signature) if not sig_valid: raise TransactionVerificationError('Invalid signature.') # Sign the final transaction self._wallet.sign_half_signed_payment(payment_tx, redeem_script) # Broadcast payment transaction to the blockchain self._blockchain.broadcast_tx(payment_tx.to_hex()) # Record the broadcast in the database self._db.pc.update_state(deposit_txid, ChannelSQLite3.CLOSED) return str(payment_tx.hash)
def close(self, deposit_txid, deposit_txid_signature): """Close a payment channel. Args: deposit_txid (string): string representation of the deposit transaction hash. This is used to look up the payment channel. deposit_txid_signature (two1.bitcoin.Signature): a signature consisting solely of the deposit_txid to verify the authenticity of the close request. """ with self.lock: channel = self._db.pc.lookup(deposit_txid) # Verify that the requested channel exists if not channel: raise PaymentChannelNotFoundError('Related channel not found.') # Parse payment channel `close` parameters try: signature_der = codecs.decode(deposit_txid_signature, 'hex_codec') deposit_txid_signature = Signature.from_der(signature_der) except TypeError: raise TransactionVerificationError('Invalid signature provided.') # Verify that there is a valid payment to close if not channel.payment_tx: raise BadTransactionError('No payments made in channel.') # Verify that the user is authorized to close the channel payment_tx = channel.payment_tx redeem_script = PaymentChannelRedeemScript.from_bytes(payment_tx.inputs[0].script[-1]) sig_valid = redeem_script.customer_public_key.verify( deposit_txid.encode(), deposit_txid_signature) if not sig_valid: raise TransactionVerificationError('Invalid signature.') # Sign the final transaction self._wallet.sign_half_signed_payment(payment_tx, redeem_script) # Broadcast payment transaction to the blockchain self._blockchain.broadcast_tx(payment_tx.to_hex()) # Record the broadcast in the database self._db.pc.update_state(deposit_txid, ChannelSQLite3.CLOSED) 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 """ # 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 """ # 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 """ 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)