Fix PSBT potential issues
MODIFY
WalletWasabi/Blockchain/Transactions/TransactionFactory.cs
WalletWasabi/Blockchain/Transactions/TransactionFactory.cs
@@ -229,7 +229,7 @@ namespace WalletWasabi.Blockchain.Transactions
builder = builder.AddKeys(signingKeys.ToArray());
builder.SignPSBT(psbt);
- UpdatePSBTInfo(psbt, spentCoins, changeHdPubKey);
+ psbt.Boost(KeyManager.MasterFingerprint, spentCoins.Select(x => (x.HdPubKey, x.ScriptPubKey)), innerWalletOutputs.Select(x => (x.HdPubKey, x.ScriptPubKey)), TransactionStore);
var isPayjoin = false;
if (!KeyManager.IsWatchOnly)
@@ -267,8 +267,6 @@ namespace WalletWasabi.Blockchain.Transactions
}
}
- UpdatePSBTInfo(psbt, spentCoins, changeHdPubKey);
-
var label = SmartLabel.Merge(payments.Requests.Select(x => x.Label).Concat(spentCoins.Select(x => x.Label)));
var outerWalletOutputs = new List<SmartCoin>();
var innerWalletOutputs = new List<SmartCoin>();
@@ -290,6 +288,8 @@ namespace WalletWasabi.Blockchain.Transactions
}
}
+ psbt.Boost(KeyManager.MasterFingerprint, spentCoins.Select(x => (x.HdPubKey, x.ScriptPubKey)), innerWalletOutputs.Select(x => (x.HdPubKey, x.ScriptPubKey)), TransactionStore);
+
foreach (var coin in outerWalletOutputs.Concat(innerWalletOutputs))
{
var foundPaymentRequest = payments.Requests.FirstOrDefault(x => x.Destination.ScriptPubKey == coin.ScriptPubKey);
@@ -349,50 +349,5 @@ namespace WalletWasabi.Blockchain.Transactions
return psbt;
}
-
- private void UpdatePSBTInfo(PSBT psbt, SmartCoin[] spentCoins, HdPubKey changeHdPubKey)
- {
- if (KeyManager.MasterFingerprint is HDFingerprint fp)
- {
- foreach (var coin in spentCoins)
- {
- var rootKeyPath = new RootedKeyPath(fp, coin.HdPubKey.FullKeyPath);
- var coinPubkey = coin.HdPubKey.PubKey;
- psbt.AddKeyPath(coinPubkey, rootKeyPath, coin.ScriptPubKey);
-
- // In case of multisig address the keyPath is added to both inputs and outputs which is a bug. The following code removes the output in that case. https://github.com/MetacoSA/NBitcoin/issues/927
- if (psbt.Outputs.SelectMany(output => output.HDKeyPaths.Keys).Contains(coinPubkey) && psbt.Inputs.SelectMany(input => input.HDKeyPaths.Keys).Contains(coinPubkey))
- {
- foreach (var output in psbt.Outputs)
- {
- output.HDKeyPaths.Remove(coinPubkey);
- }
- }
- }
-
- if (changeHdPubKey is { })
- {
- var rootKeyPath = new RootedKeyPath(fp, changeHdPubKey.FullKeyPath);
- psbt.AddKeyPath(changeHdPubKey.PubKey, rootKeyPath, changeHdPubKey.P2wpkhScript);
- }
- }
-
- foreach (var input in spentCoins)
- {
- var coinInputTxID = input.TransactionId;
- if (TransactionStore.TryGetTransaction(coinInputTxID, out var txn))
- {
- var psbtInputs = psbt.Inputs.Where(x => x.PrevOut.Hash == coinInputTxID);
- foreach (var psbtInput in psbtInputs)
- {
- psbtInput.NonWitnessUtxo = txn.Transaction;
- }
- }
- else
- {
- Logger.LogWarning($"Transaction id:{coinInputTxID} is missing from the TransactionStore. Ignoring...");
- }
- }
- }
}
}

MODIFY
WalletWasabi/Extensions/NBitcoinExtensions.cs
WalletWasabi/Extensions/NBitcoinExtensions.cs
@@ -7,9 +7,11 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.CoinJoin.Common.Crypto;
using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
using WalletWasabi.Models;
using static WalletWasabi.Crypto.SchnorrBlinding;
@@ -401,5 +403,62 @@ namespace NBitcoin
{
return ToCurrency(btc, "USD", usdExchangeRate, privacyMode);
}
+
+ /// <summary>
+ /// Boost PSBT with various data.
+ /// </summary>
+ /// <param name="fp">If not null we'll try to fill the input and output paths on best effort.</param>
+ /// <param name="transactionStore">If not null, we'll try to fill the input txs on best effort.</param>
+ public static void Boost(
+ this PSBT psbt,
+ HDFingerprint? fp,
+ IEnumerable<(HdPubKey hdPubKey, Script script)> innerWalletInputs,
+ IEnumerable<(HdPubKey hdPubKey, Script script)> innerWalletOutputs,
+ AllTransactionStore? transactionStore)
+ {
+ // Add input and outut keypaths.
+ if (fp.HasValue)
+ {
+ // Add input keypaths.
+ foreach ((HdPubKey hdPubKey, Script script) in innerWalletInputs)
+ {
+ AddKeyPath(psbt, fp.Value, hdPubKey, script);
+ }
+
+ // Hack around NBitcoin bug: https://github.com/zkSNACKs/WalletWasabi/issues/4460
+ foreach (var o in psbt.Outputs)
+ {
+ o.HDKeyPaths.Clear();
+ }
+
+ // Add output keypaths.
+ foreach ((HdPubKey hdPubKey, Script script) in innerWalletOutputs)
+ {
+ AddKeyPath(psbt, fp.Value, hdPubKey, script);
+ }
+ }
+
+ // Fill out previous transactions.
+ if (transactionStore is { })
+ {
+ foreach (var psbtInput in psbt.Inputs)
+ {
+ if (transactionStore.TryGetTransaction(psbtInput.PrevOut.Hash, out var tx))
+ {
+ psbtInput.NonWitnessUtxo = tx.Transaction;
+ }
+ else
+ {
+ Logger.LogInfo($"Transaction id: {psbtInput.PrevOut.Hash} is missing from the {nameof(transactionStore)}. Ignoring...");
+ }
+ }
+ }
+ }
+
+ private static void AddKeyPath(PSBT psbt, HDFingerprint fp, HdPubKey hdPubKey, Script script)
+ {
+ var rootKeyPath = new RootedKeyPath(fp, hdPubKey.FullKeyPath);
+ psbt.AddKeyPath(hdPubKey.PubKey, rootKeyPath, script);
+ }
}
}

Fix PSBT potential issues