Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/BitcoinBlockchain/BitcoinBlockchain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Data\Witness.cs" />
<Compile Include="Parser\ByteArrayExtension.cs" />
<Compile Include="Parser\BinaryReaderExtension.cs" />
<Compile Include="Data\BlockchainFile.cs" />
Expand All @@ -75,7 +76,6 @@
<CodeAnalysisDictionary Include="CustomDictionary.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(ProgramFiles)\MSBuild\StyleCop\v4.7\StyleCop.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
Expand Down
29 changes: 29 additions & 0 deletions Sources/BitcoinBlockchain/Data/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,23 @@ public class Transaction
/// </summary>
private readonly List<TransactionOutput> transactionOutputs;

/// <summary>
/// Witness data, one for each txin
/// </summary>
private readonly List<Witness> transactionWitness;

/// <summary>
/// Initializes a new instance of the <see cref="Transaction" /> class.
/// </summary>
public Transaction()
{
this.transactionInputs = new List<TransactionInput>();
this.transactionOutputs = new List<TransactionOutput>();
this.transactionWitness = new List<Witness>();

this.Inputs = new ReadOnlyCollection<TransactionInput>(this.transactionInputs);
this.Outputs = new ReadOnlyCollection<TransactionOutput>(this.transactionOutputs);
this.Witness = new ReadOnlyCollection<Witness>(this.transactionWitness);
}

/// <summary>
Expand Down Expand Up @@ -70,6 +77,16 @@ public Transaction()
/// </summary>
public ReadOnlyCollection<TransactionOutput> Outputs { get; private set; }

/// <summary>
/// Gets the read-only collection witness data
/// </summary>
public ReadOnlyCollection<Witness> Witness { get; private set; }

/// <summary>
/// Gets if the transaction has any any witness data (SegWit)
/// </summary>
public bool HasWitnessData { get { return transactionWitness.Count != 0; } }

/// <summary>
/// Adds a new input to the list of transaction inputs.
/// </summary>
Expand All @@ -81,6 +98,18 @@ public void AddInput(TransactionInput transactionInput)
this.transactionInputs.Add(transactionInput);
}

/// <summary>
/// Adds witness data to the list of transaction witnesses
/// There should be one Witness object for each txin
/// </summary>
/// <param name="witness">
/// The witness that should be added to the list of witnesses.
/// </param>
public void AddWitness(Witness witness)
{
this.transactionWitness.Add(witness);
}

