Described here is a variant of what has previously been published under the name "P2EP" or Pay-to-endpoint, in which A pays B but B contributes utxos, i.e. it's a coinjoin-payment.
I'm using the term "payjoin" here to refer to using that idea, but not including a URI/endpoint specific to B, and not allowing (as a merchant would) arbitrary payments, which opens up certain problems around snooping attackers (more on this below). So payjoin just means "A pays B but B actively participates and passes across utxos as extra inputs".
I'll defer a more features-focused and non-tech friendly description of what this means to a later blogpost.
Here is just the step by step protocol interactions, both at the user level and under-the-hood.
Alice will be Sender, Bob will be Receiver. Note that the Sender is a Taker class in JM code, and the Receiver a Maker class. This is because they inherit the main functional difference that one waits to complete the tx, and the other initiates.
Bob starts by running
python receive-payjoin.py -m mixdepth <walletname> <amount-in-sats>.
After normal Joinmarket start up (wallet sync, connect to message channels, also: Bob's bot puts a fake offer into the joinmarket pit; he effectively runs a spoofed yieldgenerator, but doesn't respond (for now) to requests in private-message)), Bob gets a printout like:
2018-12-27 17:57:17+0100 [-] Your receiving address is: 2NCiuh9iJ3s8vEQGUWAfeZHTwUVqE5gEdB5
2018-12-27 17:57:17+0100 [-] You will receive amount: 50000000 satoshis.
2018-12-27 17:57:17+0100 [-] The sender also needs to know your ephemeral nickname: J57Pwz4XHyyGu9QB
2018-12-27 17:57:17+0100 [-] This information has been stored in a file payjoin.txt; send it to your counterparty when you are ready.
This information is passed out of band to Alice (i.e. address, amount, nick). Bob's bot now waits as long as needed until Alice turns up and completes the protocol.
Alice runs as follows:
python sendpayment.py -m 1 <alicewalletname> 50000000 2NCiuh9iJ3s8vEQGUWAfeZHTwUVqE5gEdB5 -T J57Pwz4XHyyGu9QB
... using, of course, the data passed out of band. What happens next will usually not require further intervention, and after some seconds Alice will broadcast a transaction paying net 0.5 btc to Bob, in which at least one of his utxos is included.
- Protocol
Alice sends in plaintext !pubkey <Alice-pubkey>
.
Bob receives and in plaintext also sends !pubkey <Bob-pubkey>
back.
These are of course ephemeral ECDH pubkeys used just for this session of communication.
Both sides now set up the end-to-end encryption (the existing codebase handles this
automatically; both sides have crypto-box libnacl objects; and !tx
messages are
enforced to only be transferred under encryption).
Notice that any Alice-attacker can do this step, but it's harmless because the next step, which is encrypted, authenticates before Bob has sent any identifying info
Now Alice constructs a kind of dummy-tx: It takes form:
Inputs: <Alice utxos selected to pay .5> Outputs: <2NCiuh9iJ3s8vEQGUWAfeZHTwUVqE5gEdB5: 50000000>, <Alice-change: remainder>
Alice doesn't bother to calculate a fee here (so it's zero); this was not really a transaction, just a proposal of inputs and change. We send it as a transaction, because, as noted in the code comments:
however for the purposes of code reuse and signalling
that we are the right counterparty, sending a transaction,
containing the outputs, works fine.
The meaning of "signalling" here should be fairly obvious: by passing a transaction with the correct destination and amount, we ~ prove that we are the intended sender. Note that this line of reasoning is a bit too weak for the general merchant case, because anonymous senders operating over automated protocols may use this to make repeated spend request and collect your utxos, then not pay, damaging your privacy. The previously linked blog post I believe covers this issue in some detail (it was the subject of considerable debate in the London Coinjoin meetup).
The intention here is that for an ad-hoc payment this is not really a problem. However, TODO: I will alter the code so that Alice actually sends a signed version, she loses nothing this way and Bob can broadcast it if something goes wrong Alice-side.
So after Bob receives this !tx
encrypted transaction message, he decrypts, and
deserializes the transaction, checks that it conforms in destination, amount, and
bitcoin balance, checks that the inputs are valid, then appends his own inputs.
He selects coins to fulfil the same amount as the destination (this is a heuristic that
could easily change if someone has a better idea; use twice the amount? half? etc.). If
he fails to select that much he tries half as much, and repeats the halving three times
before giving up, i.e. at the very least he adds 1/8 the amount of coins as he is receiving.
Then he of course alters the output amount at the destination address to account for the coins
that he's added. Finally, he also alters the change output amount proposed by Alice to account
for the bitcoin network fee that he calculates. If it's dusty, he drops it.
Finally if all checks pass he signs each input he himself has added (usually 1),
and reserializes the now partially signed transaction.
He then sends another !tx
message in return to Alice, containing this partially signed
transaction.
Alice receives, decrypts and deserializes, and does the expected checks: Checks the inputs that are signed, verify correctly. Checks that her utxos are included. Checks that the destination receives net the originally intended amount. Calculates the bitcoin fee and checks that it's between 0.3 and 3x the fee that her wallet estimates for current network conditions. If not, the user is prompted to decide. If all checks pass, signs all her inputs, and broadcasts the payment.
In short: Alice is a P2EPTaker and runs sendpayment. Bob is a P2EPMaker and runs receive-payjoin.
They both use standard Joinmarket wallets. Alice connects and spoofs Taker behaviour, Bob
connects (first) and spoofs Maker behaviour. Bob sends destn, amt and nick out of band.
Alice starts with !pubkey
, Bob responds the same, both sides set up encryption, Alice
calculates and sends !tx
encrypted proposal, Bob adds his input(s), signs and sends
back !tx
, Alice checks and if OK co-signs and broadcasts.
OK just had another quick think about it, and I think I understand as follows:
UIH = "unnecessary input heuristic", basically "a wallet wouldn't choose more utxos to spend in this scenario".
"UIH1" : one output is smaller than any input. This heuristically implies that that output is not a payment, and must therefore be a change output.
"UIH2": one input is larger than any output. This heuristically implies that no output is a payment, or, to say it better, it implies that this is not a normal wallet-created payment, it's something strange/exotic.
If UIH2 is true, UIH1 is true.Doh! Not true, sorry.So we just have to focus on UIH2. Avoiding UIH1 condition is nice, because it means that both outputs could be the payment; but in any case the normal blockchain analysis will be wrong about the payment amount. If we don't avoid the UIH2 condition, though, we lose the stega- aspect which is at least 50% of the appeal of this technique.
So I'll just think about how to do wallet selection to make avoiding the UIH2 condition to whatever extent possible. My crude first impression: we want the receiver's wallet to have ~ same OOM of coins as sender. If I have 1.00btc and want to pay 0.01btc, then if receiver doesn't have 1-ish btc to contribute, it'll be hard to avoid UIH2.