electrum icon indicating copy to clipboard operation
electrum copied to clipboard

WIP: payjoin as sender

Open rage-proof opened this issue 5 years ago • 13 comments

Hi, this PR is the try to implement the payjoin functionality as defined in BIP-78. It only covers the sender part in the interactive-tx and not the receiver stuff. I tested it successful on Testnet with BTCPay Server using a hot wallet. I'm not sure if its a good approach of even intended, but I added the payjoin-link in some functions and classes to forward it to the transaction window. It's a draft and you can do with it whatever you want to.

ToDo's: These points are the obvious(for me) that must be finished.

  1. ~~Added the 'requests' package, but probably it's not necessary and can be replaced by aiohttp package that is often present in electrum.~~
  2. ~~The validation that checks if the newly added input has the same script-type(p2pkh, p2wpkh) as our own input is still missing. I don't know if there exist already a proper function in electrum for determine the script-type from a witness-utxo in a psbt.~~
  3. The check if the order of inputs and outputs was not changed. I didn't touch this, because I don't understand the BIP at this specific point.

Potential problem: I saw this: https://github.com/btcpayserver/btcpayserver/issues/1631 & https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd . Therefore probably a payjoin between HW-wallets and a merchant using BTCPay-Server is not feasible, but I didn't test it yet.

rage-proof avatar Dec 02 '20 23:12 rage-proof

thanks! indeed, it would be nice to remove requests. we had that dependency earlier, and it took us quite some effort to get rid of it.

ecdsa avatar Dec 04 '20 11:12 ecdsa

Removed requests as dependency. Found a function that is designed for doing http requests and fits into the network event-loop.

rage-proof avatar Dec 08 '20 12:12 rage-proof

related: https://github.com/spesmilo/electrum/issues/6585

SomberNight avatar Dec 08 '20 15:12 SomberNight

This pull requests adds a bip78_payjoin field to Invoice. Since invoices are saved in the wallet file, a wallet_db upgrade must be added.

ecdsa avatar Jan 13 '21 13:01 ecdsa

Hi @ecdsa I don't know what to do for this:

Since invoices are saved in the wallet file, a wallet_db upgrade must be added.

rage-proof avatar Feb 15 '21 07:02 rage-proof

@rage-proof check out the code in wallet_db.py. An upgrade is required so that your code is compatible with wallet files created before your code is merged.

ecdsa avatar Feb 15 '21 09:02 ecdsa

rebased

rage-proof avatar Feb 23 '21 20:02 rage-proof

Do you know of an easy way to test this on testnet? E.g. is there some public "testnet merchant" running btcpayserver with payjoin enabled?

SomberNight avatar Feb 26 '21 13:02 SomberNight

You can create an account here https://testnet.demo.btcpayserver.org/ and test it; that's how I did it. This is for example an invoice link with payjoin enabled: https://testnet.demo.btcpayserver.org/payment-requests/8001bd04-c095-473e-bafc-f90954d969ec .

rage-proof avatar Mar 03 '21 08:03 rage-proof

Looking at code, this does not support .onion payjoin callback URLs, right? That is the only thing JoinMarket payjoin receiver currently supports. Would be cool to have it in Electrum if user has "Use Tor proxy at port 9050" enabled in settings.

kristapsk avatar Apr 01 '21 06:04 kristapsk

Something looks broken. I've created this test case based on official test vectors:

import unittest
from electrum.transaction import PayjoinTransaction, PartialTransaction

class PayJoinTestCase(unittest.TestCase):
    def test_official_vectors(self):
        original_psbt = PartialTransaction.from_raw_psbt(b"cHNidP8BAHMCAAAAAY8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////AtyVuAUAAAAAF6kUHehJ8GnSdBUOOv6ujXLrWmsJRDCHgIQeAAAAAAAXqRR3QJbbz0hnQ8IvQ0fptGn+votneofTAAAAAAEBIKgb1wUAAAAAF6kU3k4ekGHKWRNbA1rV5tR5kEVDVNCHAQcXFgAUx4pFclNVgo1WWAdN1SYNX8tphTABCGsCRzBEAiB8Q+A6dep+Rz92vhy26lT0AjZn4PRLi8Bf9qoB/CMk0wIgP/Rj2PWZ3gEjUkTlhDRNAQ0gXwTO7t9n+V14pZ6oljUBIQMVmsAaoNWHVMS02LfTSe0e388LNitPa1UQZyOihY+FFgABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUAAA=")
        print(original_psbt.to_json())
        original_psbt.update_txin_script_type()
        proposal = b"cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcBBBYAFMeKRXJTVYKNVlgHTdUmDV/LaYUwIgYDFZrAGqDVh1TEtNi300ntHt/PCzYrT2tVEGcjooWPhRYYSFzWUDEAAIABAACAAAAAgAEAAAAAAAAAAAEBIICEHgAAAAAAF6kUyPLL+cphRyyI5GTUazV0hF2R2NWHAQcXFgAUX4BmVeWSTJIEwtUb5TlPS/ntohABCGsCRzBEAiBnu3tA3yWlT0WBClsXXS9j69Bt+waCs9JcjWtNjtv7VgIge2VYAaBeLPDB6HGFlpqOENXMldsJezF9Gs5amvDQRDQBIQJl1jz1tBt8hNx2owTm+4Du4isx0pmdKNMNIjjaMHFfrQABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUIgICygvBWB5prpfx61y1HDAwo37kYP3YRJBvAjtunBAur3wYSFzWUDEAAIABAACAAAAAgAEAAAABAAAAAAA="

        payjoin = PayjoinTransaction()
        payjoin.set_payjoin_original(original_psbt)
        payjoin.define_sender_params()
        payjoin.set_payjoin_proposal(proposal)
        payjoin.payjoin_proposal.update_txin_script_type()
        payjoin.validate_payjoin_proposal()

