Exemple #1
0
    def put(self, deposit_txid):
        """Complete the payment channel handshake or receive payments.

        Args:
            deposit_txid (string): initial signed deposit transaction id

        Params (json) (one of the following):
            deposit_tx (string): half-signed serialized deposit transaction
            payment_tx (string):  half-signed serialized payment transaction
        """
        try:
            params = request.values.to_dict()
            if 'deposit_tx' in params:
                # Complete the handshake using the received deposit
                deposit_tx = Transaction.from_hex(params['deposit_tx'])
                self.server.complete_handshake(deposit_txid, deposit_tx)
                return jsonify()
            elif 'payment_tx' in params:
                # Receive a payment in the channel using the received payment
                payment_tx = Transaction.from_hex(params['payment_tx'])
                self.server.receive_payment(deposit_txid, payment_tx)
                return jsonify({'payment_txid': str(payment_tx.hash)})
            else:
                raise KeyError('No deposit or payment received.')
        except Exception as e:
            raise BadRequest(str(e))
Exemple #2
0
    def update(self, request, pk, format='json'):
        """Complete the payment channel handshake or receive payments.

        Args:
            pk (string): initial signed deposit transaction id

        Params (json) (one of the following):
            deposit_tx (string): half-signed serialized deposit transaction
            payment_tx (string):  half-signed serialized payment transaction
        """
        try:
            params = request.data
            if 'deposit_tx' in params:
                # Complete the handshake using the received deposit
                deposit_tx = Transaction.from_hex(params['deposit_tx'])
                payment_server.complete_handshake(pk, deposit_tx)
                return Response()
            elif 'payment_tx' in params:
                # Receive a payment in the channel using the received payment
                payment_tx = Transaction.from_hex(params['payment_tx'])
                payment_server.receive_payment(pk, payment_tx)
                return Response({'payment_txid': str(payment_tx.hash)})
            else:
                raise KeyError('No deposit or payment received.')
        except Exception as e:
            error = {'error': str(e)}
            return Response(error, status=status.HTTP_400_BAD_REQUEST)
Exemple #3
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:
            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]
Exemple #4
0
 def lookup(self, deposit_txid):
     """Look up a payment channel entry by deposit txid."""
     rv = self.Channel.objects.get(deposit_txid=deposit_txid)
     deposit_tx = Transaction.from_hex(rv.deposit_tx) if rv.deposit_tx else None
     payment_tx = Transaction.from_hex(rv.payment_tx) if rv.payment_tx else None
     refund_tx = Transaction.from_hex(rv.refund_tx) if rv.refund_tx else None
     return {'deposit_txid': rv.deposit_txid, 'state': rv.state,
             'deposit_tx': deposit_tx, 'payment_tx': payment_tx,
             'refund_tx': refund_tx, 'merchant_pubkey': rv.merchant_pubkey,
             'created_at': rv.created_at, 'expires_at': rv.expires_at,
             'amount': rv.amount, 'last_payment_amount': rv.last_payment_amount}
Exemple #5
0
 def lookup(self, deposit_txid):
     """Look up a payment channel entry by deposit txid."""
     select = 'SELECT * FROM payment_channel WHERE deposit_txid=?'
     self.c.execute(select, (deposit_txid,))
     rv = self.c.fetchone()
     if rv is None:
         raise ModelNotFound()
     deposit_tx = Transaction.from_hex(rv[2]) if rv[2] else None
     payment_tx = Transaction.from_hex(rv[3]) if rv[3] else None
     refund_tx = Transaction.from_hex(rv[4]) if rv[4] else None
     return {'deposit_txid': rv[0], 'state': rv[1],
             'deposit_tx': deposit_tx, 'payment_tx': payment_tx,
             'refund_tx': refund_tx, 'merchant_pubkey': rv[5],
             'created_at': rv[6], 'expires_at': rv[7], 'amount': rv[8],
             'last_payment_amount': rv[9]}
