/// <inheritdoc/> public bool Verify(ITransaction tx, out string error) { // If a tx is already in memory pool it must have been verified and be valid. // The SigOpCount property must be set by the caller (mempool dependency). if (!isMempool && mempool.Contains(tx)) { // TODO: get the tx object from mempool the passed tx (from block) doesn't have any properties set TotalSigOpCount += tx.SigOpCount; TotalFee += utxoDb.MarkSpentAndGetFee(tx.TxInList); if (!AnySegWit) { AnySegWit = tx.WitnessList != null; } error = null; return(true); } // TODO: these 2 checks should be performed during creation of tx (ctor or Deserialize) if (tx.TxInList.Length == 0 || tx.TxOutList.Length == 0) { error = "Invalid number of inputs or outputs."; return(false); } ulong toSpend = 0; for (int i = 0; i < tx.TxInList.Length; i++) { TxIn currentInput = tx.TxInList[i]; // TODO: add a condition in UTXO for when it is a coinbase transaction (they are not spendable if haven't // reached maturity ie. 100 blocks -> thisHeight - spendingCoinbaseHeight >= 100) IUtxo prevOutput = utxoDb.Find(currentInput); if (prevOutput is null) { // TODO: add a ToString() method to TxIn? error = $"Input {currentInput.TxHash.ToBase16()}:{currentInput.Index} was not found."; return(false); } toSpend += prevOutput.Amount; PubkeyScriptSpecialType pubType = prevOutput.PubScript.GetSpecialType(consensus, BlockHeight); if (pubType == PubkeyScriptSpecialType.None || pubType == PubkeyScriptSpecialType.P2PKH) { // If the type is not witness there shouldn't be any witness item if (tx.WitnessList != null && tx.WitnessList.Length != 0 && tx.WitnessList[i].Items.Length != 0) { error = $"Unexpected witness." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}"; return(false); } if (!currentInput.SigScript.TryEvaluate(out IOperation[] signatureOps, out int signatureOpCount, out error)) { error = $"Invalid transaction signature script." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } // P2PKH is not a special type so the signature can contain extra OPs (eg. PushData() OP_DROP <sig> <pub>). // The optimization only works when the signature script is standard (Push<sig> Push<pub>). // The optimization doesn't need to use OpData, evaluate PubkeyScript, convert PubkeyScript (FindAndDelete), // run the operations, count Ops, count sigOps, check for stack item overflow, // or check stack after execution. if (pubType == PubkeyScriptSpecialType.P2PKH && signatureOps.Length == 2 && signatureOps[0] is PushDataOp sigPush && sigPush.data != null && signatureOps[1] is PushDataOp pubPush && pubPush.data != null) { if (!VerifyP2pkh(tx, i, sigPush, pubPush, prevOutput.PubScript.Data, out error)) { return(false); } } else { TotalSigOpCount += currentInput.SigScript.CountSigOps() * Constants.WitnessScaleFactor; if (!prevOutput.PubScript.TryEvaluate(out IOperation[] pubOps, out int pubOpCount, out error)) { error = $"Invalid input transaction pubkey script." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } var stack = new OpData() { Tx = tx, TxInIndex = i, ForceLowS = ForceLowS, StrictNumberEncoding = StrictNumberEncoding, IsBip65Enabled = consensus.IsBip65Enabled(BlockHeight), IsBip112Enabled = consensus.IsBip112Enabled(BlockHeight), IsStrictDerSig = consensus.IsStrictDerSig(BlockHeight), IsBip147Enabled = consensus.IsBip147Enabled(BlockHeight), }; // Note that checking OP count is below max is done during Evaluate() and op.Run() stack.ExecutingScript = signatureOps; stack.OpCount = signatureOpCount; foreach (var op in signatureOps) { if (!op.Run(stack, out error)) { error = $"Script evaluation failed." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } } stack.ExecutingScript = pubOps; stack.OpCount = pubOpCount; foreach (var op in pubOps) { if (!op.Run(stack, out error)) { error = $"Script evaluation failed." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } } if (!CheckStack(stack, out error)) { return(false); } } }
/// <inheritdoc/> public bool Verify(ITransaction tx, out string error) { // If a tx is already in memory pool it must have been verified and be valid. // The SigOpCount property must be set by the caller (mempool dependency). if (mempool.Contains(tx)) { TotalSigOpCount += tx.SigOpCount; TotalFee += utxoDb.MarkSpentAndGetFee(tx.TxInList); if (!AnySegWit) { AnySegWit = tx.WitnessList != null; } error = null; return(true); } if (tx.TxInList.Length == 0 || tx.TxOutList.Length == 0) { error = "Invalid number of inputs or outputs."; return(false); } ulong toSpend = 0; for (int i = 0; i < tx.TxInList.Length; i++) { TxIn currentInput = tx.TxInList[i]; IUtxo prevOutput = utxoDb.Find(currentInput); if (prevOutput is null) { // TODO: add a ToString() method to TxIn? error = $"Input {currentInput.TxHash.ToBase16()}:{currentInput.Index} was not found."; return(false); } toSpend += prevOutput.Amount; if (!prevOutput.PubScript.TryEvaluate(out IOperation[] pubOps, out int pubOpCount, out error)) { error = $"Invalid input transaction pubkey script." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } if (!currentInput.SigScript.TryEvaluate(out IOperation[] signatureOps, out int signatureOpCount, out error)) { error = $"Invalid transaction signature script." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } OpData stack = new OpData() { Tx = tx, TxInIndex = i, ForceLowS = ForceLowS, StrictNumberEncoding = StrictNumberEncoding, IsBip65Enabled = consensus.IsBip65Enabled(BlockHeight), IsBip112Enabled = consensus.IsBip112Enabled(BlockHeight), IsStrictDerSig = consensus.IsStrictDerSig(BlockHeight), IsBip147Enabled = consensus.IsBip147Enabled(BlockHeight), }; PubkeyScriptSpecialType pubType = prevOutput.PubScript.GetSpecialType(consensus, BlockHeight); // TODO: optimize for specific pubScrTypes if (pubType == PubkeyScriptSpecialType.None || pubType == PubkeyScriptSpecialType.P2PKH) { TotalSigOpCount += (prevOutput.PubScript.CountSigOps() + currentInput.SigScript.CountSigOps()) * Constants.WitnessScaleFactor; if ((tx.WitnessList != null && tx.WitnessList.Length != 0) && (tx.WitnessList[i].Items.Length != 0)) { error = $"Unexpected witness." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } // Note that checking OP count is below max is done during Evaluate() and op.Run() stack.ExecutingScript = signatureOps; stack.OpCount = signatureOpCount; foreach (var op in signatureOps) { if (!op.Run(stack, out error)) { error = $"Script evaluation failed." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } } stack.ExecutingScript = pubOps; stack.OpCount = pubOpCount; foreach (var op in pubOps) { if (!op.Run(stack, out error)) { error = $"Script evaluation failed." + $"{Environment.NewLine}TxId: {tx.GetTransactionId()}" + $"{Environment.NewLine}More info: {error}"; return(false); } } } else if (pubType == PubkeyScriptSpecialType.P2SH) { if (signatureOps.Length == 0 || !(signatureOps[^ 1] is PushDataOp rdmPush))