public static Transaction CreateCoinbaseTransaction(ECPublicKeyParameters publicKey, byte[] coinbase) { var tx = new Transaction ( version: 1, inputs: ImmutableArray.Create ( new TxInput ( previousTxOutputKey: new TxOutputKey ( txHash: 0, txOutputIndex: 0 ), scriptSignature: ImmutableArray.Create(coinbase), sequence: 0 ) ), outputs: ImmutableArray.Create ( new TxOutput ( value: 50L * (100 * 1000 * 1000), scriptPublicKey: ImmutableArray.Create(CreatePublicKeyScript(publicKey)) ) ), lockTime: 0 ); return tx; }
public void TestCalculateTransactionHash() { var expectedHash = UInt256.Parse("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", NumberStyles.HexNumber); var tx = new Transaction ( version: 1, inputs: ImmutableArray.Create( new TxInput ( previousTxOutputKey: new TxOutputKey(txHash: 0, txOutputIndex: 4294967295), scriptSignature: ImmutableArray.Create("04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73".HexToByteArray()), sequence: 4294967295 )), outputs: ImmutableArray.Create( new TxOutput ( value: (UInt64)(50L * 100.MILLION()), scriptPublicKey: ImmutableArray.Create("4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac".HexToByteArray()) )), lockTime: 0 ); Assert.AreEqual(expectedHash, DataCalculator.CalculateTransactionHash(tx)); Assert.AreEqual(expectedHash, DataCalculator.CalculateTransactionHash(tx.Version, tx.Inputs, tx.Outputs, tx.LockTime)); }
public bool TryReadTransaction(TxKey txKey, out Transaction transaction) { using (var conn = this.OpenConnection()) using (var cmd = conn.CreateCommand()) { cmd.CommandText = @" SELECT TxHash, TxBytes FROM BlockTransactions WHERE BlockHash = @blockHash AND TxIndex = @txIndex"; cmd.Parameters.SetValue("@blockHash", SqlDbType.Binary, 32).Value = txKey.BlockHash.ToDbByteArray(); cmd.Parameters.SetValue("@txIndex", SqlDbType.Int).Value = txKey.TxIndex.ToIntChecked(); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var txHash = reader.GetUInt256(0); var txBytes = reader.GetBytes(1); transaction = StorageEncoder.DecodeTransaction(txBytes.ToMemoryStream(), txHash); return true; } else { transaction = default(Transaction); return false; } } } }
public bool TryReadValue(UInt256 txHash, out Transaction transaction) { using (var conn = this.OpenConnection()) using (var cmd = conn.CreateCommand()) { cmd.CommandText = @" SELECT TxBytes FROM BlockTransactions WHERE TxHash = @txHash"; cmd.Parameters.SetValue("@txHash", FbDbType.Char, FbCharset.Octets, 32).Value = txHash.ToDbByteArray(); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var txBytes = reader.GetBytes(0); transaction = StorageEncoder.DecodeTransaction(txBytes.ToMemoryStream(), txHash); return true; } else { transaction = default(Transaction); return false; } } } }
public byte[] TxSignature(ImmutableArray<byte> scriptPubKey, Transaction tx, int inputIndex, byte hashType) { ///TODO Debug.Assert(inputIndex < tx.Inputs.Length); // Blank out other inputs' signatures var empty = ImmutableArray.Create<byte>(); var newInputs = new TxInput[tx.Inputs.Length]; for (var i = 0; i < tx.Inputs.Length; i++) { var oldInput = tx.Inputs[i]; var newInput = oldInput.With(scriptSignature: i == inputIndex ? scriptPubKey : empty); newInputs[i] = newInput; } //// Blank out some of the outputs //if ((hashType & 0x1F) == (int)ScriptHashType.SIGHASH_NONE) //{ // //TODO // Debug.Assert(false); // // Wildcard payee // // Let the others update at will //} //else if ((hashType & 0x1F) == (int)ScriptHashType.SIGHASH_SINGLE) //{ // //TODO // Debug.Assert(false); // // Only lock-in the txout payee at same index as txin // // Let the others update at will //} //// Blank out other inputs completely, not recommended for open transactions //if ((hashType & 0x80) == (int)ScriptHashType.SIGHASH_ANYONECANPAY) //{ // //TODO // Debug.Assert(false); //} // create simplified transaction var newTx = tx.With(Inputs: newInputs.ToImmutableArray()); // return wire-encoded simplified transaction with the 4-byte hashType tacked onto the end var stream = new MemoryStream(); using (var writer = new BinaryWriter(stream)) { writer.WriteBytes(DataCalculator.EncodeTransaction(newTx)); writer.Write4Bytes(hashType); return stream.ToArray(); } }
public static byte[] CreatePrivateKeyScript(Transaction tx, int inputIndex, byte hashType, ECPrivateKeyParameters privateKey, ECPublicKeyParameters publicKey) { //TODO var scriptEngine = new ScriptEngine(); var publicAddress = CreatePublicAddress(publicKey); var publicKeyScript = CreatePublicKeyScript(publicAddress); var txSignature = scriptEngine.TxSignature(publicKeyScript.ToImmutableArray(), tx, inputIndex, hashType); var txSignatureHash = Crypto.DoubleSHA256(txSignature); //Debug.WriteLine("Signing Tx: {0}".Format2(txSignature.ToHexDataString())); //Debug.WriteLine("Signing Tx Hash: {0}".Format2(txSignatureHash.ToHexDataString())); var signer = new ECDsaSigner(); signer.Init(forSigning: true, parameters: privateKey); var signature = signer.GenerateSignature(txSignatureHash); var r = signature[0]; var s = signature[1]; byte[] sigEncoded; using (var stream = new MemoryStream()) { using (var asn1Stream = new Asn1OutputStream(stream)) { asn1Stream.WriteObject(new DerSequence(new DerInteger(r), new DerInteger(s))); } sigEncoded = stream.ToArray().Concat(hashType); } //Debug.WriteLine("Sig R: {0}".Format2(r.ToHexNumberStringUnsigned())); //Debug.WriteLine("Sig S: {0}".Format2(s.ToHexNumberStringUnsigned())); //Debug.WriteLine("Sig Encoded: {0}".Format2(sigEncoded.ToHexDataString())); var privateKeyScript = new ScriptBuilder(); privateKeyScript.WritePushData(sigEncoded); privateKeyScript.WritePushData(publicAddress); //Debug.WriteLine("Private Script: {0}".Format2(privateKeyScript.GetScript().ToHexDataString())); return privateKeyScript.GetScript(); }
public Block CreateEmptyBlock(UInt256 previousBlockHash) { var coinbaseTx = new Transaction ( version: 0, inputs: ImmutableArray.Create ( new TxInput ( previousTxOutputKey: new TxOutputKey ( txHash: 0, txOutputIndex: 0 ), scriptSignature: ImmutableArray.Create(random.NextBytes(100)), sequence: 0 ) ), outputs: ImmutableArray.Create ( new TxOutput ( value: 50 * SATOSHI_PER_BTC, scriptPublicKey: ImmutableArray.Create(TransactionManager.CreatePublicKeyScript(_coinbasePublicKey)) ) ), lockTime: 0 ); //Debug.WriteLine("Coinbase Tx Created: {0}".Format2(coinbaseTx.Hash.ToHexNumberString())); var transactions = ImmutableArray.Create(coinbaseTx); var merkleRoot = DataCalculator.CalculateMerkleRoot(transactions); var block = new Block ( header: new BlockHeader ( version: 0, previousBlock: previousBlockHash, merkleRoot: merkleRoot, time: 0, bits: DataCalculator.TargetToBits(this._rules.HighestTarget), nonce: 0 ), transactions: transactions ); return block; }
public bool TryGetTransaction(TxKey txKey, out Transaction transaction, bool saveInCache = true) { if (this.CacheContext.TransactionCache.TryGetValue(txKey, out transaction)) { this.missingTransactions.TryRemove(txKey.TxHash); return true; } else { this.missingTransactions.TryAdd(txKey.TxHash); transaction = default(Transaction); return false; } }
//TODO utxo needs to be as-at transaction, with regards to a transaction being fully spent and added back in in the same block public virtual void ValidateTransaction(long blockHeight, Block block, Transaction tx, int txIndex, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions, out long unspentValue /*, ImmutableDictionary<UInt256, Transaction> transactions*/) { unspentValue = -1; // lookup all previous outputs var prevOutputMissing = false; var previousOutputs = new Dictionary<TxOutputKey, Tuple<TxInput, int, TxOutput>>(); for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; // find previous transaction var prevTx = GetPreviousTransaction(block, txIndex, input.PreviousTxOutputKey, utxo, newTransactions); // find previous transaction output if (input.PreviousTxOutputKey.TxOutputIndex >= prevTx.Outputs.Length) throw new ValidationException(); var prevOutput = prevTx.Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]; previousOutputs.Add(input.PreviousTxOutputKey, Tuple.Create(input, inputIndex, prevOutput)); } if (prevOutputMissing) { throw new ValidationException(); } // verify spend amounts var txInputValue = (UInt64)0; var txOutputValue = (UInt64)0; for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; // add transactions previous value to unspent amount (used to calculate allowed coinbase reward) var prevOutput = previousOutputs[input.PreviousTxOutputKey].Item3; txInputValue += prevOutput.Value; } for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++) { // remove transactions spend value from unspent amount (used to calculate allowed coinbase reward) var output = tx.Outputs[outputIndex]; txOutputValue += output.Value; } // ensure that amount being output from transaction isn't greater than amount being input if (txOutputValue > txInputValue) { throw new ValidationException("Failing tx {0}: Transaction output value is greater than input value".Format2(tx.Hash.ToHexNumberString())); } // calculate unspent value unspentValue = (long)(txInputValue - txOutputValue); // sanity check if (unspentValue < 0) { throw new ValidationException(); } // all validation has passed }
private void TestTransactionVerifyScript(Transaction tx, IDictionary<UInt256, Transaction> txLookup) { var scriptEngine = new ScriptEngine(); for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevOutput = txLookup[input.PreviousTxOutputKey.TxHash].Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]; var script = GetScriptFromInputPrevOutput(input, prevOutput); var result = scriptEngine.VerifyScript(0 /*blockIndex*/, -1 /*txIndex*/, prevOutput.ScriptPublicKey.ToArray(), tx, inputIndex, script.ToArray()); Assert.IsTrue(result); } }
private void TestTransactionVerifySignature(byte[] expectedHashTypes, byte[][] expectedSignatures, byte[][] expectedSignatureHashes, byte[][] expectedX, byte[][] expectedY, byte[][] expectedR, byte[][] expectedS, Transaction tx, IDictionary<UInt256, Transaction> txLookup) { var scriptEngine = new ScriptEngine(); for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevOutput = txLookup[input.PreviousTxOutputKey.TxHash].Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]; var hashType = GetHashTypeFromScriptSig(input.ScriptSignature); var sig = GetSigFromScriptSig(input.ScriptSignature); var pubKey = GetPubKeyFromScripts(input.ScriptSignature, prevOutput.ScriptPublicKey); byte[] txSignature, txSignatureHash; BigInteger x, y, r, s; var result = scriptEngine.VerifySignature(prevOutput.ScriptPublicKey, tx, sig.ToArray(), pubKey.ToArray(), inputIndex, out hashType, out txSignature, out txSignatureHash, out x, out y, out r, out s); Debug.WriteLine(hashType); Debug.WriteLine(txSignature.ToHexDataString()); Debug.WriteLine(txSignatureHash.ToHexNumberString()); Debug.WriteLine(x.ToHexNumberString()); Debug.WriteLine(y.ToHexNumberString()); Debug.WriteLine(r.ToHexNumberString()); Debug.WriteLine(s.ToHexNumberString()); Assert.AreEqual(expectedHashTypes[inputIndex], hashType); CollectionAssert.AreEqual(expectedSignatures[inputIndex].ToList(), txSignature.ToList()); CollectionAssert.AreEqual(expectedSignatureHashes[inputIndex].ToList(), txSignatureHash.ToList()); CollectionAssert.AreEqual(expectedX[inputIndex], x.ToByteArrayUnsigned()); CollectionAssert.AreEqual(expectedY[inputIndex], y.ToByteArrayUnsigned()); CollectionAssert.AreEqual(expectedR[inputIndex], r.ToByteArrayUnsigned()); CollectionAssert.AreEqual(expectedS[inputIndex], s.ToByteArrayUnsigned()); Assert.IsTrue(result); } }
public bool TryReadTransaction(TxKey txKey, out Transaction transaction) { throw new NotImplementedException(); }
private void TestTransactionSignature(byte[][] expectedSignatures, Transaction tx, IDictionary<UInt256, Transaction> txLookup) { var scriptEngine = new ScriptEngine(); for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevOutput = txLookup[input.PreviousTxOutputKey.TxHash].Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()]; var hashType = GetHashTypeFromScriptSig(input.ScriptSignature); var actual = scriptEngine.TxSignature(prevOutput.ScriptPublicKey, tx, inputIndex, hashType); CollectionAssert.AreEqual(expectedSignatures[inputIndex].ToList(), actual.ToList()); } }
public static void GetFirstTransaction(BlockProvider blockProvider, out Block block, out Transaction tx, out IDictionary<UInt256, Transaction> txLookup) { txLookup = new Dictionary<UInt256, Transaction>(); // prior outputs for first transaction GetTransaction(blockProvider, 9, 0, out block, out tx); txLookup.Add(tx.Hash, tx); // first transaction // do this last so its output is what is returned GetTransaction(blockProvider, 170, 1, out block, out tx); txLookup.Add(tx.Hash, tx); }
public static void GetTransaction(BlockProvider blockProvider, int blockIndex, int txIndex, out Block block, out Transaction tx) { block = blockProvider.GetBlock(blockIndex); tx = block.Transactions[txIndex]; }
private bool ExecuteOps(ImmutableArray<byte> scriptPubKey, Transaction tx, int inputIndex, byte[] script, out Stack stack, out Stack altStack) { stack = new Stack(); altStack = new Stack(); using (var opReader = new BinaryReader(script.ToMemoryStream())) { while (opReader.BaseStream.Position < script.Length) { var opByte = opReader.ReadByte(); var op = (ScriptOp)Enum.ToObject(typeof(ScriptOp), opByte); //logger.LogTrace("Executing {0} with stack count: {1}", OpName(opByte), stack.Count); switch (op) { // Constants case ScriptOp.OP_PUSHDATA1: { if (opReader.BaseStream.Position + 1 >= script.Length) return false; var length = opReader.ReadByte(); stack.PushBytes(opReader.ReadBytes(length)); } break; case ScriptOp.OP_PUSHDATA2: { if (opReader.BaseStream.Position + 2 >= script.Length) return false; var length = opReader.Read2Bytes(); stack.PushBytes(opReader.ReadBytes(length)); } break; case ScriptOp.OP_PUSHDATA4: { if (opReader.BaseStream.Position + 4 >= script.Length) return false; var length = opReader.Read4Bytes(); stack.PushBytes(opReader.ReadBytes(length.ToIntChecked())); } break; // Flow control case ScriptOp.OP_NOP: { } break; // Stack case ScriptOp.OP_DROP: { if (stack.Count < 1) return false; var value = stack.PopBytes(); //logger.LogTrace("{0} dropped {1}", OpName(opByte), value); } break; case ScriptOp.OP_DUP: { if (stack.Count < 1) return false; var value = stack.PeekBytes(); stack.PushBytes(value); //logger.LogTrace("{0} duplicated {2}", OpName(opByte), value); } break; // Splice // Bitwise logic case ScriptOp.OP_EQUAL: case ScriptOp.OP_EQUALVERIFY: { if (stack.Count < 2) return false; var value1 = stack.PopBytes(); var value2 = stack.PopBytes(); var result = value1.SequenceEqual(value2); stack.PushBool(result); // logger.LogTrace( //@"{0} compared values: //value1: {1} //value2: {2} //result: {3}", OpName(opByte), value1, value2, result); if (op == ScriptOp.OP_EQUALVERIFY) { if (result) stack.PopBool(); else return false; } } break; // Arithmetic // Note: Arithmetic inputs are limited to signed 32-bit integers, but may overflow their output. // Crypto case ScriptOp.OP_SHA256: { if (stack.Count < 1) return false; var value = stack.PopBytes().ToArray(); var hash = Crypto.SingleSHA256(value); stack.PushBytes(hash); // logger.LogTrace( //@"{0} hashed value: //value: {1} //hash: {2}", OpName(opByte), value, hash); } break; case ScriptOp.OP_HASH160: { if (stack.Count < 1) return false; var value = stack.PopBytes().ToArray(); var hash = Crypto.SingleRIPEMD160(Crypto.SingleSHA256(value)); stack.PushBytes(hash); // logger.LogTrace( //@"{0} hashed value: //value: {1} //hash: {2}", OpName(opByte), value, hash); } break; case ScriptOp.OP_CHECKSIG: case ScriptOp.OP_CHECKSIGVERIFY: { if (stack.Count < 2) return false; var pubKey = stack.PopBytes().ToArray(); var sig = stack.PopBytes().ToArray(); var startTime = DateTime.UtcNow; byte hashType; byte[] txSignature, txSignatureHash; BigIntegerBouncy x, y, r, s; var result = VerifySignature(scriptPubKey, tx, sig, pubKey, inputIndex, out hashType, out txSignature, out txSignatureHash, out x, out y, out r, out s); stack.PushBool(result); var finishTime = DateTime.UtcNow; // logger.LogTrace( //@"{0} executed in {13} ms: //tx: {1} //inputIndex: {2} //pubKey: {3} //sig: {4} //hashType: {5} //txSignature: {6} //txSignatureHash: {7} //x: {8} //y: {9} //r: {10} //s: {11} //result: {12}", OpName(opByte), tx.ToRawBytes(), inputIndex, pubKey, sig, hashType, txSignature, txSignatureHash, x, y, r, s, result, (finishTime - startTime).TotalMilliseconds.ToString("0")); if (op == ScriptOp.OP_CHECKSIGVERIFY) { if (result) stack.PopBool(); else return false; } } break; // Pseudo-words // These words are used internally for assisting with transaction matching. They are invalid if used in actual scripts. // Reserved words // Any opcode not assigned is also reserved. Using an unassigned opcode makes the transaction invalid. default: //OP_PUSHBYTES1-75 if (opByte >= (int)ScriptOp.OP_PUSHBYTES1 && opByte <= (int)ScriptOp.OP_PUSHBYTES75) { stack.PushBytes(opReader.ReadBytes(opByte)); //logger.LogTrace("{0} loaded {1} bytes onto the stack: {2}", OpName(opByte), opByte, stack.PeekBytes()); } // Unknown op else { var message = string.Format("Invalid operation in tx {0} input {1}: {2} {3}", tx.Hash.ToHexNumberString(), inputIndex, new[] { opByte }.ToHexNumberString(), OpName(opByte)); Debug.WriteLine(message); throw new Exception(message); } break; } //logger.LogTrace(new string('-', 80)); } } // TODO verify no if/else blocks left over // TODO not entirely sure what default return should be return true; }
public bool VerifySignature(ImmutableArray<byte> scriptPubKey, Transaction tx, byte[] sig, byte[] pubKey, int inputIndex, out byte hashType, out byte[] txSignature, out byte[] txSignatureHash, out BigIntegerBouncy x, out BigIntegerBouncy y, out BigIntegerBouncy r, out BigIntegerBouncy s) { // get the 1-byte hashType off the end of sig hashType = sig[sig.Length - 1]; // get the DER encoded portion of sig, which is everything except the last byte (the last byte being hashType) var sigDER = sig.Take(sig.Length - 1).ToArray(); // get the simplified/signing version of the transaction txSignature = TxSignature(scriptPubKey, tx, inputIndex, hashType); // get the hash of the simplified/signing version of the transaction txSignatureHash = Crypto.DoubleSHA256(txSignature); // load pubKey ReadPubKey(pubKey, out x, out y); var publicKeyPoint = curve.Curve.CreatePoint(x, y, withCompression: false); var publicKeyParameters = new ECPublicKeyParameters(publicKeyPoint, domainParameters); // load sig ReadSigKey(sigDER, out r, out s); // init signer var signer = new ECDsaSigner(); signer.Init(forSigning: false, parameters: publicKeyParameters); // verify that sig is a valid signature from pubKey for the simplified/signing transaction's hash var txSignatureHash2 = txSignatureHash; var r2 = r; var s2 = s; //TODO var result = BypassVerifySignature || new MethodTimer(false).Time("ECDsa Verify", () => signer.VerifySignature(txSignatureHash2.ToArray(), r2, s2)); return result; }
public static void EncodeTransaction(Stream stream, Transaction tx) { using (var writer = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true)) { writer.Write4Bytes(tx.Version); writer.EncodeList(tx.Inputs, input => EncodeTxInput(stream, input)); writer.EncodeList(tx.Outputs, output => EncodeTxOutput(stream, output)); writer.Write4Bytes(tx.LockTime); } }
public static Transaction CreateSpendTransaction(Transaction prevTx, int prevInputIndex, byte hashType, UInt64 value, ECPrivateKeyParameters fromPrivateKey, ECPublicKeyParameters fromPublicKey, ECPublicKeyParameters toPublicKey) { var tx = new Transaction ( version: 1, inputs: ImmutableArray.Create ( new TxInput ( previousTxOutputKey: new TxOutputKey ( txHash: prevTx.Hash, txOutputIndex: (UInt32)prevInputIndex ), scriptSignature: ImmutableArray.Create<byte>(), sequence: 0 ) ), outputs: ImmutableArray.Create ( new TxOutput ( value: value, scriptPublicKey: ImmutableArray.Create(CreatePublicKeyScript(toPublicKey)) ) ), lockTime: 0 ); // sign the transaction var scriptSignature = ImmutableArray.Create(CreatePrivateKeyScript(tx, 0, hashType, fromPrivateKey, fromPublicKey)); // add the signature script to the transaction tx = tx.With(Inputs: ImmutableArray.Create(tx.Inputs[0].With(scriptSignature: scriptSignature))); return tx; }
public static byte[] EncodeTransaction(Transaction tx) { var stream = new MemoryStream(); EncodeTransaction(stream, tx); return stream.ToArray(); }
public static UInt256 CalculateTransactionHash(Transaction tx) { return new UInt256(Crypto.DoubleSHA256(EncodeTransaction(tx))); }
public static byte[] EncodeTransaction(Transaction tx) { return EncodeTransaction(tx.Version, tx.Inputs, tx.Outputs, tx.LockTime); }
public static long SizeEstimator(Transaction tx) { return tx.SizeEstimate; }
public bool VerifyScript(UInt256 blockHash, int txIndex, byte[] scriptPubKey, Transaction tx, int inputIndex, byte[] script) { // logger.LogTrace( //@" //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //Verifying script for block {0}, transaction {1}, input {2} //{3} //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" // , blockHash, txIndex, inputIndex, script.ToArray().ToHexDataString()); Stack stack, altStack; if ( ExecuteOps(scriptPubKey.ToImmutableArray(), tx, inputIndex, script, out stack, out altStack) && stack.Count == 1 && altStack.Count == 0) { var success = stack.PeekBool(); //TODO Pop? does it matter? // Additional validation for spend-to-script-hash transactions: //TODO return success; } else { return false; } }