Skip to content

Commit 4cb68e9

Browse files
committed
Additional checks for nonWitnessUtxo
1 parent d3b6d6a commit 4cb68e9

File tree

4 files changed

+38
-11
lines changed

4 files changed

+38
-11
lines changed

NBitcoin.Tests/PSBTTests.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ public void CantCalculateUnsafeFee()
133133
psbt.AddTransactions(funding);
134134
Assert.True(psbt.TryGetFee(out _));
135135
Assert.True(psbt.TryGetVirtualSize(out _));
136+
137+
psbt.Inputs[0].NonWitnessUtxo = tx;
138+
Assert.False(psbt.TryGetFee(out _));
139+
Assert.NotEmpty(psbt.Inputs[0].CheckSanity());
140+
psbt.Inputs[0].NonWitnessUtxo = funding;
141+
Assert.True(psbt.TryGetFee(out _));
142+
Assert.Empty(psbt.Inputs[0].CheckSanity());
136143
}
137144

138145
[Fact]
@@ -265,12 +272,8 @@ public void CanUpdate()
265272
var psbtWithCoins = PSBT.FromTransaction(tx, Network.Main)
266273
.AddCoins(coins);
267274

268-
Assert.Null(psbtWithCoins.Inputs[0].WitnessUtxo);
269-
Assert.NotNull(psbtWithCoins.Inputs[1].WitnessUtxo);
270-
Assert.Null(psbtWithCoins.Inputs[2].WitnessUtxo);
271-
Assert.NotNull(psbtWithCoins.Inputs[3].WitnessUtxo);
272-
Assert.NotNull(psbtWithCoins.Inputs[4].WitnessUtxo);
273-
Assert.NotNull(psbtWithCoins.Inputs[5].WitnessUtxo);
275+
foreach (var input in psbtWithCoins.Inputs)
276+
Assert.NotNull(input.WitnessUtxo);
274277

275278
// Check if it holds scripts as expected.
276279
Assert.Null(psbtWithCoins.Inputs[0].RedeemScript); // p2pkh