Exemple #6
0
    def post(self):
        """Initialize the payment channel handshake.

        Params (query):
            refund_tx (string): half-signed serialized refund transaction

        Response (json) 2xx:
            refund_tx (string): fully-signed serialized refund transaction
        """
        try:
            params = request.values.to_dict()
            # Validate parameters
            if 'refund_tx' not in params:
                raise BadParametersError('No refund provided.')

            # Initialize the payment channel
            refund_tx = Transaction.from_hex(params['refund_tx'])
            self.server.initialize_handshake(refund_tx)

            # Respond with the fully-signed refund transaction
            success = {'refund_tx': bytes_to_str(bytes(refund_tx))}
            return jsonify(success)
        except Exception as e:
            # Catch payment exceptions and send error response to client
            raise BadRequest(str(e))
Exemple #7
0
    def create(self, request, format='json'):
        """Initialize the payment channel handshake.

        Params (query):
            refund_tx (string): half-signed serialized refund transaction

        Response (json) 2xx:
            refund_tx (string): fully-signed serialized refund transaction
        """
        try:
            params = request.data
            # Validate parameters
            if 'refund_tx' not in params:
                raise BadParametersError('No refund provided.')

            # Initialize the payment channel
            refund_tx = Transaction.from_hex(params['refund_tx'])
            payment_server.initialize_handshake(refund_tx)

            # Respond with the fully-signed refund transaction
            success = {'refund_tx': bytes_to_str(bytes(refund_tx))}
            return Response(success)
        except Exception as e:
            error = {'error': str(e)}
            return Response(error, status=status.HTTP_400_BAD_REQUEST)
Exemple #8
0
 def lookup(self, payment_txid):
     """Look up a payment entry by deposit txid."""
     rv = self.Payment.objects.get(payment_txid=payment_txid)
     return {'payment_txid': rv.payment_txid,
             'payment_tx': Transaction.from_hex(rv.payment_tx),
             'amount': rv.amount, 'is_redeemed': rv.is_redeemed,
             'deposit_txid': rv.deposit_txid}
Exemple #9
0
    def create_deposit_tx(self, hash160):
        """Return a mocked deposit transaction.

        This uses the hex from some transaction I had lying around and then
        modifies the outputs to pay to the multisig script hash. I basically
        just needed some UTXO's to pay for this thing.
        """
        tx_bytes = codecs.decode(
            "010000000295c5ca7c5d339d476456e5798bc5d483c37234adedeea1d6d58bd7"
            "4dcb3ab488000000006b483045022100bdb985c42ff8db57bd936fd2c7567e0b"
            "4220012b568a956c8b1dcfdef049effb0220637c5f5aad734f3fe42f8a5e2879"
            "174d219dcda516a370d8fd9d2a8d97193668012102a00465ffe29a8a3abd021b"
            "8037fb4467c0a734588449694dda8309495de5dc8affffffff1443c6572f221d"
            "02e072b5efd3af62833ade83c681e8c1ed6620a4020c300aac000000006b4830"
            "4502210086414fe8cc24bbcdebc4238201c3d021d4cd0b0a39b69538227fcfd5"
            "cca8df8c0220540cd8d8ab4f7a03a89fb686cec4f4a47f0b4d9d477c383b040b"
            "a1d7f0b5313c01210237c3846c8f7f86c86078344ff0e09c73d48bec4a1e60dd"
            "99df1ddb2816d1196dffffffff02b0ad01000000000017a9147487cdfa3235a3"
            "479dc05a10504bd8127aa0bab08780380100000000001976a914928f936fc8f9"
            "5dc733d64ebef37d7f57ea70813a88ac00000000",
            "hex_codec",
        )
        tx = Transaction.from_bytes(tx_bytes)[0]

        # Modify the transaction to use the hash160 of a redeem script
        tx.outputs[0].script = Script.build_p2sh(hash160)
        return tx
Exemple #10
0
 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)
Exemple #11
0
 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)
Exemple #12
0
 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:
         raise ModelNotFound()
     return {'payment_txid': rv[0],
             'payment_tx': Transaction.from_hex(rv[1]),
             'amount': rv[2], 'is_redeemed': (rv[3] == 1),
             'deposit_txid': rv[4]}
Exemple #13
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]
Exemple #14
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)
Exemple #15
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:
            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]
Exemple #16
0
    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)