/// <summary> /// Parses a Bitcoin transaction. /// </summary> /// <param name="blockMemoryStreamReader"> /// Provides access to a section of the Bitcoin blockchain file. /// </param> /// <returns> /// The Bitcoin transaction that was parsed. /// </returns> private static Transaction ParseTransaction(BlockMemoryStreamReader blockMemoryStreamReader) { Transaction transaction = new Transaction(); int positionInBaseStreamAtTransactionStart = (int)blockMemoryStreamReader.BaseStream.Position; transaction.TransactionVersion = blockMemoryStreamReader.ReadUInt32(); int inputsCount = (int)blockMemoryStreamReader.ReadVariableLengthInteger(); for (int inputIndex = 0; inputIndex < inputsCount; inputIndex++) { TransactionInput transactionInput = BlockchainParser.ParseTransactionInput(blockMemoryStreamReader); transaction.AddInput(transactionInput); } int outputsCount = (int)blockMemoryStreamReader.ReadVariableLengthInteger(); for (int outputIndex = 0; outputIndex < outputsCount; outputIndex++) { TransactionOutput transactionOutput = BlockchainParser.ParseTransactionOutput(blockMemoryStreamReader); transaction.AddOutput(transactionOutput); } // TODO: Need to find out more details about the semantic of TransactionLockTime. transaction.TransactionLockTime = blockMemoryStreamReader.ReadUInt32(); int positionInBaseStreamAfterTransactionEnd = (int)blockMemoryStreamReader.BaseStream.Position; using (SHA256Managed sha256 = new SHA256Managed()) { //// We need to calculate the double SHA256 hash of this transaction. //// We need to access the buffer that contains the transaction that we jut read through. //// 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[] hash1 = sha256.ComputeHash(baseBuffer, positionInBaseStreamAtTransactionStart, transactionBufferSize); transaction.TransactionHash = new ByteArray(sha256.ComputeHash(hash1).ReverseByteArray()); } return(transaction); }
/// <summary> /// Parses a Bitcoin transaction. /// </summary> /// <param name="blockMemoryStreamReader"> /// Provides access to a section of the Bitcoin blockchain file. /// </param> /// <returns> /// The Bitcoin transaction that was parsed. /// </returns> private static Transaction ParseTransaction(BlockMemoryStreamReader blockMemoryStreamReader) { Transaction transaction = new Transaction(); int positionInBaseStreamAtTransactionStart = (int)blockMemoryStreamReader.BaseStream.Position; transaction.TransactionVersion = blockMemoryStreamReader.ReadUInt32(); 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); transaction.AddInput(transactionInput); } int outputsCount = (int)blockMemoryStreamReader.ReadVariableLengthInteger(); for (int outputIndex = 0; outputIndex < outputsCount; outputIndex++) { TransactionOutput transactionOutput = BlockchainParser.ParseTransactionOutput(blockMemoryStreamReader); 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(); int positionInBaseStreamAfterTransactionEnd = (int)blockMemoryStreamReader.BaseStream.Position; using (SHA256Managed sha256 = new SHA256Managed()) { //// We need to calculate the double SHA256 hash of this transaction. //// We need to access the buffer that contains the transaction that we jut read through. //// 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()); } return(transaction); }