I got this exception:

  File "/home/user/electrum/electrum/transaction.py", line 2115, in define_sender_params
    raise ValueError(f"Transaction using a type that is unsupported for Payjoin."
ValueError: Transaction using a type that is unsupported for Payjoin.Type: unknown

If I understand it correctly the previously-written code is incapable of analyzing the input correctly (or test vectors are broken?).

Kixunil avatar Jul 08 '21 21:07 Kixunil

If I understand it correctly the previously-written code is incapable of analyzing the input correctly (or test vectors are broken?).

Yeah, thanks for testing. The issue was that the nested script (here P2WPKH type of form: 0 + <20 bytes>) can be in the fields RedeemScript or ScriptSig. The code tested just for the first case. I adjusted it.

This code will now execute. I added one line for _additionalfeeoutputindex( = index of the change output), because this can only be determined when the tx was created and belongs to the wallet itself.

import unittest
from electrum.transaction import PayjoinTransaction, PartialTransaction

class PayJoinTestCase(unittest.TestCase):

    def test_official_vectors(self):
        original_psbt = PartialTransaction.from_raw_psbt(b"cHNidP8BAHMCAAAAAY8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////AtyVuAUAAAAAF6kUHehJ8GnSdBUOOv6ujXLrWmsJRDCHgIQeAAAAAAAXqRR3QJbbz0hnQ8IvQ0fptGn+votneofTAAAAAAEBIKgb1wUAAAAAF6kU3k4ekGHKWRNbA1rV5tR5kEVDVNCHAQcXFgAUx4pFclNVgo1WWAdN1SYNX8tphTABCGsCRzBEAiB8Q+A6dep+Rz92vhy26lT0AjZn4PRLi8Bf9qoB/CMk0wIgP/Rj2PWZ3gEjUkTlhDRNAQ0gXwTO7t9n+V14pZ6oljUBIQMVmsAaoNWHVMS02LfTSe0e388LNitPa1UQZyOihY+FFgABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUAAA=")
        print(original_psbt.to_json())
        original_psbt.update_txin_script_type()
        proposal = b"cHNidP8BAJwCAAAAAo8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////jye60aAl3JgZdaIERvjkeh72VYZuTGH/ps2I4l0IO4MBAAAAAP7///8CJpW4BQAAAAAXqRQd6EnwadJ0FQ46/q6NcutaawlEMIcACT0AAAAAABepFHdAltvPSGdDwi9DR+m0af6+i2d6h9MAAAAAAQEgqBvXBQAAAAAXqRTeTh6QYcpZE1sDWtXm1HmQRUNU0IcBBBYAFMeKRXJTVYKNVlgHTdUmDV/LaYUwIgYDFZrAGqDVh1TEtNi300ntHt/PCzYrT2tVEGcjooWPhRYYSFzWUDEAAIABAACAAAAAgAEAAAAAAAAAAAEBIICEHgAAAAAAF6kUyPLL+cphRyyI5GTUazV0hF2R2NWHAQcXFgAUX4BmVeWSTJIEwtUb5TlPS/ntohABCGsCRzBEAiBnu3tA3yWlT0WBClsXXS9j69Bt+waCs9JcjWtNjtv7VgIge2VYAaBeLPDB6HGFlpqOENXMldsJezF9Gs5amvDQRDQBIQJl1jz1tBt8hNx2owTm+4Du4isx0pmdKNMNIjjaMHFfrQABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUIgICygvBWB5prpfx61y1HDAwo37kYP3YRJBvAjtunBAur3wYSFzWUDEAAIABAACAAAAAgAEAAAABAAAAAAA="

        payjoin = PayjoinTransaction()
        payjoin.set_payjoin_original(original_psbt)
        payjoin.define_sender_params()
        payjoin._additionalfeeoutputindex = 0 ### this can only determined when the input belongs to the wallet itself
        payjoin.set_payjoin_proposal(proposal)
        payjoin.payjoin_proposal.update_txin_script_type()
        payjoin.validate_payjoin_proposal()

rage-proof avatar Aug 16 '21 22:08 rage-proof

Cool! Will try to test it some more later. Looking forward into this being available!

Kixunil avatar Aug 17 '21 09:08 Kixunil