/// <inheritdoc/> public bool VerifySignature(UnspentOutputs coin, Transaction txTo, int txToInN, ScriptVerify flagScriptVerify) { Guard.NotNull(coin, nameof(coin)); Guard.NotNull(txTo, nameof(txTo)); if (txToInN < 0 || txToInN >= txTo.Inputs.Count) { return(false); } TxIn input = txTo.Inputs[txToInN]; if (input.PrevOut.N >= coin.Outputs.Length) { this.logger.LogTrace("(-)[OUTPUT_INCORRECT_LENGTH]"); return(false); } if (input.PrevOut.Hash != coin.TransactionId) { this.logger.LogTrace("(-)[INCORRECT_TX]"); return(false); } TxOut output = coin.Outputs[input.PrevOut.N]; if (output == null) { this.logger.LogTrace("(-)[OUTPUT_NOT_FOUND]"); return(false); } var txData = new PrecomputedTransactionData(txTo); var checker = new TransactionChecker(txTo, txToInN, output.Value, txData); var ctx = new ScriptEvaluationContext(this.chain.Network) { ScriptVerify = flagScriptVerify }; bool res = ctx.VerifyScript(input.ScriptSig, output.ScriptPubKey, checker); return(res); }
/// <inheritdoc/> public void CheckProofOfStake(PosRuleContext context, ChainedHeader prevChainedHeader, BlockStake prevBlockStake, Transaction transaction, uint headerBits) { Guard.NotNull(context, nameof(context)); Guard.NotNull(prevChainedHeader, nameof(prevChainedHeader)); Guard.NotNull(prevBlockStake, nameof(prevBlockStake)); Guard.NotNull(transaction, nameof(transaction)); if (!transaction.IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.NonCoinstake.Throw(); } TxIn txIn = transaction.Inputs[0]; UnspentOutputs prevUtxo = context.UnspentOutputSet.AccessCoins(txIn.PrevOut.Hash); if (prevUtxo == null) { this.logger.LogTrace("(-)[PREV_UTXO_IS_NULL]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } // Verify signature. if (!this.VerifySignature(prevUtxo, transaction, 0, ScriptVerify.None)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.CoinstakeVerifySignatureFailed.Throw(); } // Min age requirement. if (this.IsConfirmedInNPrevBlocks(prevUtxo, prevChainedHeader, this.GetTargetDepthRequired(prevChainedHeader))) { this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]"); ConsensusErrors.InvalidStakeDepth.Throw(); } if (!this.CheckStakeKernelHash(context, headerBits, prevBlockStake.StakeModifierV2, prevUtxo, txIn.PrevOut, transaction.Time)) { this.logger.LogTrace("(-)[INVALID_STAKE_HASH_TARGET]"); ConsensusErrors.StakeHashInvalidTarget.Throw(); } }
public void CheckProofOfStake(ContextInformation context, ChainedBlock pindexPrev, BlockStake prevBlockStake, Transaction tx, uint headerBits) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:'{4}',{5}:0x{6:X})", nameof(pindexPrev), pindexPrev.HashBlock, nameof(prevBlockStake), nameof(prevBlockStake.HashProof), prevBlockStake.HashProof, nameof(headerBits), headerBits); if (!tx.IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.NonCoinstake.Throw(); } // Kernel (input 0) must match the stake hash target per coin age (nBits). TxIn txIn = tx.Inputs[0]; // First try finding the previous transaction in database. FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { ConsensusErrors.ReadTxPrevFailed.Throw(); } ChainedBlock prevBlock = this.chain.GetBlock(coins.BlockHash); UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; // Verify signature. if (!this.VerifySignature(prevUtxo, tx, 0, ScriptVerify.None)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.CoinstakeVerifySignatureFailed.Throw(); } // Min age requirement. if (this.IsConfirmedInNPrevBlocks(prevUtxo, pindexPrev, this.consensusOptions.StakeMinConfirmations - 1)) { this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]"); ConsensusErrors.InvalidStakeDepth.Throw(); } this.CheckStakeKernelHash(context, pindexPrev, headerBits, prevBlock.Header.Time, prevBlockStake, prevUtxo, txIn.PrevOut, tx.Time); this.logger.LogTrace("(-)[OK]"); }
/// <inheritdoc/> public void CheckProofOfStake(PosRuleContext context, ChainedHeader prevChainedHeader, BlockStake prevBlockStake, Transaction transaction, uint headerBits) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:'{4}',{5}:0x{6:X})", nameof(prevChainedHeader), prevChainedHeader.HashBlock, nameof(prevBlockStake), nameof(prevBlockStake.HashProof), prevBlockStake.HashProof, nameof(headerBits), headerBits); if (!transaction.IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.NonCoinstake.Throw(); } TxIn txIn = transaction.Inputs[0]; // First try finding the previous transaction in database. FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) ConsensusErrors.ReadTxPrevFailed.Throw(); UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; if (prevUtxo == null) { this.logger.LogTrace("(-)[PREV_UTXO_IS_NULL]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } // Verify signature. if (!this.VerifySignature(prevUtxo, transaction, 0, ScriptVerify.None)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.CoinstakeVerifySignatureFailed.Throw(); } // Min age requirement. if (this.IsConfirmedInNPrevBlocks(prevUtxo, prevChainedHeader, this.consensusOptions.GetStakeMinConfirmations(prevChainedHeader.Height + 1, this.network) - 1)) { this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]"); ConsensusErrors.InvalidStakeDepth.Throw(); } this.CheckStakeKernelHash(context, headerBits, prevBlockStake, prevUtxo, txIn.PrevOut, transaction.Time); this.logger.LogTrace("(-)[OK]"); }
/// <inheritdoc/> public void CheckKernel(PosRuleContext context, ChainedHeader prevChainedHeader, uint headerBits, long transactionTime, OutPoint prevout) { this.logger.LogTrace("({0}:'{1}',{2}:0x{3:X},{4}:{5},{6}:'{7}.{8}')", nameof(prevChainedHeader), prevChainedHeader, nameof(headerBits), headerBits, nameof(transactionTime), transactionTime, nameof(prevout), prevout.Hash, prevout.N); FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { prevout.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { this.logger.LogTrace("(-)[READ_PREV_TX_FAILED]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } ChainedHeader prevBlock = this.chain.GetBlock(coins.BlockHash); if (prevBlock == null) { this.logger.LogTrace("(-)[REORG]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; if (this.IsConfirmedInNPrevBlocks(prevUtxo, prevChainedHeader, ((PosConsensusOptions)this.network.Consensus.Options).GetStakeMinConfirmations(prevChainedHeader.Height + 1, this.network) - 1)) { this.logger.LogTrace("(-)[LOW_COIN_AGE]"); ConsensusErrors.InvalidStakeDepth.Throw(); } BlockStake prevBlockStake = this.stakeChain.Get(prevChainedHeader.HashBlock); if (prevBlockStake == null) { this.logger.LogTrace("(-)[BAD_STAKE_BLOCK]"); ConsensusErrors.BadStakeBlock.Throw(); } this.CheckStakeKernelHash(context, headerBits, prevBlockStake, prevUtxo, prevout, (uint)transactionTime); }
/// <summary> /// Verifies transaction's signature. /// </summary> /// <param name="coin">UTXO that is spent in the transaction.</param> /// <param name="txTo">Transaction.</param> /// <param name="txToInN">Index of the transaction's input.</param> /// <param name="flagScriptVerify">Script verification flags.</param> /// <returns><c>true</c> if signature is valid.</returns> private bool VerifySignature(UnspentOutputs coin, Transaction txTo, int txToInN, ScriptVerify flagScriptVerify) { this.logger.LogTrace("({0}:'{1}/{2}',{3}:{4},{5}:{6})", nameof(coin), coin.TransactionId, coin.Height, nameof(txToInN), txToInN, nameof(flagScriptVerify), flagScriptVerify); TxIn input = txTo.Inputs[txToInN]; if (input.PrevOut.N >= coin.Outputs.Length) return false; if (input.PrevOut.Hash != coin.TransactionId) return false; TxOut output = coin.Outputs[input.PrevOut.N]; var txData = new PrecomputedTransactionData(txTo); var checker = new TransactionChecker(txTo, txToInN, output.Value, txData); var ctx = new ScriptEvaluationContext(this.chain.Network) { ScriptVerify = flagScriptVerify }; bool res = ctx.VerifyScript(input.ScriptSig, output.ScriptPubKey, checker); this.logger.LogTrace("(-):{0}", res); return res; }
private bool VerifySignature(UnspentOutputs txFrom, Transaction txTo, int txToInN, ScriptVerify flagScriptVerify) { var input = txTo.Inputs[txToInN]; if (input.PrevOut.N >= txFrom._Outputs.Length) { return(false); } if (input.PrevOut.Hash != txFrom.TransactionId) { return(false); } var output = txFrom._Outputs[input.PrevOut.N]; var txData = new PrecomputedTransactionData(txTo); var checker = new TransactionChecker(txTo, txToInN, output.Value, txData); var ctx = new ScriptEvaluationContext { ScriptVerify = flagScriptVerify }; return(ctx.VerifyScript(input.ScriptSig, output.ScriptPubKey, checker)); }
/// <summary> /// Checks that the stake kernel hash satisfies the target difficulty. /// </summary> /// <param name="context">Staking context.</param> /// <param name="headerBits">Chained block's header bits, which define the difficulty target.</param> /// <param name="prevBlockStake">Information about previous staked block.</param> /// <param name="stakingCoins">Coins that participate in staking.</param> /// <param name="prevout">Information about transaction id and index.</param> /// <param name="transactionTime">Transaction time.</param> /// <remarks> /// Coinstake must meet hash target according to the protocol: /// kernel (input 0) must meet the formula /// <c>hash(stakeModifierV2 + stakingCoins.Time + prevout.Hash + prevout.N + transactionTime) < target * weight</c>. /// This ensures that the chance of getting a coinstake is proportional to the amount of coins one owns. /// <para> /// The reason this hash is chosen is the following: /// <list type="number"> /// <item><paramref name="prevBlockStake.StakeModifierV2"/>: Scrambles computation to make it very difficult to precompute future proof-of-stake.</item> /// <item><paramref name="stakingCoins.Time"/>: Time of the coinstake UTXO. Slightly scrambles computation.</item> /// <item><paramref name="prevout.Hash"/> Hash of stakingCoins UTXO, to reduce the chance of nodes generating coinstake at the same time.</item> /// <item><paramref name="prevout.N"/>: Output number of stakingCoins UTXO, to reduce the chance of nodes generating coinstake at the same time.</item> /// <item><paramref name="transactionTime"/>: Timestamp of the coinstake transaction.</item> /// </list> /// Block or transaction tx hash should not be used here as they can be generated in vast /// quantities so as to generate blocks faster, degrading the system back into a proof-of-work situation. /// </para> /// </remarks> /// <exception cref="ConsensusErrors.StakeTimeViolation">Thrown in case transaction time is lower than it's own UTXO timestamp.</exception> /// <exception cref="ConsensusErrors.StakeHashInvalidTarget">Thrown in case PoS hash doesn't meet target protocol.</exception> private void CheckStakeKernelHash(PosRuleContext context, uint headerBits, BlockStake prevBlockStake, UnspentOutputs stakingCoins, OutPoint prevout, uint transactionTime) { this.logger.LogTrace("({0}:{1:X},{2}.{3}:'{4}',{5}:'{6}/{7}',{8}:'{9}',{10}:{11})", nameof(headerBits), headerBits, nameof(prevBlockStake), nameof(prevBlockStake.HashProof), prevBlockStake.HashProof, nameof(stakingCoins), stakingCoins.TransactionId, stakingCoins.Height, nameof(prevout), prevout, nameof(transactionTime), transactionTime); if (transactionTime < stakingCoins.Time) { this.logger.LogTrace("Coinstake transaction timestamp {0} is lower than it's own UTXO timestamp {1}.", transactionTime, stakingCoins.Time); this.logger.LogTrace("(-)[BAD_STAKE_TIME]"); ConsensusErrors.StakeTimeViolation.Throw(); } // Base target. BigInteger target = new Target(headerBits).ToBigInteger(); // TODO: Investigate: // The POS protocol should probably put a limit on the max amount that can be staked // not a hard limit but a limit that allow any amount to be staked with a max weight value. // the max weight should not exceed the max uint256 array size (array size = 32). // Weighted target. long valueIn = stakingCoins.Outputs[prevout.N].Value.Satoshi; BigInteger weight = BigInteger.ValueOf(valueIn); BigInteger weightedTarget = target.Multiply(weight); context.TargetProofOfStake = ToUInt256(weightedTarget); this.logger.LogTrace("POS target is '{0}', weighted target for {1} coins is '{2}'.", ToUInt256(target), valueIn, context.TargetProofOfStake); uint256 stakeModifierV2 = prevBlockStake.StakeModifierV2; // Calculate hash. using (var ms = new MemoryStream()) { var serializer = new BitcoinStream(ms, true); serializer.ReadWrite(stakeModifierV2); serializer.ReadWrite(stakingCoins.Time); serializer.ReadWrite(prevout.Hash); serializer.ReadWrite(prevout.N); serializer.ReadWrite(transactionTime); context.HashProofOfStake = Hashes.Hash256(ms.ToArray()); } this.logger.LogTrace("Stake modifier V2 is '{0}', hash POS is '{1}'.", stakeModifierV2, context.HashProofOfStake); // Now check if proof-of-stake hash meets target protocol. var hashProofOfStakeTarget = new BigInteger(1, context.HashProofOfStake.ToBytes(false)); if (hashProofOfStakeTarget.CompareTo(weightedTarget) > 0) { this.logger.LogTrace("(-)[TARGET_MISSED]"); ConsensusErrors.StakeHashInvalidTarget.Throw(); } this.logger.LogTrace("(-)[OK]"); }
// Stratis kernel protocol // coinstake must meet hash target according to the protocol: // kernel (input 0) must meet the formula // hash(nStakeModifier + txPrev.block.nTime + txPrev.nTime + txPrev.vout.hash + txPrev.vout.n + nTime) < bnTarget * nWeight // this ensures that the chance of getting a coinstake is proportional to the // amount of coins one owns. // The reason this hash is chosen is the following: // nStakeModifier: scrambles computation to make it very difficult to precompute // future proof-of-stake // txPrev.block.nTime: prevent nodes from guessing a good timestamp to // generate transaction for future advantage, // obsolete since v3 // txPrev.nTime: slightly scrambles computation // txPrev.vout.hash: hash of txPrev, to reduce the chance of nodes // generating coinstake at the same time // txPrev.vout.n: output number of txPrev, to reduce the chance of nodes // generating coinstake at the same time // nTime: current timestamp // block/tx hash should not be used here as they can be generated in vast // quantities so as to generate blocks faster, degrading the system back into // a proof-of-work situation. // private void CheckStakeKernelHashV2(ContextInformation context, ChainedBlock pindexPrev, uint nBits, uint nTimeBlockFrom, BlockStake prevBlockStake, UnspentOutputs txPrev, OutPoint prevout, uint nTimeTx) { if (nTimeTx < txPrev.Time) { ConsensusErrors.StakeTimeViolation.Throw(); } // Base target var bnTarget = new Target(nBits).ToBigInteger(); // TODO: Investigate: // The POS protocol should probably put a limit on the max amount that can be staked // not a hard limit but a limit that allow any amount to be staked with a max weight value. // the max weight should not exceed the max uint256 array size (array siez = 32) // Weighted target var nValueIn = txPrev._Outputs[prevout.N].Value.Satoshi; var bnWeight = BigInteger.ValueOf(nValueIn); bnTarget = bnTarget.Multiply(bnWeight); context.Stake.TargetProofOfStake = ToUInt256(bnTarget); var nStakeModifier = prevBlockStake.StakeModifier; //pindexPrev.Header.BlockStake.StakeModifier; uint256 bnStakeModifierV2 = prevBlockStake.StakeModifierV2; //pindexPrev.Header.BlockStake.StakeModifierV2; int nStakeModifierHeight = pindexPrev.Height; var nStakeModifierTime = pindexPrev.Header.Time; // Calculate hash using (var ms = new MemoryStream()) { var serializer = new BitcoinStream(ms, true); if (IsProtocolV3((int)nTimeTx)) { serializer.ReadWrite(bnStakeModifierV2); } else { serializer.ReadWrite(nStakeModifier); serializer.ReadWrite(nTimeBlockFrom); } serializer.ReadWrite(txPrev.Time); serializer.ReadWrite(prevout.Hash); serializer.ReadWrite(prevout.N); serializer.ReadWrite(nTimeTx); context.Stake.HashProofOfStake = Hashes.Hash256(ms.ToArray()); } //LogPrintf("CheckStakeKernelHash() : using modifier 0x%016x at height=%d timestamp=%s for block from timestamp=%s\n", // nStakeModifier, nStakeModifierHeight, // DateTimeStrFormat(nStakeModifierTime), // DateTimeStrFormat(nTimeBlockFrom)); //LogPrintf("CheckStakeKernelHash() : check modifier=0x%016x nTimeBlockFrom=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n", // nStakeModifier, // nTimeBlockFrom, txPrev.nTime, prevout.n, nTimeTx, // hashProofOfStake.ToString()); // Now check if proof-of-stake hash meets target protocol var hashProofOfStakeTarget = new BigInteger(1, context.Stake.HashProofOfStake.ToBytes(false)); if (hashProofOfStakeTarget.CompareTo(bnTarget) > 0) { ConsensusErrors.StakeHashInvalidTarget.Throw(); } // if (fDebug && !fPrintProofOfStake) // { // LogPrintf("CheckStakeKernelHash() : using modifier 0x%016x at height=%d timestamp=%s for block from timestamp=%s\n", // nStakeModifier, nStakeModifierHeight, // DateTimeStrFormat(nStakeModifierTime), // DateTimeStrFormat(nTimeBlockFrom)); // LogPrintf("CheckStakeKernelHash() : pass modifier=0x%016x nTimeBlockFrom=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n", // nStakeModifier, // nTimeBlockFrom, txPrev.nTime, prevout.n, nTimeTx, // hashProofOfStake.ToString()); // } }
/// <inheritdoc /> public virtual void ExecuteBlock(RuleContext context, TaskScheduler taskScheduler = null) { this.logger.LogTrace("()"); Block block = context.BlockValidationContext.Block; ChainedBlock index = context.BlockValidationContext.ChainedBlock; DeploymentFlags flags = context.Flags; UnspentOutputSet view = context.Set; this.PerformanceCounter.AddProcessedBlocks(1); taskScheduler = taskScheduler ?? TaskScheduler.Default; if (!context.SkipValidation) { if (flags.EnforceBIP30) { foreach (Transaction tx in block.Transactions) { UnspentOutputs coins = view.AccessCoins(tx.GetHash()); if ((coins != null) && !coins.IsPrunable) { this.logger.LogTrace("(-)[BAD_TX_BIP_30]"); ConsensusErrors.BadTransactionBIP30.Throw(); } } } } else { this.logger.LogTrace("BIP30 validation skipped for checkpointed block at height {0}.", index.Height); } long sigOpsCost = 0; Money fees = Money.Zero; var checkInputs = new List <Task <bool> >(); for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++) { this.PerformanceCounter.AddProcessedTransactions(1); Transaction tx = block.Transactions[txIndex]; if (!context.SkipValidation) { if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { int[] prevheights; if (!view.HaveInputs(tx)) { this.logger.LogTrace("(-)[BAD_TX_NO_INPUT]"); ConsensusErrors.BadTransactionMissingInput.Throw(); } prevheights = new int[tx.Inputs.Count]; // Check that transaction is BIP68 final. // BIP68 lock checks (as opposed to nLockTime checks) must // be in ConnectBlock because they require the UTXO set. for (int j = 0; j < tx.Inputs.Count; j++) { prevheights[j] = (int)view.AccessCoins(tx.Inputs[j].PrevOut.Hash).Height; } if (!tx.CheckSequenceLocks(prevheights, index, flags.LockTimeFlags)) { this.logger.LogTrace("(-)[BAD_TX_NON_FINAL]"); ConsensusErrors.BadTransactionNonFinal.Throw(); } } // GetTransactionSignatureOperationCost counts 3 types of sigops: // * legacy (always), // * p2sh (when P2SH enabled in flags and excludes coinbase), // * witness (when witness enabled in flags and excludes coinbase). sigOpsCost += this.GetTransactionSignatureOperationCost(tx, view, flags); if (sigOpsCost > this.ConsensusOptions.MaxBlockSigopsCost) { ConsensusErrors.BadBlockSigOps.Throw(); } // TODO: Simplify this condition. if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { this.CheckInputs(tx, view, index.Height); fees += view.GetValueIn(tx) - tx.TotalOut; var txData = new PrecomputedTransactionData(tx); for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) { this.PerformanceCounter.AddProcessedInputs(1); TxIn input = tx.Inputs[inputIndex]; int inputIndexCopy = inputIndex; TxOut txout = view.GetOutputFor(input); var checkInput = new Task <bool>(() => { var checker = new TransactionChecker(tx, inputIndexCopy, txout.Value, txData); var ctx = new ScriptEvaluationContext(); ctx.ScriptVerify = flags.ScriptFlags; return(ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker)); }); checkInput.Start(taskScheduler); checkInputs.Add(checkInput); } } } this.UpdateCoinView(context, tx); } if (!context.SkipValidation) { this.CheckBlockReward(context, fees, index.Height, block); bool passed = checkInputs.All(c => c.GetAwaiter().GetResult()); if (!passed) { this.logger.LogTrace("(-)[BAD_TX_SCRIPT]"); ConsensusErrors.BadTransactionScriptError.Throw(); } } else { this.logger.LogTrace("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.", index.Height); } this.logger.LogTrace("(-)"); }