Esempio n. 1
0
        /// <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))