NBitcoin/BIP174/PSBT0.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal PSBT0(Transaction transaction, Network network) : this(CreateMap(transa
2020
if (transaction == null)
2121
throw new ArgumentNullException(nameof(transaction));
2222
tx = transaction.Clone();
23+
tx.PrecomputeHash(true, true);
2324
}
2425

2526
private static Maps CreateMap(Transaction transaction, Network network)
@@ -81,6 +82,7 @@ internal PSBT0(Maps maps, Network network) : base(maps, network, PSBTVersion.PSB
8182
if (!maps.Global.TryRemove<byte[]>([PSBTConstants.PSBT_GLOBAL_UNSIGNED_TX], out var txBytes))
8283
throw new FormatException("Invalid PSBT. No global TX");
8384
tx = Transaction.Load(txBytes, Network);
85+
tx.PrecomputeHash(true, true);
8486
if (tx.Inputs.Any(txin => txin.ScriptSig != Script.Empty || txin.WitScript != WitScript.Empty))
8587
throw new FormatException("Malformed global tx. It should not contain any scriptsig or witness by itself");
8688
if (tx.Inputs.Count + tx.Outputs.Count + 1 != maps.Count)
@@ -110,11 +112,11 @@ internal override void FillMap(Map map)
110112
map.Add([PSBTConstants.PSBT_GLOBAL_UNSIGNED_TX], this.GetGlobalTransaction(true).ToBytes());
111113
}
112114

113-
Transaction tx;
115+
readonly Transaction tx;
114116

115117
internal override Transaction GetGlobalTransaction(bool @unsafe) => @unsafe ? tx : tx.Clone();
116118

117-
class PSBT0Input : PSBTInput
119+
internal class PSBT0Input : PSBTInput
118120
{
119121
internal PSBT0Input(Map map, PSBT0 parent, uint index) : base(map, parent, index)
120122
{
@@ -132,7 +134,11 @@ internal PSBT0Input(Map map, PSBT0 parent, uint index) : base(map, parent, index
132134
public override Sequence Sequence
133135
{
134136
get => txIn.Sequence;
135-
set => txIn.Sequence = value;
137+
set
138+
{
139+
txIn.Sequence = value;
140+
((PSBT0)Parent).tx.PrecomputeHash(true, true);
141+
}
136142
}
137143
}
138144
}

NBitcoin/BIP174/PSBTInput.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ protected virtual void Load(Map map)
185185
internal Transaction GetTransaction() => Parent.GetGlobalTransaction(true);
186186

187187
private Transaction? non_witness_utxo;
188+
// Record the hash of the last time we checked the non_witness_utxo correspond to the outpoint of the input
189+
// If NonWitnessUTXO change, we unset this.
190+
// If the Outpoint change, then non_witness_utxo_check != prevout.Hash
191+
// This is optimization to not have to calculate the hash of non_witness_utxo every time.
192+
internal uint256? non_witness_utxo_check;
188193
private TxOut? witness_utxo;
189194
private PartialSigKVMap partial_sigs = new PartialSigKVMap(PubKeyComparer.Instance);
190195

@@ -199,6 +204,7 @@ public Transaction? NonWitnessUtxo
199204
set
200205
{
201206
non_witness_utxo = value;
207+
non_witness_utxo_check = null;
202208
}
203209
}
204210

@@ -535,6 +541,8 @@ public IList<PSBTError> CheckSanity()
535541
errors.Add(new PSBTError(Index, "non_witness_utxo does not match the transaction id referenced by the global transaction sign"));
536542
validOutpoint = false;
537543
}
544+
else
545+
non_witness_utxo_check = prevOutTxId;
538546
if (PrevOut.N >= NonWitnessUtxo.Outputs.Count)
539547
{
540548
errors.Add(new PSBTError(Index, "Global transaction referencing an out of bound output in non_witness_utxo"));
@@ -797,6 +805,13 @@ private string GetName(uint sighashType)
797805
{
798806
if (PrevOut.N >= NonWitnessUtxo.Outputs.Count)
799807
return null;
808+
if (non_witness_utxo_check != PrevOut.Hash)
809+
{
810+
if (NonWitnessUtxo.GetHash() != PrevOut.Hash)
811+
return null;
812+
non_witness_utxo_check = PrevOut.Hash;
813+
}
814+
non_witness_utxo_check = PrevOut.Hash;
800815
return NonWitnessUtxo.Outputs[PrevOut.N];
801816
}
802817
if (WitnessUtxo != null)

NBitcoin/BIP174/PartiallySignedTransaction.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ public PSBT AddTransactions(params Transaction[] parentTransactions)
313313
continue;
314314
var output = tx.Outputs[input.PrevOut.N];
315315
input.NonWitnessUtxo = tx;
316+
if (input is PSBT0.PSBT0Input input0)
317+
input0.non_witness_utxo_check = input.PrevOut.Hash;
316318
if (Network.Consensus.NeverNeedPreviousTxForSigning ||
317319
input.GetCoin()?.IsMalleable is false)
318320
input.WitnessUtxo = output;
@@ -635,11 +637,12 @@ public PSBT SignWithKeys(params ISecret[] keys)
635637

636638
public PSBT SignWithKeys(params Key[] keys)
637639
{
638-
AssertSanity();
640+
var errors = CheckSanity();
641+
var hasError = new HashSet<uint>(errors.Select(e => e.InputIndex));
639642
var signingOptions = GetSigningOptions(null);
640643
foreach (var key in keys)
641644
{
642-
foreach (var input in this.Inputs)
645+
foreach (var input in this.Inputs.Where(i => !hasError.Contains(i.Index)))
643646
{
644647
input.Sign(key, signingOptions);
645648
}

0 commit comments

Comments
 (0)