From 539fd854988025dc5846299ac5142e39404e236f Mon Sep 17 00:00:00 2001 From: DanGould Date: Fri, 31 May 2024 11:39:24 -0400 Subject: [PATCH 1/3] BIP78: Allow mixed inputs Disallowing mixed inputs was based on incorrect assumption that no wallet supports mixed inputs and thus mixed inputs imply PayJoin. However there are at least three wallets supporting mixed inputs. (Confirmed: Bitcoin Core, LND, Coinomi) Thus it makes sense to enable mixed inputs to avoid a payjoin-specific fingerptint. To avoid compatibility issues a grace period is suggested. Co-authored-by: Martin Habovstiak --- bip-0078.mediawiki | 53 +++++++++------------------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/bip-0078.mediawiki b/bip-0078.mediawiki index 352872562c..49c29e96cd 100644 --- a/bip-0078.mediawiki +++ b/bip-0078.mediawiki @@ -106,6 +106,9 @@ The original PSBT MUST: The original PSBT MAY: * Have outputs unrelated to the payment for batching purpose. +The original PSBT SHOULD NOT: +* Include mixed input types until September 2024. Mixed inputs were previously completely disallowed so this gives some grace period for receivers to update. + The payjoin proposal MUST: * Use all the inputs from the original PSBT. * Use all the outputs which do not belongs to the receiver from the original PSBT. @@ -115,6 +118,9 @@ The payjoin proposal MUST: The payjoin proposal MAY: * Add, remove or modify the outputs belonging to the receiver. +The payjoin proposal SHOULD NOT: +* Include mixed input types until September 2024. Mixed inputs were previously completely disallowed so this gives some grace period for senders to update. + The payjoin proposal MUST NOT: * Shuffle the order of inputs or outputs, the additional outputs or additional inputs must be inserted at a random index. * Decrease the absolute fee of the original transaction. @@ -215,26 +221,7 @@ To prevent this, the sender can agree to pay more fee so the receiver make sure When a sender pick a specific fee rate, the sender expects the transaction to be confirmed after a specific amount of time. But if the receiver adds an input without bumping the fee of the transaction, the payjoin transaction fee rate will be lower, and thus, longer to confirm. -Our recommendation for maxadditionalfeecontribution= is originalPSBTFeeRate * vsize(sender_input_type). - -{| class="wikitable" -!sender_input_type -!vsize(sender_input_type) -|- -|P2WPKH -|68 -|- -|P2PKH -|148 -|- -|P2SH-P2WPKH -|91 -|- -|P2TR -|58 -|} - - +Our recommendation for maxadditionalfeecontribution= is originalPSBTFeeRate * 110. ===Receiver's original PSBT checklist=== @@ -242,7 +229,6 @@ The receiver needs to do some check on the original PSBT before proceeding: * Non-interactive receivers (like a payment processor) need to check that the original PSBT is broadcastable. * * If the sender included inputs in the original PSBT owned by the receiver, the receiver must either return error original-psbt-rejected or make sure they do not sign those inputs in the payjoin proposal. -* If the sender's inputs are all from the same scriptPubKey type, the receiver must match the same type. If the receiver can't match the type, they must return error unavailable. * Make sure that the inputs included in the original transaction have never been seen before. ** This prevent [[#probing-attack|probing attacks]]. ** This prevent reentrant payjoin, where a sender attempts to use payjoin transaction as a new original transaction for a new payjoin. @@ -268,7 +254,6 @@ The sender should check the payjoin proposal before signing it to prevent a mali *** Verify the PSBT input is finalized *** Verify that non_witness_utxo or witness_utxo are filled in. ** Verify that the payjoin proposal did not introduced mixed input's sequence. -** Verify that the payjoin proposal did not introduced mixed input's type. ** Verify that all of sender's inputs from the original PSBT are in the proposal. * For each outputs in the proposal: ** Verify that no keypaths is in the PSBT output @@ -429,7 +414,6 @@ public async Task RequestPayjoin( var endpoint = bip21.ExtractPayjointEndpoint(); if (signedPSBT.IsAllFinalized()) throw new InvalidOperationException("The original PSBT should not be finalized."); - ScriptPubKeyType inputScriptType = wallet.ScriptPubKeyType(); PSBTOutput feePSBTOutput = null; bool allowOutputSubstitution = !optionalParameters.DisableOutputSubstitution; @@ -520,9 +504,6 @@ public async Task RequestPayjoin( if (proposedPSBTInput.NonWitnessUtxo == null && proposedPSBTInput.WitnessUtxo == null) throw new PayjoinSenderException("The receiver did not specify non_witness_utxo or witness_utxo for one of their inputs"); sequences.Add(proposedTxIn.Sequence); - // Verify that the payjoin proposal did not introduced mixed inputs' type. - if (inputScriptType != proposedPSBTInput.GetInputScriptPubKeyType()) - throw new PayjoinSenderException("Mixed input type detected in the proposal"); } } @@ -554,7 +535,7 @@ public async Task RequestPayjoin( if (isOriginalOutput || substitutedOutput) { originalOutputs.Dequeue(); - if (output.OriginalTxOut == feeOutput) + if (originalOutput.OriginalTxOut == feeOutput) { var actualContribution = feeOutput.Value - proposedPSBTOutput.Value; // The amount that was subtracted from the output's value is less than or equal to maxadditionalfeecontribution @@ -564,8 +545,9 @@ public async Task RequestPayjoin( if (actualContribution > additionalFee) throw new PayjoinSenderException("The actual contribution is not only paying fee"); // Make sure the actual contribution is only paying for fee incurred by additional inputs + // This assumes an additional input can be up to 110 bytes. int additionalInputsCount = proposalGlobalTx.Inputs.Count - originalGlobalTx.Inputs.Count; - if (actualContribution > originalFeeRate * GetVirtualSize(inputScriptType) * additionalInputsCount) + if (actualContribution > originalFeeRate * 110 * additionalInputsCount) throw new PayjoinSenderException("The actual contribution is not only paying for additional inputs"); } else if (allowOutputSubstitution && output.OriginalTxOut.ScriptPubKey == paymentScriptPubKey) @@ -600,21 +582,6 @@ public async Task RequestPayjoin( return proposal; } -int GetVirtualSize(ScriptPubKeyType? scriptPubKeyType) -{ - switch (scriptPubKeyType) - { - case ScriptPubKeyType.Legacy: - return 148; - case ScriptPubKeyType.Segwit: - return 68; - case ScriptPubKeyType.SegwitP2SH: - return 91; - default: - return 110; - } -} - // Finalize the signedPSBT and remove confidential information PSBT CreateOriginalPSBT(PSBT signedPSBT) { From 72d8bb04b81c9b4298a3f426e9ffa2f1d55bbbc5 Mon Sep 17 00:00:00 2001 From: DanGould Date: Fri, 31 May 2024 11:43:58 -0400 Subject: [PATCH 2/3] BIP78: Clarify output substitution The original text is ambiguous to allowing transaction cut-through or not. Transaction cut-through enables savings by posting multiple transaction intents through a single 2-party payjoin and is used in practice in payjoins today. Let's explicitly allow it in the text. Co-authored-by: Martin Habovstiak --- bip-0078.mediawiki | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bip-0078.mediawiki b/bip-0078.mediawiki index 49c29e96cd..08e3129b75 100644 --- a/bip-0078.mediawiki +++ b/bip-0078.mediawiki @@ -116,7 +116,7 @@ The payjoin proposal MUST: * Only fill the witnessUTXO or nonWitnessUTXO for the additional inputs. The payjoin proposal MAY: -* Add, remove or modify the outputs belonging to the receiver. +* Add, or replace the outputs belonging to the receiver unless output substitution is disabled. The payjoin proposal SHOULD NOT: * Include mixed input types until September 2024. Mixed inputs were previously completely disallowed so this gives some grace period for senders to update. @@ -329,7 +329,7 @@ On top of this the receiver can poison analysis by randomly faking a round amoun ===Payment output substitution=== -Unless disallowed by sender explicitly via `disableoutputsubstitution=true` or by the BIP21 url via query parameter the `pjos=0`, the receiver is free to decrease the amount, remove, or change the scriptPubKey output paying to himself. +Unless disallowed by sender explicitly via `disableoutputsubstitution=true` or by the BIP21 url via query parameter the `pjos=0`, the receiver is free to decrease the amount, or change the scriptPubKey output paying to himself. Note that if payment output substitution is disallowed, the reveiver can still increase the amount of the output. (See [[#reference-impl|the reference implementation]]) For example, if the sender's scriptPubKey type is P2WPKH while the receiver's payment output in the original PSBT is P2SH, then the receiver can substitute the payment output to be P2WPKH to match the sender's scriptPubKey type. From bd01a269e5d51d0852be37ca6281f34833f43cdc Mon Sep 17 00:00:00 2001 From: DanGould Date: Fri, 31 May 2024 11:45:36 -0400 Subject: [PATCH 3/3] BIP78: Doc `amount` parameter not required It's an optional parameter in BIP 21 Bitcoin URIs, but it doesn't hurt to make it explicit. Co-authored-by: Martin Habovstiak --- bip-0078.mediawiki | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bip-0078.mediawiki b/bip-0078.mediawiki index 08e3129b75..393cbd1424 100644 --- a/bip-0078.mediawiki +++ b/bip-0078.mediawiki @@ -131,6 +131,8 @@ This proposal is defining the following new [[bip-0021.mediawiki|BIP 21 URI]] pa * pj=: Represents an http(s) endpoint which the sender can POST the original PSBT. * pjos=0: Signal to the sender that they MUST disallow [[#output-substitution|payment output substitution]]. (See [[#unsecured-payjoin|Unsecured payjoin server]]) +Note: the amount parameter is *not* required. + ===Optional parameters=== When the payjoin sender posts the original PSBT to the receiver, he can optionally specify the following HTTP query string parameters: