diff --git a/NBitcoin.Altcoins/Litecoin.cs b/NBitcoin.Altcoins/Litecoin.cs
index 88261ec7e..2f7d42d22 100644
--- a/NBitcoin.Altcoins/Litecoin.cs
+++ b/NBitcoin.Altcoins/Litecoin.cs
@@ -219,6 +219,9 @@ public override void ReadWrite(BitcoinStream stream)
}
else
{
+ if (Inputs.Count == 0 && !stream.AllowNoInputs)
+ throw new InvalidOperationException("The transaction must have at least one input");
+
stream.ReadWrite(ref nVersion);
if (witSupported)
diff --git a/NBitcoin.Tests/ChainTests.cs b/NBitcoin.Tests/ChainTests.cs
index b37446f0d..09e046788 100644
--- a/NBitcoin.Tests/ChainTests.cs
+++ b/NBitcoin.Tests/ChainTests.cs
@@ -608,7 +608,9 @@ public ChainedBlock AppendBlock(ChainedBlock previous, params ConcurrentChain[]
var nonce = RandomUtils.GetUInt32();
foreach (var chain in chains)
{
- var block = TestUtils.CreateFakeBlock(Network.Main.CreateTransaction());
+ var tx = Network.Main.CreateTransaction();
+ tx.Inputs.Add();
+ var block = TestUtils.CreateFakeBlock(tx);
block.Header.HashPrevBlock = previous == null ? chain.Tip.HashBlock : previous.HashBlock;
block.Header.Nonce = nonce;
if (!chain.TrySetTip(block.Header, out last))
diff --git a/NBitcoin.Tests/ColoredCoinsTests.cs b/NBitcoin.Tests/ColoredCoinsTests.cs
index 65fe59d34..0dbb37388 100644
--- a/NBitcoin.Tests/ColoredCoinsTests.cs
+++ b/NBitcoin.Tests/ColoredCoinsTests.cs
@@ -151,6 +151,7 @@ public void CanColorizeSpecScenario()
Assert.True(destroyed[0].Id == a2.Id);
var prior = Network.Main.CreateTransaction();
+ prior.Inputs.Add();
prior.Outputs.Add(new TxOut(dust, a1.ScriptPubKey));
prior.Outputs.Add(new TxOut(dust, a2.ScriptPubKey));
prior.Outputs.Add(new TxOut(dust, h.ScriptPubKey));
diff --git a/NBitcoin.Tests/Generators/PSBTGenerator.cs b/NBitcoin.Tests/Generators/PSBTGenerator.cs
index e749d736d..bea300588 100644
--- a/NBitcoin.Tests/Generators/PSBTGenerator.cs
+++ b/NBitcoin.Tests/Generators/PSBTGenerator.cs
@@ -38,7 +38,7 @@ from psbt in SanePSBT(network)
///
///
public static Gen SanePSBT(Network network) =>
- from inputN in Gen.Choose(0, 8)
+ from inputN in Gen.Choose(1, 8)
from scripts in Gen.ListOf(inputN, ScriptGenerator.RandomScriptSig())
from txOuts in Gen.Sequence(scripts.Select(sc => OutputFromRedeem(sc)))
from prevN in Gen.Choose(0, 5)
diff --git a/NBitcoin.Tests/PSBTTests.cs b/NBitcoin.Tests/PSBTTests.cs
index 1ae6a5503..bd8f8752d 100644
--- a/NBitcoin.Tests/PSBTTests.cs
+++ b/NBitcoin.Tests/PSBTTests.cs
@@ -56,6 +56,7 @@ public static void ShouldCalculateBalanceOfHDKey(PSBTVersion version)
var bob = bobMaster.Derive(new KeyPath("4/5/6"));
var funding = network.CreateTransaction();
+ funding.Inputs.Add();
funding.Outputs.Add(Money.Coins(1.0m), alice);
funding.Outputs.Add(Money.Coins(1.5m), bob);
@@ -642,8 +643,9 @@ public void CanRebaseKeypathInPSBT()
var accountExtKey = masterExtkey.Derive(new KeyPath("0'/0'/0'"));
var accountRootedKeyPath = new KeyPath("0'/0'/0'").ToRootedKeyPath(masterExtkey);
uint hardenedFlag = 0x80000000U;
- retry:
+ retry:
Transaction funding = masterExtkey.Network.CreateTransaction();
+ funding.Inputs.Add();
funding.Outputs.Add(Money.Coins(2.0m), accountExtKey.Derive(0 | hardenedFlag).ScriptPubKey);
funding.Outputs.Add(Money.Coins(2.0m), accountExtKey.Derive(1 | hardenedFlag).ScriptPubKey);
#if HAS_SPAN
diff --git a/NBitcoin.Tests/ProtocolTests.cs b/NBitcoin.Tests/ProtocolTests.cs
index 121dcf372..bf76c54a7 100644
--- a/NBitcoin.Tests/ProtocolTests.cs
+++ b/NBitcoin.Tests/ProtocolTests.cs
@@ -1102,7 +1102,9 @@ public void CanConnectMultipleTimeToServer()
public void CanRoundtripCmpctBlock()
{
Block block = Network.Main.Consensus.ConsensusFactory.CreateBlock();
- block.Transactions.Add(Network.Main.Consensus.ConsensusFactory.CreateTransaction());
+ var tx = Network.Main.Consensus.ConsensusFactory.CreateTransaction();
+ tx.Inputs.Add();
+ block.Transactions.Add(tx);
var cmpct = new CmpctBlockPayload(block);
cmpct.Clone();
}
diff --git a/NBitcoin.Tests/TestUtils.cs b/NBitcoin.Tests/TestUtils.cs
index c5f9de5b5..7ed95e920 100644
--- a/NBitcoin.Tests/TestUtils.cs
+++ b/NBitcoin.Tests/TestUtils.cs
@@ -47,7 +47,9 @@ public static Block CreateFakeBlock(Transaction tx)
public static Block CreateFakeBlock()
{
- var block = TestUtils.CreateFakeBlock(Network.Main.CreateTransaction());
+ var tx = Network.Main.CreateTransaction();
+ tx.Inputs.Add();
+ var block = TestUtils.CreateFakeBlock(tx);
block.Header.HashPrevBlock = new uint256(RandomUtils.GetBytes(32));
block.Header.Nonce = RandomUtils.GetUInt32();
return block;
diff --git a/NBitcoin.Tests/script_tests.cs b/NBitcoin.Tests/script_tests.cs
index bf5d7ef7d..577896a4a 100644
--- a/NBitcoin.Tests/script_tests.cs
+++ b/NBitcoin.Tests/script_tests.cs
@@ -149,6 +149,7 @@ public void BIP65_tests()
private void BIP65_testsCore(LockTime target, LockTime now, bool expectedResult)
{
Transaction tx = Network.CreateTransaction();
+ tx.Inputs.Add();
tx.Outputs.Add(new TxOut()
{
ScriptPubKey = new Script(Op.GetPushOp(target.Value), OpcodeType.OP_CHECKLOCKTIMEVERIFY)
@@ -736,6 +737,7 @@ public void script_CHECKMULTISIG12()
);
Transaction txFrom12 = Network.CreateTransaction();
+ txFrom12.Inputs.Add();
txFrom12.Outputs.Add(new TxOut());
txFrom12.Outputs[0].ScriptPubKey = scriptPubKey12;
@@ -780,6 +782,7 @@ public void script_CHECKMULTISIG23()
var txFrom23 = Network.CreateTransaction();
+ txFrom23.Inputs.Add();
txFrom23.Outputs.Add(new TxOut());
txFrom23.Outputs[0].ScriptPubKey = scriptPubKey23;
diff --git a/NBitcoin.Tests/transaction_tests.cs b/NBitcoin.Tests/transaction_tests.cs
index f31f18056..51fa8aa7c 100644
--- a/NBitcoin.Tests/transaction_tests.cs
+++ b/NBitcoin.Tests/transaction_tests.cs
@@ -988,6 +988,7 @@ public void CanBuildShuffleColoredTransaction()
var repo = new NoSqlColoredTransactionRepository(new NoSqlTransactionRepository(), new InMemoryNoSqlRepository());
var init = Network.CreateTransaction();
+ init.Inputs.Add();
init.Outputs.Add("1.0", gold.PubKey);
init.Outputs.Add("1.0", silver.PubKey);
init.Outputs.Add("1.0", satoshi.PubKey);
@@ -1279,6 +1280,7 @@ public void CanBuildColoredTransaction()
var repo = new NoSqlColoredTransactionRepository();
var init = Network.CreateTransaction();
+ init.Inputs.Add();
init.Outputs.Add("1.0", gold.PubKey);
init.Outputs.Add("1.0", silver.PubKey);
init.Outputs.Add("1.0", satoshi.PubKey);
@@ -1375,6 +1377,7 @@ public void CanBuildColoredTransaction()
//Gold receive 2.5 BTC
tx = txBuilder.Network.Consensus.ConsensusFactory.CreateTransaction();
+ tx.Inputs.Add();
tx.Outputs.Add("2.5", gold.PubKey);
repo.Transactions.Put(tx.GetHash(), tx);
@@ -1762,6 +1765,7 @@ public void CanEstimateFees()
builder.SendEstimatedFees(rate);
signed = builder.BuildTransaction(true);
Assert.True(builder.Verify(signed, estimatedFees));
+ Assert.Equal(1174, builder.EstimateSize(signed));
}
private Coin RandomCoin(Money amount, IDestination dest, bool p2sh)
@@ -1924,23 +1928,6 @@ void BitcoinStreamCoverageCore(TItem[] input, BitcoinStreamCoverageCoreDe
AssertEx.CollectionEquals(before, input);
}
- [Fact]
- [Trait("UnitTest", "UnitTest")]
- public void CanSerializeInvalidTransactionsBackAndForth()
- {
- Transaction before = Network.CreateTransaction();
- var versionBefore = before.Version;
- before.Outputs.Add(new TxOut());
- Transaction after = AssertClone(before);
- Assert.Equal(before.Version, after.Version);
- Assert.Equal(versionBefore, after.Version);
- Assert.True(after.Outputs.Count == 1);
-
- before = Network.CreateTransaction();
- after = AssertClone(before);
- Assert.Equal(before.Version, versionBefore);
- }
-
private Transaction AssertClone(Transaction before)
{
Transaction after = before.Clone();
@@ -2100,6 +2087,7 @@ public void CanFilterUneconomicalCoins()
var bob = new Key();
//P2SH(P2WSH)
var previousTx = Network.CreateTransaction();
+ previousTx.Inputs.Add();
previousTx.Outputs.Add(new TxOut(Money.Coins(1.0m), alice.PubKey.ScriptPubKey.WitHash.ScriptPubKey.Hash));
var previousCoin = previousTx.Outputs.AsCoins().First();
@@ -2658,6 +2646,7 @@ public void CanBuildTransactionWithDustPrevention()
var bob = new Key();
var alice = new Key();
var tx = Network.CreateTransaction();
+ tx.Inputs.Add();
tx.Outputs.Add(Money.Coins(1.0m), bob);
var coins = tx.Outputs.AsCoins().ToArray();
@@ -2852,6 +2841,7 @@ public void CanMutateSignature()
public void CanUseLockTime()
{
var tx = Network.CreateTransaction();
+ tx.Inputs.Add();
tx.LockTime = new LockTime(4);
var clone = tx.Clone();
Assert.Equal(tx.LockTime, clone.LockTime);
@@ -3082,6 +3072,7 @@ public void witnessHasPushSizeLimit()
{
var bob = new Key().GetWif(Network.RegTest);
Transaction tx = Network.CreateTransaction();
+ tx.Inputs.Add();
tx.Outputs.Add(new TxOut(Money.Coins(1.0m), bob.PubKey.ScriptPubKey.WitHash));
ScriptCoin coin = new ScriptCoin(tx.Outputs.AsCoins().First(), bob.PubKey.ScriptPubKey);
diff --git a/NBitcoin/BitcoinStream.cs b/NBitcoin/BitcoinStream.cs
index 0c5f68743..8b80af49f 100644
--- a/NBitcoin/BitcoinStream.cs
+++ b/NBitcoin/BitcoinStream.cs
@@ -592,6 +592,7 @@ public void CopyParameters(BitcoinStream from)
IsBigEndian = from.IsBigEndian;
MaxArraySize = from.MaxArraySize;
Type = from.Type;
+ AllowNoInputs = from.AllowNoInputs;
}
public SerializationType Type
@@ -630,6 +631,13 @@ public System.Threading.CancellationToken ReadCancellationToken
set;
}
+ ///
+ /// Allows serialization of transactions with no inputs.
+ /// Such transactions are not valid for deserialization, but may still be useful,
+ /// for example, when computing a transaction hash or estimating size.
+ ///
+ public bool AllowNoInputs { get; set; }
+
public void ReadWriteAsVarInt(ref uint val)
{
if (Serializing)
diff --git a/NBitcoin/IBitcoinSerializable.cs b/NBitcoin/IBitcoinSerializable.cs
index 682910a2f..42b31e61b 100644
--- a/NBitcoin/IBitcoinSerializable.cs
+++ b/NBitcoin/IBitcoinSerializable.cs
@@ -32,6 +32,7 @@ public static void ReadWrite(this IBitcoinSerializable serializable, Stream stre
public static int GetSerializedSize(this IBitcoinSerializable serializable, uint? version, SerializationType serializationType)
{
BitcoinStream s = new BitcoinStream(Stream.Null, true);
+ s.AllowNoInputs = true;
s.Type = serializationType;
s.ProtocolVersion = version;
s.ReadWrite(serializable);
@@ -40,6 +41,7 @@ public static int GetSerializedSize(this IBitcoinSerializable serializable, uint
public static int GetSerializedSize(this IBitcoinSerializable serializable, TransactionOptions options)
{
var bms = new BitcoinStream(Stream.Null, true);
+ bms.AllowNoInputs = true;
bms.TransactionOptions = options;
serializable.ReadWrite(bms);
return (int)bms.Counter.WrittenBytes;
diff --git a/NBitcoin/RPC/RPCClient.Wallet.cs b/NBitcoin/RPC/RPCClient.Wallet.cs
index 838603949..272ce5e0e 100644
--- a/NBitcoin/RPC/RPCClient.Wallet.cs
+++ b/NBitcoin/RPC/RPCClient.Wallet.cs
@@ -380,8 +380,12 @@ private string ToHex(Transaction tx)
// if there is inputs, then it can't be confusing
if (tx.Inputs.Count > 0)
return tx.ToHex();
- // if there is, do this ACK so that NBitcoin does not change the version number
- return Encoders.Hex.EncodeData(tx.ToBytes(70012 - 1));
+
+ var ms = new MemoryStream();
+ BitcoinStream bs = new BitcoinStream(ms, true);
+ bs.AllowNoInputs = true;
+ tx.ReadWrite(bs);
+ return Encoders.Hex.EncodeData(ms.ToArrayEfficient());
}
diff --git a/NBitcoin/Transaction.cs b/NBitcoin/Transaction.cs
index 975f36b2d..01a5f50ac 100644
--- a/NBitcoin/Transaction.cs
+++ b/NBitcoin/Transaction.cs
@@ -1574,11 +1574,8 @@ public virtual void ReadWrite(BitcoinStream stream)
{
/* The witness flag is present, and we support witnesses. */
flags ^= 1;
- if (Inputs.Count != 0)
- {
- Witness wit = new Witness(Inputs);
- wit.ReadWrite(stream);
- }
+ Witness wit = new Witness(Inputs);
+ wit.ReadWrite(stream);
}
if (flags != 0)
{
@@ -1588,6 +1585,8 @@ public virtual void ReadWrite(BitcoinStream stream)
}
else
{
+ if (Inputs.Count == 0 && !stream.AllowNoInputs)
+ throw new InvalidOperationException("The transaction must have at least one input");
stream.ReadWrite(ref nVersion);
if (witSupported)
@@ -1637,6 +1636,7 @@ public uint256 GetHash()
{
TransactionOptions = TransactionOptions.None,
ConsensusFactory = GetConsensusFactory(),
+ AllowNoInputs = true
};
stream.SerializationTypeScope(SerializationType.Hash);
this.ReadWrite(stream);
diff --git a/NBitcoin/TransactionBuilder.cs b/NBitcoin/TransactionBuilder.cs
index c445bb6fa..38f26b5ee 100644
--- a/NBitcoin/TransactionBuilder.cs
+++ b/NBitcoin/TransactionBuilder.cs
@@ -2443,10 +2443,9 @@ public void EstimateSizes(Transaction tx, out int witSize, out int baseSize)
if (tx == null)
throw new ArgumentNullException(nameof(tx));
var clone = tx.Clone();
- clone.Inputs.Clear();
- baseSize = clone.GetSerializedSize() - 1;
- baseSize += new Protocol.VarInt((ulong)tx.Inputs.Count).GetSerializedSize();
-
+ clone.RemoveSignatures();
+ baseSize = clone.GetSerializedSize();
+ baseSize -= clone.Inputs.Count; // The varint to push scriptSig is accounted later
witSize = 0;
int nonWitnessCount = 0;
bool hasWitness = tx.HasWitness;
@@ -2460,10 +2459,8 @@ public void EstimateSizes(Transaction tx, out int witSize, out int baseSize)
else
nonWitnessCount++;
EstimateScriptSigSize(coin, ref witSize, ref baseSize);
- baseSize += (32 + 4) + 4;
}
-
if (hasWitness)
{
witSize += 2; // 1 Dummy + 1 Flag