/// <summary>
/// Adds a new output to the list of transaction outputs.
/// </summary>
Expand Down
23 changes: 23 additions & 0 deletions Sources/BitcoinBlockchain/Data/Witness.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//-----------------------------------------------------------------------
// <copyright file="Witness.cs">
// Copyright © Jeffrey Quesnelle. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace BitcoinBlockchain.Data
{
using System.Collections.Generic;

/// <summary>
/// Contains witness data for transaction inputs. Currently the witness data itself is unparsed
/// For more information see:
/// <c>https://bitcoincore.org/en/segwit_wallet_dev/</c>
/// </summary>
public class Witness
{
/// <summary>
/// Witness data stack
/// </summary>
public List<ByteArray> WitnessStack { get; set; }
}
}
98 changes: 79 additions & 19 deletions Sources/BitcoinBlockchain/Parser/BlockchainParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,23 +150,16 @@ private static BlockHeader ParseBlockHeader(BlockMemoryStreamReader blockMemoryS

blockHeader.BlockVersion = blockMemoryStreamReader.ReadUInt32();

//// TODO: We need to understand better what is different in V2 and V3.

if (blockHeader.BlockVersion != 1 &&
blockHeader.BlockVersion != 2 &&
blockHeader.BlockVersion != 3 &&
blockHeader.BlockVersion != 0x20000007 &&
blockHeader.BlockVersion != 0x30000000 &&
blockHeader.BlockVersion != 4 &&
blockHeader.BlockVersion != 0x20000000 &&
blockHeader.BlockVersion != 0x20000001 &&
blockHeader.BlockVersion != 0x30000001 &&
blockHeader.BlockVersion != 0x08000004 &&
blockHeader.BlockVersion != 0x20000002 &&
blockHeader.BlockVersion != 0x30000007 &&
blockHeader.BlockVersion != 0x20000004)
switch (blockHeader.BlockVersion)
{
throw new UnknownBlockVersionException(string.Format(CultureInfo.InvariantCulture, "Unknown block version: {0} ({0:X}).", blockHeader.BlockVersion));
case 1: case 2: case 3: case 4: // original block version and BIP 34/65/66
break;
default:
if ((blockHeader.BlockVersion & 0xE0000000) != 0x20000000 && // BIP 9 signaling
(blockHeader.BlockVersion & 0x08000000) != 0x08000000 // BitPay adaptive block size HF signaling
)
throw new UnknownBlockVersionException(string.Format(CultureInfo.InvariantCulture, "Unknown block version: {0} ({0:X}).", blockHeader.BlockVersion));
break;
}

blockHeader.PreviousBlockHash = new ByteArray(blockMemoryStreamReader.ReadBytes(32).ReverseByteArray());
Expand Down Expand Up @@ -250,6 +243,22 @@ private static TransactionOutput ParseTransactionOutput(BlockMemoryStreamReader
return transactionOutput;
}

private static Witness ParseWitness(BlockMemoryStreamReader blockMemoryStreamReader)
{
Witness witness = new Witness();

int witnessStackCount = (int)blockMemoryStreamReader.ReadVariableLengthInteger();
witness.WitnessStack = new List<ByteArray>();

for (int witnessStackIndex = 0; witnessStackIndex < witnessStackCount; witnessStackIndex++)
{
int witnessSize = (int)blockMemoryStreamReader.ReadVariableLengthInteger();
witness.WitnessStack.Add(new ByteArray(blockMemoryStreamReader.ReadBytes(witnessSize)));
}

return witness;
}

/// <summary>
/// Parses a Bitcoin transaction.
/// </summary>
Expand All @@ -269,6 +278,19 @@ private static Transaction ParseTransaction(BlockMemoryStreamReader blockMemoryS

int inputsCount = (int)blockMemoryStreamReader.ReadVariableLengthInteger();

bool isSegWit = false;
if (inputsCount == 0)
{
byte flag = blockMemoryStreamReader.ReadByte();
if (flag != 0x01)
{
throw new InvalidBlockchainContentException(string.Format(CultureInfo.InvariantCulture,
"Unknown transaction serialization. No input transactions, but SegWit flag was {0} instead of 1.", flag));
}
inputsCount = (int)blockMemoryStreamReader.ReadVariableLengthInteger();
isSegWit = true;
}

for (int inputIndex = 0; inputIndex < inputsCount; inputIndex++)
{
TransactionInput transactionInput = BlockchainParser.ParseTransactionInput(blockMemoryStreamReader);
Expand All @@ -283,6 +305,17 @@ private static Transaction ParseTransaction(BlockMemoryStreamReader blockMemoryS
transaction.AddOutput(transactionOutput);
}

int positionInBaseStreamAfterTxOuts = (int)blockMemoryStreamReader.BaseStream.Position;

if (isSegWit)
{
for (int inputIndex = 0; inputIndex < inputsCount; inputIndex++)
{
Witness witness = BlockchainParser.ParseWitness(blockMemoryStreamReader);
transaction.AddWitness(witness);
}
}

// TODO: Need to find out more details about the semantic of TransactionLockTime.
transaction.TransactionLockTime = blockMemoryStreamReader.ReadUInt32();

Expand All @@ -295,10 +328,37 @@ private static Transaction ParseTransaction(BlockMemoryStreamReader blockMemoryS
//// Here we take advantage of the fact that the entire block was loaded as an in-memory buffer.
//// The base stream of blockMemoryStreamReader is that in-memory buffer.

byte[] baseBuffer = blockMemoryStreamReader.GetBuffer();
int transactionBufferSize = positionInBaseStreamAfterTransactionEnd - positionInBaseStreamAtTransactionStart;
byte[] baseBuffer = blockMemoryStreamReader.GetBuffer(), hash1 = null;

if (isSegWit)
{
using (SHA256Managed innerSHA256 = new SHA256Managed())
{
//// SegWit transactions are still identified by their txid, which is double SHA256 of the old
//// serialization format (i.e. no marker, flag, or witness). So, we need to calculate the txid by
//// recreating the old format as the input to the hash algorithm.

// First, the version number
innerSHA256.TransformBlock(baseBuffer, positionInBaseStreamAtTransactionStart, 4, baseBuffer, positionInBaseStreamAtTransactionStart);

// Skip the marker and flag (each one byte), then read in txins and txouts (starting with txin count)
int txStart = positionInBaseStreamAtTransactionStart + 6;
int txSize = positionInBaseStreamAfterTxOuts - txStart;
innerSHA256.TransformBlock(baseBuffer, txStart, txSize, baseBuffer, txStart);

///// After the transactions comes the segregated witness data, which is not included in the txid.
///// The only thing left to add to calcualte the txid is nLockTime located in the last 4 bytes
int lockTimeStart = positionInBaseStreamAfterTransactionEnd - 4;
innerSHA256.TransformFinalBlock(baseBuffer, lockTimeStart, 4);
hash1 = innerSHA256.Hash;
}
}
else
{
int transactionBufferSize = positionInBaseStreamAfterTransactionEnd - positionInBaseStreamAtTransactionStart;
hash1 = sha256.ComputeHash(baseBuffer, positionInBaseStreamAtTransactionStart, transactionBufferSize);
}

byte[] hash1 = sha256.ComputeHash(baseBuffer, positionInBaseStreamAtTransactionStart, transactionBufferSize);
transaction.TransactionHash = new ByteArray(sha256.ComputeHash(hash1).ReverseByteArray());
}

Expand Down