From 188bd83110638747874e1763ea6dc8e66f80d4e4 Mon Sep 17 00:00:00 2001 From: Zannick Date: Wed, 18 May 2022 18:04:06 -0700 Subject: [PATCH 01/25] [Refactor] SetOutputs, ArrangeOutBlinds Moves some functionality for creating RingCT transactions into helper functions. Removes a duplicate error for when setting a fee of 0 fails. (Later we will attempt to set a fee of nFeeRet, which would be 0 in this case anyway.) --- src/veil/ringct/anonwallet.cpp | 279 +++++++++++++++++++-------------- src/veil/ringct/anonwallet.h | 22 +++ 2 files changed, 179 insertions(+), 122 deletions(-) diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index a39f6eed5a..a4377e093b 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -3471,6 +3471,114 @@ bool AnonWallet::SetBlinds( return true; } +bool AnonWallet::SetOutputs( + std::vector& vpout, + std::vector& vecSend, + CAmount& nFeeRet, + CAmount& nFeeNeeded, + size_t nSubtractFeeFromAmount, + CAmount& nValueOutPlain, + int& nChangePosInOut, + bool fSkipFee, + bool fFeesFromChange, + std::string& sError) +{ + OUTPUT_PTR outFee = MAKE_OUTPUT(); + outFee->vData.push_back(DO_FEE); + outFee->vData.resize(9); // More bytes than varint fee could use + vpout.push_back(outFee); + + bool fFirst = true; + for (size_t i = 0; i < vecSend.size(); ++i) { + auto &recipient = vecSend[i]; + + // Don't take out the fee if a) skip fee, or + // b) we're in multi-tx mode and nChange is sufficient to cover the fee + if (!fSkipFee && !fFeesFromChange) + recipient.ApplySubFee(nFeeRet, nSubtractFeeFromAmount, fFirst); + + OUTPUT_PTR txbout; + CreateOutput(txbout, recipient, sError); + + if (recipient.nType == OUTPUT_STANDARD) { + nValueOutPlain += recipient.nAmount; + } + + if (recipient.fChange) { + nChangePosInOut = i; + // Remove the fee from the change for multitx + if (fFeesFromChange) { + recipient.SetAmount(recipient.nAmount - nFeeRet); + } + } + + recipient.n = vpout.size(); + vpout.push_back(txbout); + if (recipient.nType == OUTPUT_CT || recipient.nType == OUTPUT_RINGCT) { + if (recipient.vBlind.size() != 32) { + recipient.vBlind.resize(32); + GetStrongRandBytes(&recipient.vBlind[0], 32); + } + + if (!AddCTData(txbout.get(), recipient, sError)) + return false; + } + } + return true; +} + +bool AnonWallet::ArrangeOutBlinds( + std::vector& vpout, + std::vector& vecSend, + std::vector& vpOutCommits, + std::vector& vpOutBlinds, + std::vector& vBlindPlain, + secp256k1_pedersen_commitment* plainCommitment, + CAmount nValueOutPlain, + int nChangePosInOut, + bool fCTOut, + std::string& sError) +{ + if (nValueOutPlain > 0) { + if (!secp256k1_pedersen_commit(secp256k1_ctx_blind, plainCommitment, &vBlindPlain[0], + (uint64_t) nValueOutPlain, secp256k1_generator_h)) { + sError = strprintf("Pedersen Commit failed for plain out."); + return error("%s: %s", __func__, sError); + } + + vpOutCommits.push_back(plainCommitment->data); + vpOutBlinds.push_back(&vBlindPlain[0]); + } + + // Update the change output commitment + for (int i = 0; i < vecSend.size(); ++i) { + auto &r = vecSend[i]; + + if (i == nChangePosInOut) { + // Change amount may have changed + + if (r.nType != (fCTOut ? OUTPUT_CT : OUTPUT_RINGCT)) { + sError = strprintf("Change output is not %s type.", fCTOut ? "CT" : "RingCT"); + return error("%s: %s", __func__, sError); + } + + if (r.vBlind.size() != 32) { + r.vBlind.resize(32); + GetStrongRandBytes(&r.vBlind[0], 32); + } + + if (!AddCTData(vpout[r.n].get(), r, sError)) + return false; + } + + if (r.nType == OUTPUT_CT || r.nType == OUTPUT_RINGCT) { + vpOutCommits.push_back(vpout[r.n]->GetPCommitment()->data); + vpOutBlinds.push_back(&r.vBlind[0]); + } + } + return true; +} + // Returns bool bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, std::vector &vecSend, bool sign, size_t nRingSize, size_t nInputsPerSig, size_t nMaximumInputs, CAmount &nFeeRet, const CCoinControl *coinControl, @@ -3662,47 +3770,10 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st nValueOutPlain = 0; nChangePosInOut = -1; - OUTPUT_PTR outFee = MAKE_OUTPUT(); - outFee->vData.push_back(DO_FEE); - outFee->vData.resize(9); // More bytes than varint fee could use - txNew.vpout.push_back(outFee); - - bool fFirst = true; bool fFeesFromChange = nMaximumInputs > 0 && nChange >= MIN_FINAL_CHANGE + nFeeRet; - for (size_t i = 0; i < vecSend.size(); ++i) { - auto &recipient = vecSend[i]; - - // Don't take out the fee if a) skip fee, or - // b) we're in multi-tx mode and nChange is sufficient to cover the fee - if (!fSkipFee && !fFeesFromChange) - recipient.ApplySubFee(nFeeRet, nSubtractFeeFromAmount, fFirst); - - OUTPUT_PTR txbout; - CreateOutput(txbout, recipient, sError); - - if (recipient.nType == OUTPUT_STANDARD) { - nValueOutPlain += recipient.nAmount; - } - - if (recipient.fChange) { - nChangePosInOut = i; - // Remove the fee from the change for multitx - if (fFeesFromChange) { - recipient.SetAmount(recipient.nAmount - nFeeRet); - } - } - - recipient.n = txNew.vpout.size(); - txNew.vpout.push_back(txbout); - if (recipient.nType == OUTPUT_CT || recipient.nType == OUTPUT_RINGCT) { - if (recipient.vBlind.size() != 32) { - recipient.vBlind.resize(32); - GetStrongRandBytes(&recipient.vBlind[0], 32); - } - - if (!AddCTData(txbout.get(), recipient, sError)) - return false; - } + if (!SetOutputs(txNew.vpout, vecSend, nFeeRet, nFeeNeeded, nSubtractFeeFromAmount, + nValueOutPlain, nChangePosInOut, fSkipFee, fFeesFromChange, sError)) { + return false; } if (!fAlreadyHaveInputs) { @@ -3713,14 +3784,9 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st if (fSkipFee) { nFeeNeeded = 0; nFeeRet = 0; - std::vector &vData = ((CTxOutData*)txNew.vpout[0].get())->vData; - vData.resize(1); - if (0 != PutVarInt(vData, 0)) { - sError = strprintf("Failed to add skipped fee to transaction."); - return error("%s: %s", __func__, sError); - } break; } + // Fee required beyond this point nBytes = GetVirtualTransactionSize(txNew); @@ -3730,58 +3796,57 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st // If we made it here and we aren't even able to meet the relay fee on the next pass, give up // because we must be at the maximum allowed fee. - if (!fSkipFee && nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { + if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { sError = strprintf("Transaction too large for fee policy."); return error("%s: %s", __func__, sError); } - if (!fSkipFee) { - if (nFeeRet >= nFeeNeeded) { - // Reduce fee to only the needed amount if possible. This - // prevents potential overpayment in fees if the coins - // selected to meet nFeeNeeded result in a transaction that - // requires less fee than the prior iteration. - if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 - && (!fSubtractFeeFromOutputs || fFeesFromChange)) { - auto &r = vecSend[nChangePosInOut]; - - CAmount extraFeePaid = nFeeRet - nFeeNeeded; - r.nAmount += extraFeePaid; - nFeeRet -= extraFeePaid; - } - break; // Done, enough fee included. - } else if (!pick_new_inputs) { - // This shouldn't happen, we should have had enough excess - // fee to pay for the new output and still meet nFeeNeeded - // Or we should have just subtracted fee from recipients and - // nFeeNeeded should not have changed + if (nFeeRet >= nFeeNeeded) { + // Reduce fee to only the needed amount if possible. This + // prevents potential overpayment in fees if the coins + // selected to meet nFeeNeeded result in a transaction that + // requires less fee than the prior iteration. + if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 + && (!fSubtractFeeFromOutputs || fFeesFromChange)) { + auto &r = vecSend[nChangePosInOut]; + CAmount extraFeePaid = nFeeRet - nFeeNeeded; - if (!fSubtractFeeFromOutputs || !(--nSubFeeTries)) { - sError = strprintf("Transaction fee and change calculation failed."); - return error("%s: %s", __func__, sError); - } + r.nAmount += extraFeePaid; + nFeeRet -= extraFeePaid; } + break; // Done, enough fee included. + } else if (!pick_new_inputs) { + // This shouldn't happen, we should have had enough excess + // fee to pay for the new output and still meet nFeeNeeded + // Or we should have just subtracted fee from recipients and + // nFeeNeeded should not have changed - // Try to reduce change to include necessary fee - if (nChangePosInOut != -1 && (!fSubtractFeeFromOutputs || nMaximumInputs > 0)) { - auto &r = vecSend[nChangePosInOut]; - CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet; - if (r.nAmount >= MIN_FINAL_CHANGE + additionalFeeNeeded) { - r.nAmount -= additionalFeeNeeded; - nFeeRet += additionalFeeNeeded; - break; // Done, able to increase fee from change - } + if (!fSubtractFeeFromOutputs || !(--nSubFeeTries)) { + sError = strprintf("Transaction fee and change calculation failed."); + return error("%s: %s", __func__, sError); } + } - // If subtracting fee from recipients, we now know what fee we - // need to subtract, we have no reason to reselect inputs - if (fSubtractFeeFromOutputs) { - pick_new_inputs = false; + // Try to reduce change to include necessary fee + if (nChangePosInOut != -1 && (!fSubtractFeeFromOutputs || nMaximumInputs > 0)) { + auto &r = vecSend[nChangePosInOut]; + CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet; + if (r.nAmount >= MIN_FINAL_CHANGE + additionalFeeNeeded) { + r.nAmount -= additionalFeeNeeded; + nFeeRet += additionalFeeNeeded; + break; // Done, able to increase fee from change } + } - // Include more fee and try again. - nFeeRet = nFeeNeeded; + // If subtracting fee from recipients, we now know what fee we + // need to subtract, we have no reason to reselect inputs + if (fSubtractFeeFromOutputs) { + pick_new_inputs = false; } + + // Include more fee and try again. + nFeeRet = nFeeNeeded; + if (fZerocoinInputs) { sError = strprintf("Not able to calculate fee."); return error("%s: %s", __func__, sError); @@ -3806,42 +3871,9 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st vBlindPlain.resize(32); memset(&vBlindPlain[0], 0, 32); - if (nValueOutPlain > 0) { - if (!secp256k1_pedersen_commit(secp256k1_ctx_blind, &plainCommitment, &vBlindPlain[0], - (uint64_t) nValueOutPlain, secp256k1_generator_h)) { - sError = strprintf("Pedersen Commit failed for plain out."); - return error("%s: %s", __func__, sError); - } - - vpOutCommits.push_back(plainCommitment.data); - vpOutBlinds.push_back(&vBlindPlain[0]); - } - - // Update the change output commitment - for (size_t i = 0; i < vecSend.size(); ++i) { - auto &r = vecSend[i]; - - if ((int)i == nChangePosInOut) { - // Change amount may have changed - - if (r.nType != (fCTOut ? OUTPUT_CT : OUTPUT_RINGCT)) { - sError = strprintf("Change output is not %s type.", fCTOut ? "CT" : "RingCT"); - return error("%s: %s", __func__, sError); - } - - if (r.vBlind.size() != 32) { - r.vBlind.resize(32); - GetStrongRandBytes(&r.vBlind[0], 32); - } - - if (!AddCTData(txNew.vpout[r.n].get(), r, sError)) - return false; - } - - if (r.nType == OUTPUT_CT || r.nType == OUTPUT_RINGCT) { - vpOutCommits.push_back(txNew.vpout[r.n]->GetPCommitment()->data); - vpOutBlinds.push_back(&r.vBlind[0]); - } + if (!ArrangeOutBlinds(txNew.vpout, vecSend, vpOutCommits, vpOutBlinds, vBlindPlain, + &plainCommitment, nValueOutPlain, nChangePosInOut, fCTOut, sError)) { + return false; } //Add actual fee to CT Fee output @@ -3883,6 +3915,9 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st vSecretColumns[l], sError)) return false; + // Do a bunch of math on both inputs and outputs. + // TODO: Refactor into Complete + std::vector &vKeyImages = txin.scriptData.stack[0]; uint8_t blindSum[32]; diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 84806a566a..5ac81e0120 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -413,6 +413,28 @@ class AnonWallet ec_point& vInputBlinds, size_t& secretColumn, std::string& sError); + bool SetOutputs( + std::vector& vpout, + std::vector& vecSend, + CAmount& nFeeRet, + CAmount& nFeeNeeded, + size_t nSubtractFeeFromAmount, + CAmount& nValueOutPlain, + int& nChangePosInOut, + bool fSkipFee, + bool fFeesFromChange, + std::string& sError); + bool ArrangeOutBlinds( + std::vector& vpout, + std::vector& vecSend, + std::vector& vpOutCommits, + std::vector& vpOutBlinds, + std::vector& vBlindPlain, + secp256k1_pedersen_commitment* plainCommitment, + CAmount nValueOutPlain, + int nChangePosInOut, + bool fCTOut, + std::string& sError); template bool werror(std::string fmt, Params... parameters) const { From 43d50f7ec0533a3f75cd44b2b1ab6e824adaadb6 Mon Sep 17 00:00:00 2001 From: Zannick Date: Wed, 18 May 2022 18:46:04 -0700 Subject: [PATCH 02/25] [Refactor] Parameterize CreateCoinStake. Change CreateTxOuts to create the actual TxOut pointers rather than fill a list of TxOuts and separately extract the pointers. --- src/miner.cpp | 2 +- src/veil/proofofstake/stakeinput.cpp | 6 +++--- src/veil/proofofstake/stakeinput.h | 4 ++-- src/wallet/wallet.cpp | 22 ++++++++++++++-------- src/wallet/wallet.h | 8 +++++++- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index 64537c9176..65abd5a725 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -186,7 +186,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc uint32_t nTxNewTime = 0; #ifdef ENABLE_WALLET - if (!gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) && pwalletMain->CreateCoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) { + if (!gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) && pwalletMain->CreateZerocoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) { pblock->nTime = nTxNewTime; } else { return nullptr; diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 70ab6da97f..2ed335a674 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -253,7 +253,7 @@ bool ZerocoinStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) #endif } -bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) +bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) { #ifdef ENABLE_WALLET if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { @@ -268,7 +268,7 @@ bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CA CDeterministicMint dMint; if (!pwallet->CreateZOutPut(denomStaked, outReward, dMint)) return error("%s: failed to create zerocoin output", __func__); - vout.emplace_back(outReward); + vpout.emplace_back(outReward.GetSharedPtr()); //Add new staked denom to our wallet if (!pwallet->DatabaseMint(dMint)) @@ -281,7 +281,7 @@ bool ZerocoinStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CA auto denomReward = libzerocoin::CoinDenomination::ZQ_TEN; if (!pwallet->CreateZOutPut(denomReward, out, dMintReward)) return error("%s: failed to create Zerocoin output", __func__); - vout.emplace_back(out); + vpout.emplace_back(out.GetSharedPtr()); if (!pwallet->DatabaseMint(dMintReward)) return error("%s: failed to database mint reward", __func__); diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index b2a7443316..96336fb23a 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -34,7 +34,7 @@ class CStakeInput libzerocoin::CoinDenomination GetDenomination() {return denom;}; virtual bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) = 0; - virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) = 0; + virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) = 0; virtual bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) = 0; }; @@ -67,7 +67,7 @@ class ZerocoinStake : public CStakeInput bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; CDataStream GetUniqueness() override; bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; - bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) override; + bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; bool IsZerocoins() override { return true; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d9274a1b0d..14604a1bf0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3859,8 +3859,10 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CTransac return true; } +template bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart) { + static_assert(std::is_base_of::value, "TStake must derive from CStakeInput"); // The following split & combine thresholds are important to security // Should not be adjusted if you don't understand the consequences //int64_t nCombineThreshold = 0; @@ -3873,7 +3875,7 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, txNew.vpout.emplace_back(CTxOut(0, scriptEmpty).GetSharedPtr()); // Get the list of stakable inputs - std::list > listInputs; + std::list> listInputs; if (!SelectStakeCoins(listInputs)) return false; @@ -3886,7 +3888,7 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CScript scriptPubKeyKernel; bool fKernelFound = false; - for (std::unique_ptr& stakeInput : listInputs) { + for (std::unique_ptr& stakeInput : listInputs) { CAmount nCredit = 0; // Make sure the wallet is unlocked and shutdown hasn't been requested if (IsLocked() || ShutdownRequested()) @@ -3941,15 +3943,13 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, nCredit += nNetworkReward; // Create the output transaction(s) - std::vector vout; - if (!stakeInput->CreateTxOuts(this, vout, nBlockReward)) { + txNew.vpout.clear(); + txNew.vpout.emplace_back(CTxOut(0, scriptEmpty).GetSharedPtr()); + + if (!stakeInput->CreateTxOuts(this, txNew.vpout, nBlockReward)) { LogPrintf("%s : failed to get scriptPubKey\n", __func__); continue; } - txNew.vpout.clear(); - txNew.vpout.emplace_back(CTxOut(0, scriptEmpty).GetSharedPtr()); - for (auto& txOut : vout) - txNew.vpout.emplace_back(txOut.GetSharedPtr()); // Limit size unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR; @@ -3981,6 +3981,12 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, } return fKernelFound; } + +bool CWallet::CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart) +{ + return CreateCoinStake(pindexBest, nBits, txNew, nTxNewTime, nComputeTimeStart); +} + bool CWallet::SelectStakeCoins(std::list >& listInputs) { LOCK(cs_main); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 710505e1bd..598d02aaac 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1094,7 +1094,14 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface CAmount GetZerocoinBalance(bool fMatureOnly, const int min_depth=0) const; CAmount GetUnconfirmedZerocoinBalance() const; CAmount GetImmatureZerocoinBalance() const; + +private: + template bool CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart); + +public: + bool CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart); + bool SelectStakeCoins(std::list >& listInputs); // sub wallet seeds @@ -1381,7 +1388,6 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface }; - /** A key allocated from the key pool. */ class CReserveKey final : public CReserveScript { From 01c1ae91887f7ae29ded2ffdf60d72c406619b7a Mon Sep 17 00:00:00 2001 From: Zannick Date: Mon, 27 Jun 2022 17:16:18 -0700 Subject: [PATCH 03/25] Initial RingCTStake class. This sets up a basic but incomplete implementation of CreateRingCTStake. Includes a weighting function that groups coins into buckets along powers of 16 sats, where the weight of the coin would be equal to the smallest value in the bucket regardless of the actual value of the coin. --- src/validation.cpp | 1 + src/veil/proofofstake/kernel.cpp | 1 + src/veil/proofofstake/stakeinput.cpp | 157 ++++++++++++++++++++++++++- src/veil/proofofstake/stakeinput.h | 31 +++++- src/veil/ringct/anonwallet.cpp | 110 +++++++++++++++++++ src/veil/ringct/anonwallet.h | 1 + src/wallet/wallet.cpp | 28 +++++ src/wallet/wallet.h | 2 + 8 files changed, 329 insertions(+), 2 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index be1430b363..33bbc8b2cd 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2823,6 +2823,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CAmount nCreated = nBlockValueOut - nBlockValueIn; // Check change doesn't exceed fees and that only PoFN blocks have blind txouts + // TODO for ringctstake if ((block.fProofOfFullNode || block.hashPoFN != uint256()) && block.vtx[1]->HasBlindedValues()) { // This should consist only of block fees, so it should be <= nFees CAmount nBlindFeePayout = 0; diff --git a/src/veil/proofofstake/kernel.cpp b/src/veil/proofofstake/kernel.cpp index 69d9f1d84f..67344acdda 100644 --- a/src/veil/proofofstake/kernel.cpp +++ b/src/veil/proofofstake/kernel.cpp @@ -110,6 +110,7 @@ bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockF } // Check kernel hash target and coinstake signature +// TODO: parameterize bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, const uint32_t& nBits, const unsigned int& nTimeBlock, uint256& hashProofOfStake, std::unique_ptr& stake) { if (!txRef->IsCoinStake()) diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 2ed335a674..6ffcfe6845 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -15,8 +15,24 @@ #ifdef ENABLE_WALLET #include "wallet/wallet.h" #endif +#include "veil/ringct/anonwallet.h" -typedef std::vector valtype; +// Based on https://stackoverflow.com/a/23000588 +int fast_log16(uint64_t value) +{ + // Round value up to the nearest 2^k - 1 + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + // value & 0x1111... reduces the max index of the leftmost 1 down to a multiple of 4 + // There are now only 16 possible values + // Multiply by 0x0111... results in a unique value in the top 4 bits + // Thanks to the repeated addition, this is exactly log16 of our number. + return ((value & 0x1111111111111111u) * 0x0111111111111111u) >> 60; +} // Use the PoW hash or the PoS hash uint256 GetHashFromIndex(const CBlockIndex* pindexSample) @@ -99,6 +115,145 @@ bool GetStakeModifier(uint64_t& nStakeModifier, const CBlockIndex& pindexChainPr return true; } +// BRACKETBASE is the Log base that establishes brackets, see below +const uint64_t BRACKETBASE = 16; +// log2 of the BASE is the amount we have to shift by to be equivalent to multiplying by the BASE +// so that << (4 * x) is the same as pow(16, x+1) +const uint32_t LOG2BRACKETBASE = 4; + +const CAmount nBareMinStake = BRACKETBASE; +const CAmount nOneSat = 1; + +bool CheckMinStake(const CAmount& nAmount) +{ + // Protocol needs to enforce bare minimum (mathematical min). User can define selective min + if (nAmount <= nBareMinStake) + return false; + return true; +} + + +CBlockIndex* RingCTStake::GetIndexFrom() +{ + if (pindexFrom) + return pindexFrom; + + if (coin.nDepth > 0) + //note that this will be a nullptr if the height DNE + pindexFrom = chainActive[coin.nDepth]; + + return pindexFrom; +} + +bool RingCTStake::GetTxFrom(CTransaction& tx) +{ + // TODO + return false; +} + +CAmount RingCTStake::GetValue() +{ + const COutputRecord* oR = coin.rtx->second.GetOutput(coin.i); + if (!oR) + return 0; + return oR->GetAmount(); +} + +// Returns a weight amount based on a bracket for privacy. +// The bracket is given by log16 (value in sats - 1), and the weight is equal to +// the minimum value of the bracket. Values of 16 sats and below are not eligible +// to be staked. +// +// Bracket min max +// ------- --- --- +// 0 0 ( 0.00000000) 16 ( 0.00000016) +// 1 17 ( 0.00000017) 256 ( 0.00000256) +// 2 257 ( 0.00000257) 4096 ( 0.00004096) +// 3 4097 ( 0.00004097) 65536 ( 0.00065536) +// 4 65537 ( 0.00065537) 1048576 ( 0.01048576) +// 5 1048577 ( 0.01048577) 16777216 ( 0.16777216) +// 6 16777217 ( 0.16777217) 268435456 ( 2.68435456) +// 7 268435457 ( 2.68435457) 4294967296 ( 42.94967296) +// 8 4294967297 ( 42.94967297) 68719476736 ( 687.19476736) +// 9 68719476737 ( 687.19476737) 1099511627776 ( 10995.11627776) +// 10 1099511627777 ( 10995.11627777) 17592186044416 ( 175921.86044416) +// 11 17592186044417 ( 175921.86044417) 281474976710656 ( 2814749.76710656) +// 12 281474976710657 ( 2814749.76710657) 4503599627370496 ( 45035996.27370496) +// 13 4503599627370497 ( 45035996.27370497) 72057594037927936 (720575940.37927936) +CAmount RingCTStake::GetWeight() +{ + CAmount nValueIn = GetValue(); + // fast mode + if (nValueIn <= nBareMinStake) + return 0; + + // bracket is at least 1 now. + int bracket = fast_log16(nValueIn - nOneSat); + // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really + // 1 << (4 + 4 * bracket - 4) + return (1 << (4 * bracket)) + nOneSat; +} + +bool RingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) +{ + if (!pindexChainPrev) + return false; + + return GetStakeModifier(nStakeModifier, *pindexChainPrev); +} + +CDataStream RingCTStake::GetUniqueness() +{ + //The unique identifier for a VEIL RingCT txo is... txhash + n? + CDataStream ss(SER_GETHASH, 0); + ss << coin.txhash; + ss << coin.i; + return ss; +} + +bool RingCTStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) +{ +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { +#endif + return error("%s: wallet disabled", __func__); +#ifdef ENABLE_WALLET + } + + return pwallet->GetAnonWallet()->CoinToTxIn(coin, txIn, 32); +#endif +} + +bool RingCTStake::CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nReward) +{ +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { +#endif + return error("%s: wallet disabled", __func__); +#ifdef ENABLE_WALLET + } + + //Create an output returning the RingCT and adding the reward to it + // or two separate rewards + // The former could be an info leak over time when it gets staked again in a different + // bucket. + // The latter is an info leak of the reward... + + + return true; +#endif +} + +bool RingCTStake::CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) +{ + return false; +} + +bool RingCTStake::MarkSpent(AnonWallet* panonwallet, const uint256& txid) +{ + return false; +} + ZerocoinStake::ZerocoinStake(const libzerocoin::CoinSpend& spend) { this->nChecksum = spend.getAccumulatorChecksum(); diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 96336fb23a..3b2428f379 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -12,9 +12,11 @@ #include "libzerocoin/CoinSpend.h" +class AnonWallet; class CKeyStore; class CWallet; class CWalletTx; +class COutputR; class CStakeInput { @@ -39,7 +41,34 @@ class CStakeInput }; -// zPIVStake can take two forms +class RingCTStake : public CStakeInput +{ +private: + const COutputR& coin; + // Need: depth, COutputRecord (amount) + +public: + explicit RingCTStake(const COutputR& coin_) : coin(coin_) { }; + + CBlockIndex* GetIndexFrom() override; + bool GetTxFrom(CTransaction& tx) override; + CAmount GetValue() override; + CAmount GetWeight() override; + bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; + CDataStream GetUniqueness() override; + + bool IsZerocoins() override { return false; } + + bool MarkSpent(AnonWallet* panonwallet, const uint256& txid); + bool CompleteTx(AnonWallet* panonwallet, CMutableTransaction& txNew); + + bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; + bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; + bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; +}; + + +// ZerocoinStake can take two forms // 1) the stake candidate, which is a zcmint that is attempted to be staked // 2) a staked zerocoin, which is a zcspend that has successfully staked class ZerocoinStake : public CStakeInput diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index a4377e093b..fb32938d35 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -4073,6 +4073,116 @@ bool AnonWallet::AddAnonInputs(CWalletTx &wtx, CTransactionRecord &rtx, std::vec return true; } +bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) +{ + // Mostly copied from AddAnonInputs_Inner + std::string sError; + int rv; + + txin.nSequence = CTxIn::SEQUENCE_FINAL; + txin.prevout.n = COutPoint::ANON_MARKER; + txin.SetAnonInfo(1, nRingSize); + + std::vector> vCoins; + vCoins.emplace_back(coin.rtx, coin.rtx->second.GetOutput(coin.i)->GetAmount()); + + std::set setHave; // Anon prev-outputs can only be used once per transaction. + // Must add real outputs to setHave before picking decoys + uint32_t nSigInputs, nSigRingSize; + txin.GetAnonInfo(nSigInputs, nSigRingSize); + + std::vector> vMI; + ec_point vInputBlinds; + size_t secretColumn; + + // ArrangeBlinds part 1: place real output + vInputBlinds.resize(32 * nSigInputs); + + if (!PlaceRealOutputs(vMI, secretColumn, nSigRingSize, setHave, vCoins, vInputBlinds, sError)) { + return false; + } + + // ArrangeBlinds part 2: hiding outputs (no dummy sigs) + if (!PickHidingOutputs(vMI, secretColumn, nSigRingSize, setHave, sError)) + return false; + + std::vector vPubkeyMatrixIndices; + + for (size_t k = 0; k < nSigInputs; ++k) + for (size_t i = 0; i < nSigRingSize; ++i) { + PutVarInt(vPubkeyMatrixIndices, vMI[k][i]); + } + + // Since we don't need the dummy sigs for fee calculation, + // just emplace empty vectors in the proper places. + txin.scriptWitness.stack.emplace_back(vPubkeyMatrixIndices); + + // end ArrangeBlinds + + // if sign + txin.scriptData.stack.emplace_back(33 * nSigInputs); + std::vector &vKeyImages = txin.scriptData.stack[0]; + + if (!GetKeyImage(txin, vMI, secretColumn, sError)) + return false; + + // SetBlinds. Some of these later vectors contain pointers to data in the earlier ones. + size_t nCols = nSigRingSize; + size_t nRows = nSigInputs + 1; + + std::vector vsk(nSigInputs); + std::vector vpsk(nRows); + + std::vector vm(nCols * nRows * 33); + std::vector vCommitments; + vCommitments.reserve(nCols * nSigInputs); + std::vector vpInCommits(nCols * nSigInputs); + std::vector vpBlinds; + + if (!SetBlinds(nSigRingSize, nSigInputs, vMI, vsk, vpsk, vm, vCommitments, + vpInCommits, vpBlinds, vInputBlinds, + secretColumn, sError)) + return false; + + // This deals with the outputs and the tx as a whole + // and probably has to be moved. + /* + uint8_t blindSum[32]; + memset(blindSum, 0, 32); + vpsk[nRows-1] = blindSum; + + txin.scriptWitness.stack.emplace_back((1 + (nSigInputs+1) * nSigRingSize) * 32); // extra element for C, extra row for commitment row + std::vector &vDL = txin.scriptWitness.stack[1]; + + vpBlinds.insert(vpBlinds.end(), vpOutBlinds.begin(), vpOutBlinds.end()); + + if (0 != (rv = secp256k1_prepare_mlsag(&vm[0], blindSum, + vpOutCommits.size(), vpOutCommits.size(), nCols, nRows, + &vpInCommits[0], &vpOutCommits[0], &vpBlinds[0]))) { + sError = strprintf("Failed to prepare mlsag with %d.", rv); + return error("%s: %s", __func__, sError); + } + + + uint256 hashOutputs = txNew.GetOutputsHash(); + if (0 != (rv = secp256k1_generate_mlsag(secp256k1_ctx_blind, &vKeyImages[0], &vDL[0], &vDL[32], + randSeed, hashOutputs.begin(), nCols, nRows, vSecretColumns[l], + &vpsk[0], &vm[0]))) { + sError = strprintf("Failed to generate mlsag with %d.", rv); + return error("%s: %s", __func__, sError); + } + + // Validate the mlsag + if (0 != (rv = secp256k1_verify_mlsag(secp256k1_ctx_blind, hashOutputs.begin(), nCols, + nRows, &vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { + sError = strprintf("Failed to generate mlsag on initial generation %d.", rv); + return error("%s: %s", __func__, sError); + } +*/ + + return true; +} + bool AnonWallet::CreateStealthChangeAccount(AnonWalletDB* wdb) { /** Derive a stealth address that is specifically for change **/ diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 5ac81e0120..13b276e2a2 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -253,6 +253,7 @@ class AnonWallet void GetAllScanKeys(std::vector& vStealthAddresses); bool IsMyPubKey(const CKeyID& keyId); + bool CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize); void LoadToWallet(const uint256 &hash, const CTransactionRecord &rtx); bool LoadTxRecords(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 14604a1bf0..fea86eeb83 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3986,6 +3986,10 @@ bool CWallet::CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nB { return CreateCoinStake(pindexBest, nBits, txNew, nTxNewTime, nComputeTimeStart); } +bool CWallet::CreateRingCTStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart) +{ + return CreateCoinStake(pindexBest, nBits, txNew, nTxNewTime, nComputeTimeStart); +} bool CWallet::SelectStakeCoins(std::list >& listInputs) { @@ -4027,6 +4031,30 @@ bool CWallet::SelectStakeCoins(std::list >& listI return true; } +bool CWallet::SelectStakeCoins(std::list >& listInputs) +{ + LOCK(cs_main); + + std::vector vCoins; + pAnonWalletMain->AvailableAnonCoins(vCoins); + + // TODO: change required depth + int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); + if (chainActive.Height() >= Params().HeightLightZerocoin()) + nRequiredDepth = Params().Zerocoin_RequiredStakeDepthV2(); + + for (auto coin : vCoins) { + if (coin.nDepth >= nRequiredDepth) { + std::unique_ptr input(new RingCTStake(coin)); + listInputs.emplace_back(std::move(input)); + } + } + + LogPrint(BCLog::STAKING, "%s: FOUND %d STAKABLE RINGCT COINS\n", __func__, listInputs.size()); + + return true; +} + /** * Call after CreateTransaction unless you want to abort diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 598d02aaac..2908a1b55f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1101,8 +1101,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface public: bool CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart); + bool CreateRingCTStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart); bool SelectStakeCoins(std::list >& listInputs); + bool SelectStakeCoins(std::list >& listInputs); // sub wallet seeds bool GetZerocoinSeed(CKey& keyZerocoinMaster); From e67ccd2b1e29a33687ad2cdbfc5178653f537727 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 16 Jul 2022 13:25:05 -0700 Subject: [PATCH 04/25] Reduce effective weights of inputs. --- src/veil/proofofstake/stakeinput.cpp | 48 +++++++++++++++++---- src/veil/proofofstake/stakeinput.h | 2 + src/veil/ringct/anonwallet.cpp | 64 +++++++++++++++++++++++++--- src/veil/ringct/anonwallet.h | 4 +- 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 6ffcfe6845..9c7f1941e4 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -180,7 +180,7 @@ CAmount RingCTStake::GetValue() // 11 17592186044417 ( 175921.86044417) 281474976710656 ( 2814749.76710656) // 12 281474976710657 ( 2814749.76710657) 4503599627370496 ( 45035996.27370496) // 13 4503599627370497 ( 45035996.27370497) 72057594037927936 (720575940.37927936) -CAmount RingCTStake::GetWeight() +CAmount RingCTStake::GetBracketMinValue() { CAmount nValueIn = GetValue(); // fast mode @@ -194,6 +194,43 @@ CAmount RingCTStake::GetWeight() return (1 << (4 * bracket)) + nOneSat; } +// We further reduce the weights of higher brackets to match zerocoin reductions. +CAmount RingCTStake::GetWeight() { + CAmount nValueIn = GetValue(); + // fast mode + if (nValueIn <= nBareMinStake) + return 0; + + // bracket is at least 1 now. + int bracket = fast_log16(nValueIn - nOneSat); + // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really + // 1 << (4 + 4 * bracket - 4) + int val = (1 << (4 * bracket)) + nOneSat; + + switch (bracket) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + return val; + case 8: + return (val * 95) / 100; + case 9: + return (val * 91) / 100; + case 10: + return (val * 71) / 100; + case 11: + return (val * 5) / 10; + case 12: + return (val * 3) / 10; + default: + return val / 10; + } +} + bool RingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) { if (!pindexChainPrev) @@ -233,14 +270,7 @@ bool RingCTStake::CreateTxOuts(CWallet* pwallet, std::vector& vpo #ifdef ENABLE_WALLET } - //Create an output returning the RingCT and adding the reward to it - // or two separate rewards - // The former could be an info leak over time when it gets staked again in a different - // bucket. - // The latter is an info leak of the reward... - - - return true; + return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), 32); #endif } diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 3b2428f379..ff5b85f437 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -47,6 +47,8 @@ class RingCTStake : public CStakeInput const COutputR& coin; // Need: depth, COutputRecord (amount) + CAmount GetBracketMinValue(); + public: explicit RingCTStake(const COutputR& coin_) : coin(coin_) { }; diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index fb32938d35..83e225d666 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -3474,8 +3474,7 @@ bool AnonWallet::SetBlinds( bool AnonWallet::SetOutputs( std::vector& vpout, std::vector& vecSend, - CAmount& nFeeRet, - CAmount& nFeeNeeded, + CAmount nFeeRet, size_t nSubtractFeeFromAmount, CAmount& nValueOutPlain, int& nChangePosInOut, @@ -3771,7 +3770,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st nChangePosInOut = -1; bool fFeesFromChange = nMaximumInputs > 0 && nChange >= MIN_FINAL_CHANGE + nFeeRet; - if (!SetOutputs(txNew.vpout, vecSend, nFeeRet, nFeeNeeded, nSubtractFeeFromAmount, + if (!SetOutputs(txNew.vpout, vecSend, nFeeRet, nSubtractFeeFromAmount, nValueOutPlain, nChangePosInOut, fSkipFee, fFeesFromChange, sError)) { return false; } @@ -4099,12 +4098,12 @@ bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) vInputBlinds.resize(32 * nSigInputs); if (!PlaceRealOutputs(vMI, secretColumn, nSigRingSize, setHave, vCoins, vInputBlinds, sError)) { - return false; + return error("%s: %s", __func__, sError); } // ArrangeBlinds part 2: hiding outputs (no dummy sigs) if (!PickHidingOutputs(vMI, secretColumn, nSigRingSize, setHave, sError)) - return false; + return error("%s: %s", __func__, sError); std::vector vPubkeyMatrixIndices; @@ -4124,9 +4123,10 @@ bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) std::vector &vKeyImages = txin.scriptData.stack[0]; if (!GetKeyImage(txin, vMI, secretColumn, sError)) - return false; + return error("%s: %s", __func__, sError); // SetBlinds. Some of these later vectors contain pointers to data in the earlier ones. + // This probably needs to be done in Complete() because these may be needed there as well. size_t nCols = nSigRingSize; size_t nRows = nSigInputs + 1; @@ -4142,7 +4142,7 @@ bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) if (!SetBlinds(nSigRingSize, nSigInputs, vMI, vsk, vpsk, vm, vCommitments, vpInCommits, vpBlinds, vInputBlinds, secretColumn, sError)) - return false; + return error("%s: %s", __func__, sError); // This deals with the outputs and the tx as a whole // and probably has to be moved. @@ -4183,6 +4183,56 @@ bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) return true; } +bool AnonWallet::CreateStakeTxOuts( + const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, size_t nRingSize) +{ + std::string sError; + std::vector vecOut; + + CTempRecipient stakeRet; + stakeRet.nType = OUTPUT_RINGCT; + stakeRet.fChange = true; + stakeRet.address = GetStealthChangeAddress(); + stakeRet.SetAmount(nInput); + // This should set the proof that our stake input is large enough when SetOutputs is called. + stakeRet.fOverwriteRangeProofParams = true; + stakeRet.min_value = (uint64_t)bracketMin; + + CTempRecipient reward; + reward.nType = OUTPUT_RINGCT; + reward.fChange = false; + reward.address = GetStealthChangeAddress(); + reward.SetAmount(nReward); + + vecOut.push_back(stakeRet); + vecOut.push_back(reward); + + // TODO: maybe shuffle the order? + + int nChangePosInOut = -1; + CAmount nValueOutPlain = 0; + + // SetOutputs + if (!SetOutputs(vpout, vecOut, 0, 0, nValueOutPlain, nChangePosInOut, true, false, sError)) { + return error("%s: %s", __func__, sError); + } + + // ArrangeOutBlinds + std::vector vpOutCommits; + std::vector vpOutBlinds; + std::vector vBlindPlain; + secp256k1_pedersen_commitment plainCommitment; + vBlindPlain.resize(32); + memset(&vBlindPlain[0], 0, 32); + + if (!ArrangeOutBlinds(vpout, vecOut, vpOutCommits, vpOutBlinds, vBlindPlain, + &plainCommitment, 0, nChangePosInOut, false, sError)) { + return error("%s: %s", __func__, sError); + } + + return true; +} + bool AnonWallet::CreateStealthChangeAccount(AnonWalletDB* wdb) { /** Derive a stealth address that is specifically for change **/ diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 13b276e2a2..800424de46 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -254,6 +254,7 @@ class AnonWallet bool IsMyPubKey(const CKeyID& keyId); bool CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize); + bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, size_t nRingSize); void LoadToWallet(const uint256 &hash, const CTransactionRecord &rtx); bool LoadTxRecords(); @@ -417,8 +418,7 @@ class AnonWallet bool SetOutputs( std::vector& vpout, std::vector& vecSend, - CAmount& nFeeRet, - CAmount& nFeeNeeded, + CAmount nFeeRet, size_t nSubtractFeeFromAmount, CAmount& nValueOutPlain, int& nChangePosInOut, From b5366b0dff7072132687b65b0ddfba758411b2b7 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 16 Jul 2022 14:51:53 -0700 Subject: [PATCH 05/25] RingCT: SignStakeTx Refactor out the necessary vectors to store separately in the stakeinput, since we need them from both the input blinds and the output blinds. --- CMakeLists.txt | 1 + src/veil/proofofstake/stakeinput.cpp | 18 ++- src/veil/proofofstake/stakeinput.h | 2 + src/veil/ringct/anonwallet.cpp | 139 ++++++++++++------------ src/veil/ringct/anonwallet.h | 6 +- src/veil/ringct/transactionsigcontext.h | 46 ++++++++ 6 files changed, 139 insertions(+), 73 deletions(-) create mode 100644 src/veil/ringct/transactionsigcontext.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 798509245f..e984eba3f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -822,6 +822,7 @@ add_executable(veil src/veil/ringct/rpcanonwallet.h src/veil/ringct/stealth.cpp src/veil/ringct/stealth.h + src/veil/ringct/transactionsigcontext.h src/veil/ringct/types.h src/veil/zerocoin/accumulatormap.cpp src/veil/zerocoin/accumulatormap.h diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 9c7f1941e4..3f9f931709 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -257,7 +257,11 @@ bool RingCTStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) #ifdef ENABLE_WALLET } - return pwallet->GetAnonWallet()->CoinToTxIn(coin, txIn, 32); + uint32_t nSigInputs, nSigRingSize; + txIn.GetAnonInfo(nSigInputs, nSigRingSize); + tx_sig_context = std::make_unique(nSigRingSize, nSigInputs + 1); + + return pwallet->GetAnonWallet()->CoinToTxIn(coin, txIn, *tx_sig_context, 32); #endif } @@ -270,13 +274,21 @@ bool RingCTStake::CreateTxOuts(CWallet* pwallet, std::vector& vpo #ifdef ENABLE_WALLET } - return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), 32); + return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), *tx_sig_context, 32); #endif } bool RingCTStake::CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) { - return false; +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { +#endif + return error("%s: wallet disabled", __func__); +#ifdef ENABLE_WALLET + } + + return pwallet->GetAnonWallet()->SignStakeTx(coin, txNew, *tx_sig_context); +#endif } bool RingCTStake::MarkSpent(AnonWallet* panonwallet, const uint256& txid) diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index ff5b85f437..1704396e1a 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -6,6 +6,7 @@ #ifndef PIVX_STAKEINPUT_H #define PIVX_STAKEINPUT_H +#include "veil/ringct/transactionsigcontext.h" #include "veil/zerocoin/accumulatormap.h" #include "chain.h" #include "streams.h" @@ -46,6 +47,7 @@ class RingCTStake : public CStakeInput private: const COutputR& coin; // Need: depth, COutputRecord (amount) + std::unique_ptr tx_sig_context; CAmount GetBracketMinValue(); diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 83e225d666..62fbc68468 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -4072,7 +4072,9 @@ bool AnonWallet::AddAnonInputs(CWalletTx &wtx, CTransactionRecord &rtx, std::vec return true; } -bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) +bool AnonWallet::CoinToTxIn( + const COutputR& coin, CTxIn& txin, veil_ringct::TransactionSigContext& ctx, + size_t nRingSize) { // Mostly copied from AddAnonInputs_Inner std::string sError; @@ -4092,17 +4094,16 @@ bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) std::vector> vMI; ec_point vInputBlinds; - size_t secretColumn; // ArrangeBlinds part 1: place real output vInputBlinds.resize(32 * nSigInputs); - if (!PlaceRealOutputs(vMI, secretColumn, nSigRingSize, setHave, vCoins, vInputBlinds, sError)) { + if (!PlaceRealOutputs(vMI, ctx.secretColumn, nSigRingSize, setHave, vCoins, vInputBlinds, sError)) { return error("%s: %s", __func__, sError); } // ArrangeBlinds part 2: hiding outputs (no dummy sigs) - if (!PickHidingOutputs(vMI, secretColumn, nSigRingSize, setHave, sError)) + if (!PickHidingOutputs(vMI, ctx.secretColumn, nSigRingSize, setHave, sError)) return error("%s: %s", __func__, sError); std::vector vPubkeyMatrixIndices; @@ -4122,69 +4123,24 @@ bool AnonWallet::CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize) txin.scriptData.stack.emplace_back(33 * nSigInputs); std::vector &vKeyImages = txin.scriptData.stack[0]; - if (!GetKeyImage(txin, vMI, secretColumn, sError)) + if (!GetKeyImage(txin, vMI, ctx.secretColumn, sError)) return error("%s: %s", __func__, sError); - // SetBlinds. Some of these later vectors contain pointers to data in the earlier ones. - // This probably needs to be done in Complete() because these may be needed there as well. - size_t nCols = nSigRingSize; - size_t nRows = nSigInputs + 1; - - std::vector vsk(nSigInputs); - std::vector vpsk(nRows); - - std::vector vm(nCols * nRows * 33); - std::vector vCommitments; - vCommitments.reserve(nCols * nSigInputs); - std::vector vpInCommits(nCols * nSigInputs); - std::vector vpBlinds; - - if (!SetBlinds(nSigRingSize, nSigInputs, vMI, vsk, vpsk, vm, vCommitments, - vpInCommits, vpBlinds, vInputBlinds, - secretColumn, sError)) + // SetBlinds + // TODO: pass ctx instead of its components + if (!SetBlinds(nSigRingSize, nSigInputs, vMI, ctx.vsk, ctx.vpsk, ctx.vm, ctx.vCommitments, + ctx.vpInCommits, ctx.vpBlinds, vInputBlinds, + ctx.secretColumn, sError)) return error("%s: %s", __func__, sError); - // This deals with the outputs and the tx as a whole - // and probably has to be moved. - /* - uint8_t blindSum[32]; - memset(blindSum, 0, 32); - vpsk[nRows-1] = blindSum; - - txin.scriptWitness.stack.emplace_back((1 + (nSigInputs+1) * nSigRingSize) * 32); // extra element for C, extra row for commitment row - std::vector &vDL = txin.scriptWitness.stack[1]; - - vpBlinds.insert(vpBlinds.end(), vpOutBlinds.begin(), vpOutBlinds.end()); - - if (0 != (rv = secp256k1_prepare_mlsag(&vm[0], blindSum, - vpOutCommits.size(), vpOutCommits.size(), nCols, nRows, - &vpInCommits[0], &vpOutCommits[0], &vpBlinds[0]))) { - sError = strprintf("Failed to prepare mlsag with %d.", rv); - return error("%s: %s", __func__, sError); - } - - - uint256 hashOutputs = txNew.GetOutputsHash(); - if (0 != (rv = secp256k1_generate_mlsag(secp256k1_ctx_blind, &vKeyImages[0], &vDL[0], &vDL[32], - randSeed, hashOutputs.begin(), nCols, nRows, vSecretColumns[l], - &vpsk[0], &vm[0]))) { - sError = strprintf("Failed to generate mlsag with %d.", rv); - return error("%s: %s", __func__, sError); - } - - // Validate the mlsag - if (0 != (rv = secp256k1_verify_mlsag(secp256k1_ctx_blind, hashOutputs.begin(), nCols, - nRows, &vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { - sError = strprintf("Failed to generate mlsag on initial generation %d.", rv); - return error("%s: %s", __func__, sError); - } -*/ - return true; } bool AnonWallet::CreateStakeTxOuts( - const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, size_t nRingSize) + const COutputR& coin, std::vector& vpout, + CAmount nInput, CAmount nReward, CAmount bracketMin, + veil_ringct::TransactionSigContext& ctx, + size_t nRingSize) { std::string sError; std::vector vecOut; @@ -4218,21 +4174,68 @@ bool AnonWallet::CreateStakeTxOuts( } // ArrangeOutBlinds - std::vector vpOutCommits; - std::vector vpOutBlinds; - std::vector vBlindPlain; - secp256k1_pedersen_commitment plainCommitment; - vBlindPlain.resize(32); - memset(&vBlindPlain[0], 0, 32); - - if (!ArrangeOutBlinds(vpout, vecOut, vpOutCommits, vpOutBlinds, vBlindPlain, - &plainCommitment, 0, nChangePosInOut, false, sError)) { + if (!ArrangeOutBlinds(vpout, vecOut, ctx.vpOutCommits, ctx.vpOutBlinds, ctx.vBlindPlain, + &ctx.plainCommitment, 0, nChangePosInOut, false, sError)) { return error("%s: %s", __func__, sError); } return true; } +bool AnonWallet::SignStakeTx( + const COutputR& coin, CMutableTransaction& txNew, + veil_ringct::TransactionSigContext& ctx) +{ + std::string sError; + int rv; + + + CTxIn& txin = txNew.vin[0]; + // This deals with the outputs and the tx as a whole. + uint32_t nSigInputs, nSigRingSize; + txin.GetAnonInfo(nSigInputs, nSigRingSize); + size_t nCols = nSigRingSize; + size_t nRows = nSigInputs + 1; + + uint8_t blindSum[32]; + memset(blindSum, 0, 32); + ctx.vpsk[nRows-1] = blindSum; + + txin.scriptWitness.stack.emplace_back((1 + nRows * nCols) * 32); // extra element for C, extra row for commitment row + std::vector &vDL = txin.scriptWitness.stack[1]; + + ctx.vpBlinds.insert(ctx.vpBlinds.end(), ctx.vpOutBlinds.begin(), ctx.vpOutBlinds.end()); + + if (0 != (rv = secp256k1_prepare_mlsag(&ctx.vm[0], blindSum, + ctx.vpOutCommits.size(), ctx.vpOutCommits.size(), nCols, nRows, + &ctx.vpInCommits[0], &ctx.vpOutCommits[0], &ctx.vpBlinds[0]))) { + sError = strprintf("Failed to prepare mlsag with %d.", rv); + return error("%s: %s", __func__, sError); + } + + std::vector& vKeyImages = txin.scriptData.stack[0]; + uint256 hashOutputs = txNew.GetOutputsHash(); + + uint8_t randSeed[32]; + GetStrongRandBytes(randSeed, 32); + if (0 != (rv = secp256k1_generate_mlsag(secp256k1_ctx_blind, &vKeyImages[0], &vDL[0], &vDL[32], + randSeed, hashOutputs.begin(), nCols, nRows, ctx.secretColumn, + &ctx.vpsk[0], &ctx.vm[0]))) { + sError = strprintf("Failed to generate mlsag with %d.", rv); + return error("%s: %s", __func__, sError); + } + + // Validate the mlsag + if (0 != (rv = secp256k1_verify_mlsag(secp256k1_ctx_blind, hashOutputs.begin(), nCols, + nRows, &ctx.vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { + sError = strprintf("Failed to generate mlsag on initial generation %d.", rv); + return error("%s: %s", __func__, sError); + } + + return true; +} + + bool AnonWallet::CreateStealthChangeAccount(AnonWalletDB* wdb) { /** Derive a stealth address that is specifically for change **/ diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 800424de46..b8373dcf7d 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -253,8 +254,9 @@ class AnonWallet void GetAllScanKeys(std::vector& vStealthAddresses); bool IsMyPubKey(const CKeyID& keyId); - bool CoinToTxIn(const COutputR& coin, CTxIn& txin, size_t nRingSize); - bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, size_t nRingSize); + bool CoinToTxIn(const COutputR& coin, CTxIn& txin, veil_ringct::TransactionSigContext& ctx, size_t nRingSize); + bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, veil_ringct::TransactionSigContext& ctx, size_t nRingSize); + bool SignStakeTx(const COutputR& coin, CMutableTransaction& txNew, veil_ringct::TransactionSigContext& ctx); void LoadToWallet(const uint256 &hash, const CTransactionRecord &rtx); bool LoadTxRecords(); diff --git a/src/veil/ringct/transactionsigcontext.h b/src/veil/ringct/transactionsigcontext.h new file mode 100644 index 0000000000..21cbb62e6d --- /dev/null +++ b/src/veil/ringct/transactionsigcontext.h @@ -0,0 +1,46 @@ +// Copyright (c) 2018-2019 Veil developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef VEIL_RINGCT_TRANSACTIONSIGCONTEXT_H +#define VEIL_RINGCT_TRANSACTIONSIGCONTEXT_H + +#include +#include +#include + +#include +#include + +namespace veil_ringct { + +// A wrapper to hold vectors for ringct txn signing. +struct TransactionSigContext { + TransactionSigContext(size_t nCols, size_t nRows) + : vsk(nRows - 1), vpsk(nRows), vm(nCols * nRows * 33), + vpInCommits(nCols * (nRows - 1)), + vBlindPlain(32) + { + vCommitments.reserve(nCols * (nRows - 1)); + } + + size_t secretColumn; + + // SetBlinds + std::vector vsk; + std::vector vpsk; + std::vector vm; + std::vector vCommitments; + std::vector vpInCommits; + std::vector vpBlinds; + + // ArrangeOutBlinds + std::vector vpOutCommits; + std::vector vpOutBlinds; + std::vector vBlindPlain; + secp256k1_pedersen_commitment plainCommitment; +}; + +} // namespace veil_ringct + +#endif // VEIL_RINGCT_TRANSACTIONSIGCONTEXT_H \ No newline at end of file From 3c258cc36ad37aa61d4874b8d9d01b1f28d6e1bb Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 16 Jul 2022 15:53:07 -0700 Subject: [PATCH 06/25] RingCT Stake: mark input as spent --- src/veil/proofofstake/stakeinput.cpp | 23 +++++++++++++++++++---- src/veil/proofofstake/stakeinput.h | 7 +++++-- src/veil/ringct/anonwallet.cpp | 6 +++--- src/veil/ringct/anonwallet.h | 2 +- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 3f9f931709..ab104cf9a5 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -274,7 +274,7 @@ bool RingCTStake::CreateTxOuts(CWallet* pwallet, std::vector& vpo #ifdef ENABLE_WALLET } - return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), *tx_sig_context, 32); + return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), *tx_sig_context, rtx); #endif } @@ -287,13 +287,28 @@ bool RingCTStake::CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) #ifdef ENABLE_WALLET } - return pwallet->GetAnonWallet()->SignStakeTx(coin, txNew, *tx_sig_context); + if (!pwallet->GetAnonWallet()->SignStakeTx(coin, txNew, *tx_sig_context)) + return false; + + if (!MarkSpent(pwallet->GetAnonWallet(), txNew)) + return error("%s: failed to mark ringct input as used\n", __func__); + return true; #endif } -bool RingCTStake::MarkSpent(AnonWallet* panonwallet, const uint256& txid) +bool RingCTStake::MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew) { - return false; + rtx.nFlags |= ORF_ANON_IN; + rtx.vin.emplace_back(txNew.vin[0].prevout); + + std::vector spends; + spends.emplace_back(coin.rtx->first, coin.i); + panonwallet->MarkInputsAsPendingSpend(spends); + + uint256 txid = txNew.GetHash(); + panonwallet->SaveRecord(txid, rtx); + + return true; } ZerocoinStake::ZerocoinStake(const libzerocoin::CoinSpend& spend) diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 1704396e1a..1327706bf0 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -6,6 +6,7 @@ #ifndef PIVX_STAKEINPUT_H #define PIVX_STAKEINPUT_H +#include "veil/ringct/transactionrecord.h" #include "veil/ringct/transactionsigcontext.h" #include "veil/zerocoin/accumulatormap.h" #include "chain.h" @@ -47,12 +48,14 @@ class RingCTStake : public CStakeInput private: const COutputR& coin; // Need: depth, COutputRecord (amount) + std::unique_ptr tx_sig_context; + CTransactionRecord rtx; CAmount GetBracketMinValue(); public: - explicit RingCTStake(const COutputR& coin_) : coin(coin_) { }; + explicit RingCTStake(const COutputR& coin_) : coin(coin_) { } CBlockIndex* GetIndexFrom() override; bool GetTxFrom(CTransaction& tx) override; @@ -63,7 +66,7 @@ class RingCTStake : public CStakeInput bool IsZerocoins() override { return false; } - bool MarkSpent(AnonWallet* panonwallet, const uint256& txid); + bool MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew); bool CompleteTx(AnonWallet* panonwallet, CMutableTransaction& txNew); bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 62fbc68468..98faede185 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -4140,7 +4140,7 @@ bool AnonWallet::CreateStakeTxOuts( const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, veil_ringct::TransactionSigContext& ctx, - size_t nRingSize) + CTransactionRecord& rtx) { std::string sError; std::vector vecOut; @@ -4163,7 +4163,7 @@ bool AnonWallet::CreateStakeTxOuts( vecOut.push_back(stakeRet); vecOut.push_back(reward); - // TODO: maybe shuffle the order? + Shuffle(vecOut.begin(), vecOut.end(), FastRandomContext()); int nChangePosInOut = -1; CAmount nValueOutPlain = 0; @@ -4179,6 +4179,7 @@ bool AnonWallet::CreateStakeTxOuts( return error("%s: %s", __func__, sError); } + AddOutputRecordMetaData(rtx, vecOut); return true; } @@ -4189,7 +4190,6 @@ bool AnonWallet::SignStakeTx( std::string sError; int rv; - CTxIn& txin = txNew.vin[0]; // This deals with the outputs and the tx as a whole. uint32_t nSigInputs, nSigRingSize; diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index b8373dcf7d..4b64cfee03 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -255,7 +255,7 @@ class AnonWallet bool IsMyPubKey(const CKeyID& keyId); bool CoinToTxIn(const COutputR& coin, CTxIn& txin, veil_ringct::TransactionSigContext& ctx, size_t nRingSize); - bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, veil_ringct::TransactionSigContext& ctx, size_t nRingSize); + bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, veil_ringct::TransactionSigContext& ctx, CTransactionRecord& rtx); bool SignStakeTx(const COutputR& coin, CMutableTransaction& txNew, veil_ringct::TransactionSigContext& ctx); void LoadToWallet(const uint256 &hash, const CTransactionRecord &rtx); From ba6ad674735da1a30e5199da56fe7dd7383041d9 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 16 Jul 2022 16:18:29 -0700 Subject: [PATCH 07/25] RingCT: Refactor to isolate input/output handling. And allow static allocation of the context structs. --- src/veil/proofofstake/stakeinput.cpp | 10 ++---- src/veil/proofofstake/stakeinput.h | 10 +++--- src/veil/ringct/anonwallet.cpp | 41 +++++++++++++------------ src/veil/ringct/anonwallet.h | 6 ++-- src/veil/ringct/transactionsigcontext.h | 14 ++++++--- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index ab104cf9a5..4e095eafd1 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -257,11 +257,7 @@ bool RingCTStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) #ifdef ENABLE_WALLET } - uint32_t nSigInputs, nSigRingSize; - txIn.GetAnonInfo(nSigInputs, nSigRingSize); - tx_sig_context = std::make_unique(nSigRingSize, nSigInputs + 1); - - return pwallet->GetAnonWallet()->CoinToTxIn(coin, txIn, *tx_sig_context, 32); + return pwallet->GetAnonWallet()->CoinToTxIn(coin, txIn, tx_inCtx, RING_SIZE); #endif } @@ -274,7 +270,7 @@ bool RingCTStake::CreateTxOuts(CWallet* pwallet, std::vector& vpo #ifdef ENABLE_WALLET } - return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), *tx_sig_context, rtx); + return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), tx_outCtx, rtx); #endif } @@ -287,7 +283,7 @@ bool RingCTStake::CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) #ifdef ENABLE_WALLET } - if (!pwallet->GetAnonWallet()->SignStakeTx(coin, txNew, *tx_sig_context)) + if (!pwallet->GetAnonWallet()->SignStakeTx(coin, txNew, tx_inCtx, tx_outCtx)) return false; if (!MarkSpent(pwallet->GetAnonWallet(), txNew)) diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 1327706bf0..570c816083 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -46,16 +46,17 @@ class CStakeInput class RingCTStake : public CStakeInput { private: - const COutputR& coin; - // Need: depth, COutputRecord (amount) + static const int RING_SIZE = 32; - std::unique_ptr tx_sig_context; + const COutputR& coin; // Contains: depth (coin.i), output record (coin.rtx->second) + veil_ringct::TransactionInputsSigContext tx_inCtx; + veil_ringct::TransactionOutputsSigContext tx_outCtx; CTransactionRecord rtx; CAmount GetBracketMinValue(); public: - explicit RingCTStake(const COutputR& coin_) : coin(coin_) { } + explicit RingCTStake(const COutputR& coin_) : coin(coin_), tx_inCtx(RING_SIZE, 2) { } CBlockIndex* GetIndexFrom() override; bool GetTxFrom(CTransaction& tx) override; @@ -67,7 +68,6 @@ class RingCTStake : public CStakeInput bool IsZerocoins() override { return false; } bool MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew); - bool CompleteTx(AnonWallet* panonwallet, CMutableTransaction& txNew); bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 98faede185..59ebda6e4a 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -4073,7 +4073,7 @@ bool AnonWallet::AddAnonInputs(CWalletTx &wtx, CTransactionRecord &rtx, std::vec } bool AnonWallet::CoinToTxIn( - const COutputR& coin, CTxIn& txin, veil_ringct::TransactionSigContext& ctx, + const COutputR& coin, CTxIn& txin, veil_ringct::TransactionInputsSigContext& inCtx, size_t nRingSize) { // Mostly copied from AddAnonInputs_Inner @@ -4098,12 +4098,12 @@ bool AnonWallet::CoinToTxIn( // ArrangeBlinds part 1: place real output vInputBlinds.resize(32 * nSigInputs); - if (!PlaceRealOutputs(vMI, ctx.secretColumn, nSigRingSize, setHave, vCoins, vInputBlinds, sError)) { + if (!PlaceRealOutputs(vMI, inCtx.secretColumn, nSigRingSize, setHave, vCoins, vInputBlinds, sError)) { return error("%s: %s", __func__, sError); } // ArrangeBlinds part 2: hiding outputs (no dummy sigs) - if (!PickHidingOutputs(vMI, ctx.secretColumn, nSigRingSize, setHave, sError)) + if (!PickHidingOutputs(vMI, inCtx.secretColumn, nSigRingSize, setHave, sError)) return error("%s: %s", __func__, sError); std::vector vPubkeyMatrixIndices; @@ -4123,14 +4123,14 @@ bool AnonWallet::CoinToTxIn( txin.scriptData.stack.emplace_back(33 * nSigInputs); std::vector &vKeyImages = txin.scriptData.stack[0]; - if (!GetKeyImage(txin, vMI, ctx.secretColumn, sError)) + if (!GetKeyImage(txin, vMI, inCtx.secretColumn, sError)) return error("%s: %s", __func__, sError); // SetBlinds - // TODO: pass ctx instead of its components - if (!SetBlinds(nSigRingSize, nSigInputs, vMI, ctx.vsk, ctx.vpsk, ctx.vm, ctx.vCommitments, - ctx.vpInCommits, ctx.vpBlinds, vInputBlinds, - ctx.secretColumn, sError)) + // TODO: pass inCtx instead of its components + if (!SetBlinds(nSigRingSize, nSigInputs, vMI, inCtx.vsk, inCtx.vpsk, inCtx.vm, inCtx.vCommitments, + inCtx.vpInCommits, inCtx.vpBlinds, vInputBlinds, + inCtx.secretColumn, sError)) return error("%s: %s", __func__, sError); return true; @@ -4139,7 +4139,7 @@ bool AnonWallet::CoinToTxIn( bool AnonWallet::CreateStakeTxOuts( const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, - veil_ringct::TransactionSigContext& ctx, + veil_ringct::TransactionOutputsSigContext& outCtx, CTransactionRecord& rtx) { std::string sError; @@ -4174,8 +4174,8 @@ bool AnonWallet::CreateStakeTxOuts( } // ArrangeOutBlinds - if (!ArrangeOutBlinds(vpout, vecOut, ctx.vpOutCommits, ctx.vpOutBlinds, ctx.vBlindPlain, - &ctx.plainCommitment, 0, nChangePosInOut, false, sError)) { + if (!ArrangeOutBlinds(vpout, vecOut, outCtx.vpOutCommits, outCtx.vpOutBlinds, outCtx.vBlindPlain, + &outCtx.plainCommitment, 0, nChangePosInOut, false, sError)) { return error("%s: %s", __func__, sError); } @@ -4185,7 +4185,8 @@ bool AnonWallet::CreateStakeTxOuts( bool AnonWallet::SignStakeTx( const COutputR& coin, CMutableTransaction& txNew, - veil_ringct::TransactionSigContext& ctx) + veil_ringct::TransactionInputsSigContext& inCtx, + veil_ringct::TransactionOutputsSigContext& outCtx) { std::string sError; int rv; @@ -4199,16 +4200,16 @@ bool AnonWallet::SignStakeTx( uint8_t blindSum[32]; memset(blindSum, 0, 32); - ctx.vpsk[nRows-1] = blindSum; + inCtx.vpsk[nRows-1] = blindSum; txin.scriptWitness.stack.emplace_back((1 + nRows * nCols) * 32); // extra element for C, extra row for commitment row std::vector &vDL = txin.scriptWitness.stack[1]; - ctx.vpBlinds.insert(ctx.vpBlinds.end(), ctx.vpOutBlinds.begin(), ctx.vpOutBlinds.end()); + inCtx.vpBlinds.insert(inCtx.vpBlinds.end(), outCtx.vpOutBlinds.begin(), outCtx.vpOutBlinds.end()); - if (0 != (rv = secp256k1_prepare_mlsag(&ctx.vm[0], blindSum, - ctx.vpOutCommits.size(), ctx.vpOutCommits.size(), nCols, nRows, - &ctx.vpInCommits[0], &ctx.vpOutCommits[0], &ctx.vpBlinds[0]))) { + if (0 != (rv = secp256k1_prepare_mlsag(&inCtx.vm[0], blindSum, + outCtx.vpOutCommits.size(), outCtx.vpOutCommits.size(), nCols, nRows, + &inCtx.vpInCommits[0], &outCtx.vpOutCommits[0], &inCtx.vpBlinds[0]))) { sError = strprintf("Failed to prepare mlsag with %d.", rv); return error("%s: %s", __func__, sError); } @@ -4219,15 +4220,15 @@ bool AnonWallet::SignStakeTx( uint8_t randSeed[32]; GetStrongRandBytes(randSeed, 32); if (0 != (rv = secp256k1_generate_mlsag(secp256k1_ctx_blind, &vKeyImages[0], &vDL[0], &vDL[32], - randSeed, hashOutputs.begin(), nCols, nRows, ctx.secretColumn, - &ctx.vpsk[0], &ctx.vm[0]))) { + randSeed, hashOutputs.begin(), nCols, nRows, inCtx.secretColumn, + &inCtx.vpsk[0], &inCtx.vm[0]))) { sError = strprintf("Failed to generate mlsag with %d.", rv); return error("%s: %s", __func__, sError); } // Validate the mlsag if (0 != (rv = secp256k1_verify_mlsag(secp256k1_ctx_blind, hashOutputs.begin(), nCols, - nRows, &ctx.vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { + nRows, &inCtx.vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { sError = strprintf("Failed to generate mlsag on initial generation %d.", rv); return error("%s: %s", __func__, sError); } diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 4b64cfee03..d208d000f7 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -254,9 +254,9 @@ class AnonWallet void GetAllScanKeys(std::vector& vStealthAddresses); bool IsMyPubKey(const CKeyID& keyId); - bool CoinToTxIn(const COutputR& coin, CTxIn& txin, veil_ringct::TransactionSigContext& ctx, size_t nRingSize); - bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, veil_ringct::TransactionSigContext& ctx, CTransactionRecord& rtx); - bool SignStakeTx(const COutputR& coin, CMutableTransaction& txNew, veil_ringct::TransactionSigContext& ctx); + bool CoinToTxIn(const COutputR& coin, CTxIn& txin, veil_ringct::TransactionInputsSigContext& inCtx, size_t nRingSize); + bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, veil_ringct::TransactionOutputsSigContext& outCtx, CTransactionRecord& rtx); + bool SignStakeTx(const COutputR& coin, CMutableTransaction& txNew, veil_ringct::TransactionInputsSigContext& inCtx, veil_ringct::TransactionOutputsSigContext& outCtx); void LoadToWallet(const uint256 &hash, const CTransactionRecord &rtx); bool LoadTxRecords(); diff --git a/src/veil/ringct/transactionsigcontext.h b/src/veil/ringct/transactionsigcontext.h index 21cbb62e6d..fdf4ac600a 100644 --- a/src/veil/ringct/transactionsigcontext.h +++ b/src/veil/ringct/transactionsigcontext.h @@ -14,12 +14,11 @@ namespace veil_ringct { -// A wrapper to hold vectors for ringct txn signing. -struct TransactionSigContext { - TransactionSigContext(size_t nCols, size_t nRows) +// A wrapper to hold vectors for ringct txn signing, for the inputs. +struct TransactionInputsSigContext { + TransactionInputsSigContext(size_t nCols, size_t nRows) : vsk(nRows - 1), vpsk(nRows), vm(nCols * nRows * 33), - vpInCommits(nCols * (nRows - 1)), - vBlindPlain(32) + vpInCommits(nCols * (nRows - 1)) { vCommitments.reserve(nCols * (nRows - 1)); } @@ -33,6 +32,11 @@ struct TransactionSigContext { std::vector vCommitments; std::vector vpInCommits; std::vector vpBlinds; +}; + +// A wrapper to hold vectors for ringct txn signing, for the outputs. +struct TransactionOutputsSigContext { + TransactionOutputsSigContext() : vBlindPlain(32) {} // ArrangeOutBlinds std::vector vpOutCommits; From 66f7e401a045ada40cf1dadae7af2fc69591a5bd Mon Sep 17 00:00:00 2001 From: Zannick Date: Mon, 8 Aug 2022 17:44:00 -0700 Subject: [PATCH 08/25] RingCT staking params and fixes Move COutputR to a common location. Hold a copy of COutputR in RingCTStake for its lifetime. Handle other memory lifetime issues. --- src/chainparams.cpp | 1 + src/chainparams.h | 2 ++ src/miner.cpp | 20 +++++++++++--- src/veil/proofofstake/stakeinput.cpp | 11 +++++--- src/veil/proofofstake/stakeinput.h | 11 +++++--- src/veil/ringct/anonwallet.cpp | 28 ++++++++++--------- src/veil/ringct/anonwallet.h | 23 ---------------- src/veil/ringct/transactionrecord.h | 25 ++++++++++++++++- src/veil/ringct/transactionsigcontext.h | 2 ++ src/wallet/wallet.cpp | 36 ++++++++++++++++++++++--- src/wallet/wallet.h | 1 + 11 files changed, 109 insertions(+), 51 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 254bac98d3..ac3d58a4cb 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -882,6 +882,7 @@ class CRegTestParams : public CChainParams { nHeightLightZerocoin = 500; nZerocoinRequiredStakeDepthV2 = 10; //The required confirmations for a zerocoin to be stakable nHeightEnforceBlacklist = 0; + nHeightRingCTStaking = 300; nMaxHeaderRequestWithoutPoW = 50; nPreferredMintsPerBlock = 70; //Miner will not include more than this many mints per block diff --git a/src/chainparams.h b/src/chainparams.h index 94f2aab527..4cd70a9176 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -147,6 +147,7 @@ class CChainParams int HeightLightZerocoin() const { return nHeightLightZerocoin; } int HeightEnforceBlacklist() const { return nHeightEnforceBlacklist; } int HeightProgPowDAGSizeReduction() const { return nHeightProgPowDAGSizeReduction; } + int HeightRingCTStaking() const { return nHeightRingCTStaking; } uint32_t PowUpdateTimestamp() const { return nPowUpdateTimestamp; } uint64_t KIforkTimestamp() const { return nTimeKIfork; } @@ -236,6 +237,7 @@ class CChainParams int nHeightLightZerocoin; int nHeightEnforceBlacklist; int nHeightProgPowDAGSizeReduction; + int nHeightRingCTStaking = 50000000; //Settings that are not chain critical, but should not be edited unless the person changing understands the consequence int nMaxHeaderRequestWithoutPoW; diff --git a/src/miner.cpp b/src/miner.cpp index 65abd5a725..c595e22062 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -186,11 +186,19 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc uint32_t nTxNewTime = 0; #ifdef ENABLE_WALLET - if (!gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) && pwalletMain->CreateZerocoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) { - pblock->nTime = nTxNewTime; - } else { + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) return nullptr; + + if (!pwalletMain->CreateZerocoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) { + if (!pwalletMain->CreateRingCTStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) + return nullptr; + else { + // output debugging info + return nullptr; + } } + + pblock->nTime = nTxNewTime; #endif } @@ -891,7 +899,7 @@ double GetRecentHashSpeed() { } void BitcoinMiner(std::shared_ptr coinbaseScript, bool fProofOfStake = false, bool fProofOfFullNode = false, ThreadHashSpeed* pThreadHashSpeed = nullptr) { - LogPrintf("Veil Miner started\n"); + LogPrintf("Veil Miner started: stake=%s\n", fProofOfStake); unsigned int nExtraNonce = 0; static const int nInnerLoopCount = 0x010000; @@ -940,6 +948,8 @@ void BitcoinMiner(std::shared_ptr coinbaseScript, bool fProofOfS { nMintableLastCheck = GetTime(); fMintableCoins = pwallet->MintableCoins(); + if (nHeight >= Params().HeightRingCTStaking()) + fMintableCoins |= pwallet->StakeableRingCTCoins(); } bool fNextIter = false; @@ -951,6 +961,8 @@ void BitcoinMiner(std::shared_ptr coinbaseScript, bool fProofOfS { nMintableLastCheck = GetTime(); fMintableCoins = pwallet->MintableCoins(); + if (nHeight >= Params().HeightRingCTStaking()) + fMintableCoins |= pwallet->StakeableRingCTCoins(); } if (!fMintableCoins) fNextIter = true; diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 4e095eafd1..91ccffb400 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -14,8 +14,8 @@ #include "veil/proofofstake/kernel.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" -#endif #include "veil/ringct/anonwallet.h" +#endif // Based on https://stackoverflow.com/a/23000588 int fast_log16(uint64_t value) @@ -98,7 +98,11 @@ bool GetStakeModifier(uint64_t& nStakeModifier, const CBlockIndex& pindexChainPr nHeightPrevious = nHeightSample; auto pindexSample = pindexChainPrev.GetAncestor(nHeightSample); - if (!pindexSample) return false; + if (!pindexSample) { + if (i > 5 && nHeightSample < 0 && (Params().NetworkIDString() == "regtest" || Params().NetworkIDString() == "dev")) + break; + return false; + } //Get a sampling of entropy from this block. Rehash the sample, since PoW hashes may have lots of 0's uint256 hashSample = GetHashFromIndex(pindexSample); hashSample = Hash(hashSample.begin(), hashSample.end()); @@ -139,8 +143,7 @@ CBlockIndex* RingCTStake::GetIndexFrom() return pindexFrom; if (coin.nDepth > 0) - //note that this will be a nullptr if the height DNE - pindexFrom = chainActive[coin.nDepth]; + pindexFrom = LookupBlockIndex(coin.rtx->second.blockHash); return pindexFrom; } diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 570c816083..fcc50ae8a6 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -18,12 +18,11 @@ class AnonWallet; class CKeyStore; class CWallet; class CWalletTx; -class COutputR; class CStakeInput { protected: - CBlockIndex* pindexFrom; + CBlockIndex* pindexFrom = nullptr; libzerocoin::CoinDenomination denom = libzerocoin::CoinDenomination::ZQ_ERROR; public: @@ -46,9 +45,13 @@ class CStakeInput class RingCTStake : public CStakeInput { private: - static const int RING_SIZE = 32; + static const int RING_SIZE = 11; - const COutputR& coin; // Contains: depth (coin.i), output record (coin.rtx->second) + // Contains: depth (coin.nDepth), txo idx (coin.i), output record (coin.rtx->second) + // A copy is required for lifetime. + const COutputR coin; + + // Details for constructing a tx from this stake input. veil_ringct::TransactionInputsSigContext tx_inCtx; veil_ringct::TransactionOutputsSigContext tx_outCtx; CTransactionRecord rtx; diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 59ebda6e4a..54958bbc39 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -3915,7 +3915,6 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st return false; // Do a bunch of math on both inputs and outputs. - // TODO: Refactor into Complete std::vector &vKeyImages = txin.scriptData.stack[0]; @@ -4085,7 +4084,7 @@ bool AnonWallet::CoinToTxIn( txin.SetAnonInfo(1, nRingSize); std::vector> vCoins; - vCoins.emplace_back(coin.rtx, coin.rtx->second.GetOutput(coin.i)->GetAmount()); + vCoins.emplace_back(coin.rtx, coin.i); std::set setHave; // Anon prev-outputs can only be used once per transaction. // Must add real outputs to setHave before picking decoys @@ -4093,12 +4092,11 @@ bool AnonWallet::CoinToTxIn( txin.GetAnonInfo(nSigInputs, nSigRingSize); std::vector> vMI; - ec_point vInputBlinds; // ArrangeBlinds part 1: place real output - vInputBlinds.resize(32 * nSigInputs); + inCtx.vInputBlinds.resize(32 * nSigInputs); - if (!PlaceRealOutputs(vMI, inCtx.secretColumn, nSigRingSize, setHave, vCoins, vInputBlinds, sError)) { + if (!PlaceRealOutputs(vMI, inCtx.secretColumn, nSigRingSize, setHave, vCoins, inCtx.vInputBlinds, sError)) { return error("%s: %s", __func__, sError); } @@ -4117,19 +4115,18 @@ bool AnonWallet::CoinToTxIn( // just emplace empty vectors in the proper places. txin.scriptWitness.stack.emplace_back(vPubkeyMatrixIndices); - // end ArrangeBlinds - // if sign txin.scriptData.stack.emplace_back(33 * nSigInputs); - std::vector &vKeyImages = txin.scriptData.stack[0]; + + // end ArrangeBlinds if (!GetKeyImage(txin, vMI, inCtx.secretColumn, sError)) return error("%s: %s", __func__, sError); // SetBlinds - // TODO: pass inCtx instead of its components + // TODO: pass inCtx instead of its components? if (!SetBlinds(nSigRingSize, nSigInputs, vMI, inCtx.vsk, inCtx.vpsk, inCtx.vm, inCtx.vCommitments, - inCtx.vpInCommits, inCtx.vpBlinds, vInputBlinds, + inCtx.vpInCommits, inCtx.vpBlinds, inCtx.vInputBlinds, inCtx.secretColumn, sError)) return error("%s: %s", __func__, sError); @@ -4153,6 +4150,8 @@ bool AnonWallet::CreateStakeTxOuts( // This should set the proof that our stake input is large enough when SetOutputs is called. stakeRet.fOverwriteRangeProofParams = true; stakeRet.min_value = (uint64_t)bracketMin; + stakeRet.ct_exponent = 2; + stakeRet.ct_bits = 32; CTempRecipient reward; reward.nType = OUTPUT_RINGCT; @@ -4165,6 +4164,9 @@ bool AnonWallet::CreateStakeTxOuts( Shuffle(vecOut.begin(), vecOut.end(), FastRandomContext()); + if (!ExpandTempRecipients(vecOut, sError)) + return false; + int nChangePosInOut = -1; CAmount nValueOutPlain = 0; @@ -4198,12 +4200,15 @@ bool AnonWallet::SignStakeTx( size_t nCols = nSigRingSize; size_t nRows = nSigInputs + 1; + std::vector& vKeyImages = txin.scriptData.stack[0]; uint8_t blindSum[32]; memset(blindSum, 0, 32); inCtx.vpsk[nRows-1] = blindSum; - txin.scriptWitness.stack.emplace_back((1 + nRows * nCols) * 32); // extra element for C, extra row for commitment row + size_t vDLsize = (1 + nRows * nCols) * 32; // extra element for C, extra row for commitment row + txin.scriptWitness.stack.emplace_back(vDLsize); // creates the vector with |vDLsize| capacity std::vector &vDL = txin.scriptWitness.stack[1]; + vDL.resize(vDLsize); // creates |vDLsize| elements inCtx.vpBlinds.insert(inCtx.vpBlinds.end(), outCtx.vpOutBlinds.begin(), outCtx.vpOutBlinds.end()); @@ -4214,7 +4219,6 @@ bool AnonWallet::SignStakeTx( return error("%s: %s", __func__, sError); } - std::vector& vKeyImages = txin.scriptData.stack[0]; uint256 hashOutputs = txNew.GetOutputsHash(); uint8_t randSeed[32]; diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index d208d000f7..a75d336f4d 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -24,7 +24,6 @@ typedef std::map ExtKeyAccountMap; typedef std::map ExtKeyMap; typedef std::map MapWallet_t; -typedef std::map MapRecords_t; typedef std::multimap::iterator> RtxOrdered_t; @@ -33,28 +32,6 @@ class CWatchOnlyTx; const uint16_t OR_PLACEHOLDER_N = 0xFFFF; // index of a fake output to contain reconstructed amounts for txns with undecodeable outputs -class COutputR -{ -public: - COutputR() {}; - - COutputR(const uint256 &txhash_, MapRecords_t::const_iterator rtx_, int i_, int nDepth_, - bool fSpendable_, bool fSolvable_, bool fSafe_, bool fMature_, bool fNeedHardwareKey_) - : txhash(txhash_), rtx(rtx_), i(i_), nDepth(nDepth_), - fSpendable(fSpendable_), fSolvable(fSolvable_), fSafe(fSafe_), fMature(fMature_), fNeedHardwareKey(fNeedHardwareKey_) {}; - - uint256 txhash; - MapRecords_t::const_iterator rtx; - int i; - int nDepth; - bool fSpendable; - bool fSolvable; - bool fSafe; - bool fMature; - bool fNeedHardwareKey; -}; - - class CStoredTransaction { public: diff --git a/src/veil/ringct/transactionrecord.h b/src/veil/ringct/transactionrecord.h index 17939cb0cf..0d36a7009a 100644 --- a/src/veil/ringct/transactionrecord.h +++ b/src/veil/ringct/transactionrecord.h @@ -36,7 +36,7 @@ class CTransactionRecord // Stored by uint256 txnHash; public: CTransactionRecord() : - nFlags(0), nIndex(0), nBlockTime(0) , nTimeReceived(0) , nFee(0) {}; + nFlags(0), nIndex(0), nBlockTime(0) , nTimeReceived(0) , nFee(0), vin(), vout() {}; // Conflicted state is marked by set blockHash and nIndex -1 @@ -196,4 +196,27 @@ class CTransactionRecord }; }; +typedef std::map MapRecords_t; + +class COutputR +{ +public: + COutputR() {}; + + COutputR(const uint256 &txhash_, MapRecords_t::const_iterator rtx_, int i_, int nDepth_, + bool fSpendable_, bool fSolvable_, bool fSafe_, bool fMature_, bool fNeedHardwareKey_) + : txhash(txhash_), rtx(rtx_), i(i_), nDepth(nDepth_), + fSpendable(fSpendable_), fSolvable(fSolvable_), fSafe(fSafe_), fMature(fMature_), fNeedHardwareKey(fNeedHardwareKey_) {}; + + uint256 txhash; + MapRecords_t::const_iterator rtx; + int i; + int nDepth; + bool fSpendable; + bool fSolvable; + bool fSafe; + bool fMature; + bool fNeedHardwareKey; +}; + #endif //VEIL_TRANSACTIONRECORD_H diff --git a/src/veil/ringct/transactionsigcontext.h b/src/veil/ringct/transactionsigcontext.h index fdf4ac600a..903c60a946 100644 --- a/src/veil/ringct/transactionsigcontext.h +++ b/src/veil/ringct/transactionsigcontext.h @@ -11,6 +11,7 @@ #include #include +#include "veil/ringct/types.h" namespace veil_ringct { @@ -31,6 +32,7 @@ struct TransactionInputsSigContext { std::vector vm; std::vector vCommitments; std::vector vpInCommits; + ec_point vInputBlinds; std::vector vpBlinds; }; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fea86eeb83..74b57f6686 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3969,7 +3969,7 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, } txNew.vin.emplace_back(in); - //Mark mints as spent + //Mark input as spent if (!stakeInput->CompleteTx(this, txNew)) return false; @@ -4031,12 +4031,42 @@ bool CWallet::SelectStakeCoins(std::list >& listI return true; } +bool CWallet::StakeableRingCTCoins() +{ + LOCK2(cs_main, cs_wallet); + + // TODO: change required depth + int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); + if (chainActive.Height() >= Params().HeightLightZerocoin()) + nRequiredDepth = Params().Zerocoin_RequiredStakeDepthV2(); + + BalanceList bal; + pAnonWalletMain->GetBalances(bal); + + // zero coin + if (bal.nRingCT > 0) { + std::vector vCoins; + CCoinControl coincontrol; + pAnonWalletMain->AvailableAnonCoins(vCoins, true, &coincontrol); + LogPrintf("I have %d available ringct coins\n", vCoins.size()); + + for (auto coin : vCoins) { + if (coin.nDepth >= nRequiredDepth) { + return true; + } + } + } + + return false; +} + bool CWallet::SelectStakeCoins(std::list >& listInputs) { LOCK(cs_main); std::vector vCoins; - pAnonWalletMain->AvailableAnonCoins(vCoins); + CCoinControl coincontrol; + pAnonWalletMain->AvailableAnonCoins(vCoins, true, &coincontrol); // TODO: change required depth int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); @@ -4046,7 +4076,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp for (auto coin : vCoins) { if (coin.nDepth >= nRequiredDepth) { std::unique_ptr input(new RingCTStake(coin)); - listInputs.emplace_back(std::move(input)); + listInputs.push_back(std::move(input)); } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 2908a1b55f..5138254a2c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1105,6 +1105,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface bool SelectStakeCoins(std::list >& listInputs); bool SelectStakeCoins(std::list >& listInputs); + bool StakeableRingCTCoins(); // sub wallet seeds bool GetZerocoinSeed(CKey& keyZerocoinMaster); From d9ebfd24c067a9f3a655c7d5c56eecc3194f2cfd Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 13 Aug 2022 11:23:13 -0700 Subject: [PATCH 09/25] Fix weight calculation. --- src/veil/proofofstake/stakeinput.cpp | 2 +- src/veil/ringct/anonwallet.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 91ccffb400..55e6e421c9 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -208,7 +208,7 @@ CAmount RingCTStake::GetWeight() { int bracket = fast_log16(nValueIn - nOneSat); // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really // 1 << (4 + 4 * bracket - 4) - int val = (1 << (4 * bracket)) + nOneSat; + CAmount val = (1L << (4 * bracket)) + nOneSat; switch (bracket) { case 1: diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 54958bbc39..fb435c37c1 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -4232,7 +4232,7 @@ bool AnonWallet::SignStakeTx( // Validate the mlsag if (0 != (rv = secp256k1_verify_mlsag(secp256k1_ctx_blind, hashOutputs.begin(), nCols, - nRows, &inCtx.vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { + nRows, &inCtx.vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { sError = strprintf("Failed to generate mlsag on initial generation %d.", rv); return error("%s: %s", __func__, sError); } From e588f976c603f91924c7c0974d5621a3a2eb7382 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sun, 14 Aug 2022 18:29:39 -0700 Subject: [PATCH 10/25] More attempted fixes --- src/Makefile.am | 1 + src/veil/ringct/anonwallet.cpp | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 9beb294329..057c10d81d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -216,6 +216,7 @@ BITCOIN_CORE_H = \ veil/ringct/stealth.h \ veil/ringct/temprecipient.h \ veil/ringct/transactionrecord.h \ + veil/ringct/transactionsigcontext.h \ veil/ringct/types.h \ veil/ringct/watchonlydb.h \ veil/ringct/watchonly.h \ diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index fb435c37c1..d4f8bb76b4 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -1621,8 +1621,8 @@ bool AnonWallet::AddCTData(CTxOutBase *txout, CTempRecipient &r, std::string &sE if (r.fOverwriteRangeProofParams == true) { min_value = r.min_value; - ct_exponent = r.ct_exponent; - ct_bits = r.ct_bits; + ct_exponent = r.ct_exponent || ct_exponent; + ct_bits = r.ct_bits || ct_bits; } if (1 != secp256k1_rangeproof_sign(secp256k1_ctx_blind, @@ -4150,8 +4150,8 @@ bool AnonWallet::CreateStakeTxOuts( // This should set the proof that our stake input is large enough when SetOutputs is called. stakeRet.fOverwriteRangeProofParams = true; stakeRet.min_value = (uint64_t)bracketMin; - stakeRet.ct_exponent = 2; - stakeRet.ct_bits = 32; + stakeRet.ct_exponent = 0; + stakeRet.ct_bits = 0; CTempRecipient reward; reward.nType = OUTPUT_RINGCT; @@ -4177,7 +4177,14 @@ bool AnonWallet::CreateStakeTxOuts( // ArrangeOutBlinds if (!ArrangeOutBlinds(vpout, vecOut, outCtx.vpOutCommits, outCtx.vpOutBlinds, outCtx.vBlindPlain, - &outCtx.plainCommitment, 0, nChangePosInOut, false, sError)) { + &outCtx.plainCommitment, nValueOutPlain, nChangePosInOut, false, sError)) { + return error("%s: %s", __func__, sError); + } + + std::vector &vData = ((CTxOutData*)vpout[1].get())->vData; + vData.resize(1); + if (0 != PutVarInt(vData, nValueOutPlain)) { + sError = strprintf("Failed to add zero fee to stake transaction."); return error("%s: %s", __func__, sError); } From ce5daefc92dfaa0fcd38f8656dbda8f42c2b63b9 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sun, 28 Aug 2022 13:58:03 -0700 Subject: [PATCH 11/25] checkpoint some more attempts for ringct --- src/veil/ringct/anonwallet.cpp | 97 ++++++++++++++++++++----- src/veil/ringct/anonwallet.h | 4 + src/veil/ringct/transactionsigcontext.h | 2 + src/wallet/wallet.cpp | 1 + 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index d4f8bb76b4..be3ea34833 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -162,6 +162,20 @@ bool AnonWallet::Initialise(CExtKey* pExtMaster) return error("%s: Failed to write stealth change address to wallet db", __func__); } + idStakeAccount.SetNull(); + if (!wdb.ReadNamedExtKeyId("stealthstake", idStakeAccount)) { + //If the stake account is not created yet, then create it + if (!IsLocked() && !CreateStealthStakeAccount(&wdb)) + return error("%s: failed to create stealth stake account ", __func__); + } + //Load the stake address + if (!idStakeAccount.IsNull() && !wdb.ReadNamedExtKeyId("stealthstakeaddress", idStakeAddress)) { + auto address = GetStealthStakeAddress(); + idStakeAddress = address.GetID(); + if (!wdb.WriteNamedExtKeyId("stealthstakeaddress", idStakeAddress)) + return error("%s: Failed to write stealth stake address to wallet db", __func__); + } + // Load all accounts, keys, stealth addresses from db if (!LoadAccountCounters()) return error("%s: failed to read account counters from db", __func__); @@ -4091,45 +4105,43 @@ bool AnonWallet::CoinToTxIn( uint32_t nSigInputs, nSigRingSize; txin.GetAnonInfo(nSigInputs, nSigRingSize); - std::vector> vMI; - // ArrangeBlinds part 1: place real output inCtx.vInputBlinds.resize(32 * nSigInputs); - if (!PlaceRealOutputs(vMI, inCtx.secretColumn, nSigRingSize, setHave, vCoins, inCtx.vInputBlinds, sError)) { + if (!PlaceRealOutputs(inCtx.vMI, inCtx.secretColumn, nSigRingSize, setHave, vCoins, inCtx.vInputBlinds, sError)) { return error("%s: %s", __func__, sError); } // ArrangeBlinds part 2: hiding outputs (no dummy sigs) - if (!PickHidingOutputs(vMI, inCtx.secretColumn, nSigRingSize, setHave, sError)) + if (!PickHidingOutputs(inCtx.vMI, inCtx.secretColumn, nSigRingSize, setHave, sError)) return error("%s: %s", __func__, sError); - std::vector vPubkeyMatrixIndices; for (size_t k = 0; k < nSigInputs; ++k) for (size_t i = 0; i < nSigRingSize; ++i) { - PutVarInt(vPubkeyMatrixIndices, vMI[k][i]); + PutVarInt(inCtx.vPubkeyMatrixIndices, inCtx.vMI[k][i]); } // Since we don't need the dummy sigs for fee calculation, // just emplace empty vectors in the proper places. - txin.scriptWitness.stack.emplace_back(vPubkeyMatrixIndices); + txin.scriptWitness.stack.emplace_back(inCtx.vPubkeyMatrixIndices); // if sign txin.scriptData.stack.emplace_back(33 * nSigInputs); // end ArrangeBlinds - if (!GetKeyImage(txin, vMI, inCtx.secretColumn, sError)) + if (!GetKeyImage(txin, inCtx.vMI, inCtx.secretColumn, sError)) return error("%s: %s", __func__, sError); // SetBlinds // TODO: pass inCtx instead of its components? - if (!SetBlinds(nSigRingSize, nSigInputs, vMI, inCtx.vsk, inCtx.vpsk, inCtx.vm, inCtx.vCommitments, + if (!SetBlinds(nSigRingSize, nSigInputs, inCtx.vMI, inCtx.vsk, inCtx.vpsk, inCtx.vm, inCtx.vCommitments, inCtx.vpInCommits, inCtx.vpBlinds, inCtx.vInputBlinds, inCtx.secretColumn, sError)) return error("%s: %s", __func__, sError); + LogPrintf("Stake: set up tx ins\n"); return true; } @@ -4144,30 +4156,34 @@ bool AnonWallet::CreateStakeTxOuts( CTempRecipient stakeRet; stakeRet.nType = OUTPUT_RINGCT; - stakeRet.fChange = true; - stakeRet.address = GetStealthChangeAddress(); + stakeRet.fChange = false; + stakeRet.address = GetStealthStakeAddress(); stakeRet.SetAmount(nInput); + stakeRet.isMine = true; + stakeRet.fExemptFeeSub = true; // This should set the proof that our stake input is large enough when SetOutputs is called. stakeRet.fOverwriteRangeProofParams = true; stakeRet.min_value = (uint64_t)bracketMin; stakeRet.ct_exponent = 0; stakeRet.ct_bits = 0; + int nChangePosInOut = -1; + CTempRecipient reward; reward.nType = OUTPUT_RINGCT; reward.fChange = false; - reward.address = GetStealthChangeAddress(); + reward.fExemptFeeSub = true; + reward.address = GetStealthStakeAddress(); reward.SetAmount(nReward); - vecOut.push_back(stakeRet); - vecOut.push_back(reward); + vecOut.push_back(stakeRet); // already inserted via InsertChangeAddress + //vecOut.push_back(reward); Shuffle(vecOut.begin(), vecOut.end(), FastRandomContext()); if (!ExpandTempRecipients(vecOut, sError)) return false; - int nChangePosInOut = -1; CAmount nValueOutPlain = 0; // SetOutputs @@ -4177,18 +4193,19 @@ bool AnonWallet::CreateStakeTxOuts( // ArrangeOutBlinds if (!ArrangeOutBlinds(vpout, vecOut, outCtx.vpOutCommits, outCtx.vpOutBlinds, outCtx.vBlindPlain, - &outCtx.plainCommitment, nValueOutPlain, nChangePosInOut, false, sError)) { + &outCtx.plainCommitment, nValueOutPlain, -1, false, sError)) { return error("%s: %s", __func__, sError); } std::vector &vData = ((CTxOutData*)vpout[1].get())->vData; vData.resize(1); - if (0 != PutVarInt(vData, nValueOutPlain)) { - sError = strprintf("Failed to add zero fee to stake transaction."); + if (0 != PutVarInt(vData, 0)) { + sError = strprintf("Failed to add fee to stake transaction."); return error("%s: %s", __func__, sError); } AddOutputRecordMetaData(rtx, vecOut); + LogPrintf("Stake: set up tx outs\n"); return true; } @@ -4237,6 +4254,9 @@ bool AnonWallet::SignStakeTx( return error("%s: %s", __func__, sError); } + CTransaction tx(txNew); + LogPrintf("Signed stake tx: %s\n", tx.ToString()); + // Validate the mlsag if (0 != (rv = secp256k1_verify_mlsag(secp256k1_ctx_blind, hashOutputs.begin(), nCols, nRows, &inCtx.vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { @@ -4287,6 +4307,45 @@ CStealthAddress AnonWallet::GetStealthChangeAddress() return mapStealthAddresses.at(idChangeAddress); } +bool AnonWallet::CreateStealthStakeAccount(AnonWalletDB* wdb) +{ + /** Derive a stealth address that is specifically for stake **/ + BIP32Path vPathStealthStake; + vPathStealthStake.emplace_back(4, true); + CExtKey keyStealthStake = DeriveKeyFromPath(*pkeyMaster, vPathStealthStake); + idStakeAccount = keyStealthStake.key.GetPubKey().GetID(); + mapKeyPaths.emplace(idStakeAccount, std::make_pair(idMaster, vPathStealthStake)); + mapAccountCounter.emplace(idStakeAccount, 1); + if (!wdb->WriteExtKey(idMaster, idStakeAccount, vPathStealthStake)) + return false; + if (!wdb->WriteAccountCounter(idStakeAccount, (uint32_t)1)) + return false; + if (!wdb->WriteNamedExtKeyId("stealthstake", idStakeAccount)) + return false; + + //Make the stake address + GetStealthStakeAddress(); + + if (!wdb->WriteNamedExtKeyId("stealthstakeaddress", idStakeAddress)) + return false; + + return true; +} + +CStealthAddress AnonWallet::GetStealthStakeAddress() +{ + if (!mapStealthAddresses.count(idStakeAddress)) { + //Make sure idStakeAccount is loaded + if (!mapAccountCounter.count(idStakeAddress)) + mapAccountCounter.emplace(idStakeAddress, 0); + + CStealthAddress address; + NewStealthKey(address, 0 , nullptr, &idStakeAccount); + idStakeAddress = address.GetID(); + } + return mapStealthAddresses.at(idStakeAddress); +} + bool AnonWallet::MakeDefaultAccount(const CExtKey& extKeyMaster) { LogPrintf("%s: Generating new default account.\n", __func__); @@ -4330,10 +4389,12 @@ bool AnonWallet::MakeDefaultAccount(const CExtKey& extKeyMaster) /** Derive a stealth address that is specifically for change **/ CreateStealthChangeAccount(&wdb); + CreateStealthStakeAccount(&wdb); LogPrintf("%s: Default account %s\n", __func__, idDefaultAccount.GetHex()); LogPrintf("%s: Stealth account %s\n", __func__, idStealthAccount.GetHex()); LogPrintf("%s: Stealth Change account %s\n", __func__, idChangeAccount.GetHex()); + LogPrintf("%s: Stealth Stake account %s\n", __func__, idStakeAccount.GetHex()); LogPrintf("%s: Master account %s\n", __func__, idMaster.GetHex()); return true; diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index a75d336f4d..9f38bc5582 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -93,6 +93,8 @@ class AnonWallet CKeyID idStealthAccount; CKeyID idChangeAccount; CKeyID idChangeAddress; + CKeyID idStakeAccount; + CKeyID idStakeAddress; typedef std::multimap TxSpends; TxSpends mapTxSpends; @@ -244,6 +246,7 @@ class AnonWallet bool MakeDefaultAccount(const CExtKey& extKeyMaster); bool CreateStealthChangeAccount(AnonWalletDB* wdb); + bool CreateStealthStakeAccount(AnonWalletDB* wdb); bool SetMasterKey(const CExtKey& keyMasterIn); bool UnlockWallet(const CExtKey& keyMasterIn, bool fRescan = true); bool LoadAccountCounters(); @@ -263,6 +266,7 @@ class AnonWallet bool NewStealthKey(CStealthAddress& stealthAddress, uint32_t nPrefixBits, const char *pPrefix, CKeyID* paccount = nullptr); CStealthAddress GetStealthChangeAddress(); + CStealthAddress GetStealthStakeAddress(); /** * Insert additional inputs into the transaction by diff --git a/src/veil/ringct/transactionsigcontext.h b/src/veil/ringct/transactionsigcontext.h index 903c60a946..e67b643967 100644 --- a/src/veil/ringct/transactionsigcontext.h +++ b/src/veil/ringct/transactionsigcontext.h @@ -26,6 +26,8 @@ struct TransactionInputsSigContext { size_t secretColumn; + std::vector> vMI; + std::vector vPubkeyMatrixIndices; // SetBlinds std::vector vsk; std::vector vpsk; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 74b57f6686..f6fa8e128d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5614,6 +5614,7 @@ std::shared_ptr CWallet::CreateWalletFromFile(const std::string& name, throw std::runtime_error(strprintf("%s: could not get anon wallet seed on wallet load", __func__)); AnonWallet* anonwallet = walletInstance->GetAnonWallet(); assert(anonwallet->UnlockWallet(extMasterAnon)); + assert(anonwallet->Initialise()); } return walletInstance; From f14b4c3e65fe072345d1a42d66eb115890da82a8 Mon Sep 17 00:00:00 2001 From: Zannick Date: Fri, 21 Oct 2022 17:09:49 -0700 Subject: [PATCH 12/25] RingCT stake: add reward outputs. --- src/primitives/transaction.cpp | 2 +- src/veil/proofofstake/stakeinput.cpp | 83 ++++++++++++++++++++++++++++ src/veil/proofofstake/stakeinput.h | 4 ++ src/veil/ringct/anonwallet.cpp | 56 +++++++++++++++++-- src/veil/ringct/transactionrecord.h | 2 + src/wallet/coincontrol.h | 3 + src/wallet/wallet.cpp | 36 ++---------- 7 files changed, 149 insertions(+), 37 deletions(-) diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 7f913d1ca4..80f8184c8d 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -306,7 +306,7 @@ bool CTransaction::IsCoinStake() const if (vin.empty()) return false; - if (vin.size() != 1 || !vin[0].IsZerocoinSpend()) + if (vin.size() != 1 || !(vin[0].IsZerocoinSpend() || vin[0].IsAnonInput())) return false; // the coin stake transaction is marked with the first output empty diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 55e6e421c9..5b5d495c42 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -8,11 +8,13 @@ #include "veil/zerocoin/mintmeta.h" #include "chain.h" #include "chainparams.h" +#include "consensus/consensus.h" #include "wallet/deterministicmint.h" #include "validation.h" #include "stakeinput.h" #include "veil/proofofstake/kernel.h" #ifdef ENABLE_WALLET +#include "wallet/coincontrol.h" #include "wallet/wallet.h" #include "veil/ringct/anonwallet.h" #endif @@ -310,6 +312,53 @@ bool RingCTStake::MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew) return true; } +/** + * @brief Create a coinstake transaction from the stake candidate. + * + * @note Call CreateCoinStake() after finding a valid stake kernel. A kernel can be found without needing to create the full transaction. + * + * @param[in] pwallet: The CWallet that holds the AnonWallet that holds the RingCT output that is being staked. + * @return true upon success. + * false if the AnonWallet fails to find the StakeAddress or if AddAnonInputs() fails. + */ +bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMutableTransaction& txCoinStake, bool& retryable) +{ + AnonWallet* panonWallet = pwallet->GetAnonWallet(); + CTransactionRef ptx = MakeTransactionRef(); + CWalletTx wtx(pwallet, ptx); + + //Add the input to coincontrol so that addanoninputs knows what to use + CCoinControl coinControl; + coinControl.Select(coin.GetOutpoint(), GetValue()); + coinControl.nCoinType = OUTPUT_RINGCT; + coinControl.fProofOfStake = true; + coinControl.nStakeReward = nReward; + + //Tell the rct code who the recipient is + std::vector vecSend; + CTempRecipient tempRecipient; + tempRecipient.nType = OUTPUT_RINGCT; + tempRecipient.SetAmount(GetValue()); + tempRecipient.address = panonWallet->GetStealthStakeAddress(); + tempRecipient.fSubtractFeeFromAmount = false; + tempRecipient.fExemptFeeSub = true; + tempRecipient.fOverwriteRangeProofParams = true; + tempRecipient.min_value = GetBracketMinValue(); + vecSend.emplace_back(tempRecipient); + + std::string strError; + CTransactionRecord rtx; + CAmount nFeeRet = 0; + retryable = false; + if (!panonWallet->AddAnonInputs( + wtx, rtx, vecSend, /*fSign*/true, + /*nRingSize*/Params().DefaultRingSize(), /*nInputsPerSig*/32, + /*nMaximumInputs*/0, nFeeRet, &coinControl, strError)) + return error("%s: AddAnonInputs failed with error %s", __func__, strError); + + return true; +} + ZerocoinStake::ZerocoinStake(const libzerocoin::CoinSpend& spend) { this->nChecksum = spend.getAccumulatorChecksum(); @@ -534,3 +583,37 @@ bool ZerocoinStake::MarkSpent(CWallet *pwallet, const uint256& txid) #endif } +bool ZerocoinStake::CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) { + if (!CreateTxOuts(pwallet, txCoinStake.vpout, nBlockReward)) { + LogPrintf("%s : failed to get scriptPubKey\n", __func__); + retryable = true; + return false; + } + + // Limit size + unsigned int nBytes = ::GetSerializeSize(txCoinStake, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR; + + if (nBytes >= MAX_BLOCK_WEIGHT / 5) { + retryable = false; + return error("CreateCoinStake : exceeded coinstake size limit"); + } + + uint256 hashTxOut = txCoinStake.GetOutputsHash(); + txCoinStake.vin.emplace_back(); + { + if (!CreateTxIn(pwallet, txCoinStake.vin[0], hashTxOut)) { + LogPrintf("%s : failed to create TxIn\n", __func__); + txCoinStake.vin.clear(); + txCoinStake.vpout.clear(); + retryable = true; + return false; + } + } + + //Mark input as spent + if (!CompleteTx(pwallet, txCoinStake)) { + retryable = false; + return false; + } + return true; +} \ No newline at end of file diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index fcc50ae8a6..670ae33755 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -39,6 +39,7 @@ class CStakeInput virtual bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) = 0; virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) = 0; virtual bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) = 0; + virtual bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) = 0; }; @@ -75,6 +76,8 @@ class RingCTStake : public CStakeInput bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; + + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) override; }; @@ -108,6 +111,7 @@ class ZerocoinStake : public CStakeInput bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) override; bool IsZerocoins() override { return true; } bool MarkSpent(CWallet* pwallet, const uint256& txid); diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index be3ea34833..5f3c3ccd26 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -43,6 +43,7 @@ #include #include +#include #include #include @@ -3496,10 +3497,12 @@ bool AnonWallet::SetOutputs( bool fFeesFromChange, std::string& sError) { - OUTPUT_PTR outFee = MAKE_OUTPUT(); - outFee->vData.push_back(DO_FEE); - outFee->vData.resize(9); // More bytes than varint fee could use - vpout.push_back(outFee); + if (vpout.empty()) { + OUTPUT_PTR outFee = MAKE_OUTPUT(); + outFee->vData.push_back(DO_FEE); + outFee->vData.resize(9); // More bytes than varint fee could use + vpout.push_back(outFee); + } bool fFirst = true; for (size_t i = 0; i < vecSend.size(); ++i) { @@ -3613,6 +3616,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st return error("%s: %s", __func__, sError); } + bool fProofOfStake = coinControl->fProofOfStake; nFeeRet = 0; CAmount nValueOutAnon; size_t nSubtractFeeFromAmount; @@ -3637,7 +3641,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st r.fExemptFeeSub = true; } } - fSkipFee = fZerocoinInputs && nZerocoinMintOuts == 0; + fSkipFee = fProofOfStake || (fZerocoinInputs && nZerocoinMintOuts == 0); //If output is going out as zerocoin, then it is being double counted and needs to be subtracted nValueOutAnon -= nValueOutZerocoin; @@ -3998,7 +4002,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } vpBlinds.pop_back(); - }; + } uint256 hashOutputs = txNew.GetOutputsHash(); @@ -4020,6 +4024,46 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } } + + if (fProofOfStake) { + // Add reward outputs + std::vector rewards(2); + rewards[0].address = rewards[1].address = vecSend[0].address; + rewards[0].nType = rewards[1].nType = OUTPUT_RINGCT; + rewards[0].fSubtractFeeFromAmount = rewards[1].fSubtractFeeFromAmount = false; + rewards[0].fExemptFeeSub = rewards[1].fExemptFeeSub = true; + rewards[0].SetAmount(GetRandInt(coinControl->nStakeReward - 1)); + rewards[1].SetAmount(coinControl->nStakeReward - rewards[0].nAmount); + if (!ExpandTempRecipients(rewards, sError)) + return false; + if (!SetOutputs(txNew.vpout, rewards, nFeeRet, nSubtractFeeFromAmount, + nValueOutPlain, nChangePosInOut, true, false, sError)) { + return false; + } + std::vector vpOutCommits; + std::vector vpOutBlinds; + std::vector vBlindPlain(32, 0); + secp256k1_pedersen_commitment plainCommitment; + // Add 3 blinds: reward total as plain output, two rewards that sum to total + nValueOutPlain = coinControl->nStakeReward; + if (!ArrangeOutBlinds(txNew.vpout, rewards, vpOutCommits, vpOutBlinds, vBlindPlain, + &plainCommitment, nValueOutPlain, nChangePosInOut, fCTOut, sError)) { + return false; + } + + // compute the blind sum of the rewards to equal the total + if (!secp256k1_pedersen_blind_sum(secp256k1_ctx_blind, &rewards[1].vBlind[0], &vpOutBlinds[0], vpOutBlinds.size(), 1)) { + wserrorN(1, sError, __func__, "secp256k1_pedersen_blind_sum failed."); + return false; + } + + CTxOutBase *pout = (CTxOutBase*)txNew.vpout[rewards[1].n].get(); + if (!AddCTData(pout, rewards[1], sError)) { + return false; // sError will be set + } + vecSend.insert(vecSend.end(), rewards.begin(), rewards.end()); + } + rtx.nFee = nFeeRet; rtx.nFlags |= ORF_ANON_IN; AddOutputRecordMetaData(rtx, vecSend); diff --git a/src/veil/ringct/transactionrecord.h b/src/veil/ringct/transactionrecord.h index 0d36a7009a..7a8b50bcb2 100644 --- a/src/veil/ringct/transactionrecord.h +++ b/src/veil/ringct/transactionrecord.h @@ -217,6 +217,8 @@ class COutputR bool fSafe; bool fMature; bool fNeedHardwareKey; + + COutPoint GetOutpoint() const { return COutPoint(txhash, i); } }; #endif //VEIL_TRANSACTIONRECORD_H diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index bf7811baa0..d2dc74f7d7 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -58,6 +58,9 @@ class CCoinControl mutable int nChangePos = -1; bool m_addChangeOutput = true; + bool fProofOfStake = false; + CAmount nStakeReward; + CCoinControl() { SetNull(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f6fa8e128d..5e407f9642 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3887,7 +3887,6 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, UninterruptibleSleep(std::chrono::milliseconds{2500}); CScript scriptPubKeyKernel; - bool fKernelFound = false; for (std::unique_ptr& stakeInput : listInputs) { CAmount nCredit = 0; // Make sure the wallet is unlocked and shutdown hasn't been requested @@ -3946,40 +3945,17 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, txNew.vpout.clear(); txNew.vpout.emplace_back(CTxOut(0, scriptEmpty).GetSharedPtr()); - if (!stakeInput->CreateTxOuts(this, txNew.vpout, nBlockReward)) { - LogPrintf("%s : failed to get scriptPubKey\n", __func__); - continue; - } - - // Limit size - unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR; - - if (nBytes >= MAX_BLOCK_WEIGHT / 5) - return error("CreateCoinStake : exceeded coinstake size limit"); - - uint256 hashTxOut = txNew.GetOutputsHash(); - CTxIn in; - { - if (!stakeInput->CreateTxIn(this, in, hashTxOut)) { - LogPrintf("%s : failed to create TxIn\n", __func__); - txNew.vin.clear(); - txNew.vpout.clear(); + bool retryable = true; + if (!stakeInput->CreateCoinStake(this, nBlockReward, txNew, retryable)) { + if (retryable) continue; - } - } - txNew.vin.emplace_back(in); - - //Mark input as spent - if (!stakeInput->CompleteTx(this, txNew)) return false; + } - fKernelFound = true; - break; + return true; } - if (fKernelFound) - break; // if kernel is found stop searching } - return fKernelFound; + return false; } bool CWallet::CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart) From d971e6c93c594862c0f3026d5ae653a659b6e2e6 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 22 Oct 2022 21:11:06 -0700 Subject: [PATCH 13/25] RingCT Stake: Sign block. --- src/miner.cpp | 40 +++++++++++++++++----------- src/primitives/transaction.cpp | 9 +++++++ src/primitives/transaction.h | 1 + src/veil/proofofstake/stakeinput.cpp | 3 ++- src/veil/ringct/anonwallet.cpp | 22 ++++++++++----- src/veil/ringct/anonwallet.h | 1 + 6 files changed, 53 insertions(+), 23 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index c595e22062..db781b8b66 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -518,27 +518,37 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc //Sign block if this is a proof of stake block if (fProofOfStake) { - if (!pblock->vtx[1]->IsZerocoinSpend()) { - error("%s: invalid block created. Stake is not zerocoinspend!", __func__); - return nullptr; - } - auto spend = TxInToZerocoinSpend(pblock->vtx[1]->vin[0]); - if (!spend) { - LogPrint(BCLog::STAKING, "%s: failed to get spend for txin", __func__); - return nullptr; - } + CKey key; + if (pblock->vtx[1]->IsZerocoinSpend()) { + auto spend = TxInToZerocoinSpend(pblock->vtx[1]->vin[0]); + if (!spend) { + LogPrint(BCLog::STAKING, "%s: failed to get spend for txin\n", __func__); + return nullptr; + } - auto bnSerial = spend->getCoinSerialNumber(); + auto bnSerial = spend->getCoinSerialNumber(); - CKey key; #ifdef ENABLE_WALLET - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) || !pwalletMain->GetZerocoinKey(bnSerial, key)) { + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) || !pwalletMain->GetZerocoinKey(bnSerial, key)) { #endif - LogPrint(BCLog::STAKING, "%s: Failed to get zerocoin key from wallet!\n", __func__); - return nullptr; + LogPrint(BCLog::STAKING, "%s: Failed to get zerocoin key from wallet!\n", __func__); + return nullptr; #ifdef ENABLE_WALLET - } + } #endif + } else if (pblock->vtx[1]->IsRingCtSpend()) { + // TODO: enable_wallet guards. + COutPoint prevout; + COutputRecord record; + CKeyID keyid; + if (!pwalletMain->GetAnonWallet()->IsMyAnonInput(pblock->vtx[1]->vin[0], prevout, key)) { + LogPrint(BCLog::STAKING, "Failed to get key from anon spend\n"); + return nullptr; + } + } else { + error("%s: invalid block created. Stake is neither zerocoin nor anon spend!", __func__); + return nullptr; + } if (!key.Sign(pblock->GetHash(), pblock->vchBlockSig)) { LogPrint(BCLog::STAKING, "%s: Failed to sign block hash\n", __func__); diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 80f8184c8d..e1d9519e57 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -340,6 +340,15 @@ bool CTransaction::IsZerocoinMint() const return false; } +bool CTransaction::IsRingCtSpend() const +{ + for (const CTxIn& in : vin) { + if (in.IsAnonInput()) + return true; + } + return false; +} + bool CTransaction::IsZerocoinSpend() const { for (const CTxIn& in : vin) { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index c4aaaf28b5..8c197f2158 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -816,6 +816,7 @@ class CTransaction bool IsCoinStake() const; + bool IsRingCtSpend() const; bool IsZerocoinSpend() const; bool IsZerocoinMint() const; diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 5b5d495c42..bedb436295 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -324,7 +324,7 @@ bool RingCTStake::MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew) bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMutableTransaction& txCoinStake, bool& retryable) { AnonWallet* panonWallet = pwallet->GetAnonWallet(); - CTransactionRef ptx = MakeTransactionRef(); + CTransactionRef ptx = MakeTransactionRef(txCoinStake); CWalletTx wtx(pwallet, ptx); //Add the input to coincontrol so that addanoninputs knows what to use @@ -356,6 +356,7 @@ bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMut /*nMaximumInputs*/0, nFeeRet, &coinControl, strError)) return error("%s: AddAnonInputs failed with error %s", __func__, strError); + txCoinStake = CMutableTransaction(*wtx.tx); return true; } diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 5f3c3ccd26..c475f9f5ba 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -865,7 +865,7 @@ bool AnonWallet::GetOutputRecord(const COutPoint& outpoint, COutputRecord& recor auto& r = mapRecords.at(outpoint.hash); auto precord = r.GetOutput(outpoint.n); if (!precord) - return error("%s: Could no locate output record for tx %s n=%d. FIXME", __func__, outpoint.hash.GetHex(), outpoint.n); + return error("%s: Could not locate output record for tx %s n=%d. FIXME", __func__, outpoint.hash.GetHex(), outpoint.n); record = *precord; return true; } @@ -3261,7 +3261,7 @@ bool AnonWallet::GetRandomHidingOutputs(size_t nInputSize, size_t nRingSize, std } /**Compute whether an anon input's key images belongs to us**/ -bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) +bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint, CKey& myKey) { uint32_t nInputs, nRingSize; txin.GetAnonInfo(nInputs, nRingSize); @@ -3308,6 +3308,7 @@ bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) if (vchKeyImage == image) { myOutpoint = ao.outpoint; + myKey = key; return true; } @@ -3317,6 +3318,10 @@ bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) return false; } +bool AnonWallet::IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint) { + CKey key; + return IsMyAnonInput(txin, myOutpoint, key); +} bool AnonWallet::ArrangeBlinds( std::vector& vin, @@ -3497,7 +3502,7 @@ bool AnonWallet::SetOutputs( bool fFeesFromChange, std::string& sError) { - if (vpout.empty()) { + if (vpout.size() < 2) { OUTPUT_PTR outFee = MAKE_OUTPUT(); outFee->vData.push_back(DO_FEE); outFee->vData.resize(9); // More bytes than varint fee could use @@ -3652,7 +3657,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st wtx.BindWallet(pwalletParent.get()); wtx.fFromMe = true; CMutableTransaction txNew; - if (fZerocoinInputs) { + if (fZerocoinInputs || fProofOfStake) { txNew = CMutableTransaction(*wtx.tx); } @@ -3672,6 +3677,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st CAmount nValueOutPlain = 0; int nChangePosInOut = -1; + size_t nDataIndex = fProofOfStake ? 1 : 0; std::vector > > vMI; std::vector > vInputBlinds; @@ -3696,7 +3702,10 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st if (!fAlreadyHaveInputs) txNew.vin.clear(); - txNew.vpout.clear(); + if (fProofOfStake) + txNew.vpout.resize(1); + else + txNew.vpout.clear(); wtx.fFromMe = true; CAmount nValueToSelect = nValueOutAnon + nValueOutZerocoin; @@ -3894,7 +3903,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } //Add actual fee to CT Fee output - std::vector &vData = ((CTxOutData*)txNew.vpout[0].get())->vData; + std::vector &vData = ((CTxOutData*)txNew.vpout[nDataIndex].get())->vData; vData.resize(1); if (0 != PutVarInt(vData, nFeeRet)) { sError = strprintf("Failed to add fee to transaction."); @@ -4024,7 +4033,6 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } } - if (fProofOfStake) { // Add reward outputs std::vector rewards(2); diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 9f38bc5582..1600b27ff9 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -222,6 +222,7 @@ class AnonWallet bool IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint); + bool IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint, CKey& key); bool AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, std::vector &vecSend, bool sign, size_t nRingSize, size_t nInputsPerSig, size_t nMaximumInputs, CAmount &nFeeRet, const CCoinControl *coinControl, std::string &sError, bool fZerocoinInputs, CAmount nInputValue); From a4b064fc7962adb12d2c2b09a3b1c1cabaf50875 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sun, 23 Oct 2022 15:02:53 -0700 Subject: [PATCH 14/25] RingCT: Correctly mark pending spends. Add a helper class for resetting pending spends. --- src/veil/ringct/anonwallet.cpp | 90 ++++++++++++++++++++++++---------- src/veil/ringct/anonwallet.h | 25 ++++++++++ 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index c475f9f5ba..3ff75b95e7 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -122,6 +122,10 @@ const COutputRecord *CTransactionRecord::GetChangeOutput() const return nullptr; }; +CPendingSpend::~CPendingSpend() { + if (!success) + wallet.DeletePendingTx(txhash); +} int AnonWallet::Finalise() { @@ -4076,15 +4080,11 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st rtx.nFlags |= ORF_ANON_IN; AddOutputRecordMetaData(rtx, vecSend); - for (auto txin : txNew.vin) - rtx.vin.emplace_back(txin.prevout); - // Convert the real inputs (setCoins) into COutPoints that we can mark as pending spends - std::vector spends; - spends.reserve(setCoins.size()); - std::transform(setCoins.begin(), setCoins.end(), std::back_inserter(spends), + rtx.vin.reserve(setCoins.size()); + std::transform(setCoins.begin(), setCoins.end(), std::back_inserter(rtx.vin), [](std::pair coin) -> COutPoint { return COutPoint(coin.first->first, coin.second); }); - MarkInputsAsPendingSpend(spends); + MarkInputsAsPendingSpend(rtx.vin); uint256 txid = txNew.GetHash(); if (nValueOutZerocoin) @@ -5489,6 +5489,60 @@ void AnonWallet::MarkOutputSpent(const COutPoint& outpoint, bool isSpent) SaveRecord(outpoint.hash, record); } +std::unique_ptr AnonWallet::GetPendingSpendForTx(uint256 txid) { + int nHeightTx = 0; + bool isTransactionInChain = IsTransactionInChain(txid, nHeightTx, Params().GetConsensus()); + if (isTransactionInChain) + return nullptr; + + return std::unique_ptr(new CPendingSpend(*this, txid)); +} + +void AnonWallet::InternalResetSpent(AnonWalletDB& wdb, CTransactionRecord* txrecord) { + AssertLockHeld(pwalletParent->cs_wallet); + AssertLockHeld(cs_main); + + for (auto input : txrecord->vin) { + //If the input is marked as spent because of this tx, then unmark + auto mi_2 = mapRecords.find(input.hash); + if (mi_2 != mapRecords.end()) { + CTransactionRecord* txrecord_input = &mi_2->second; + COutputRecord* outrecord = txrecord_input->GetOutput(input.n); + + //not sure why this would ever happen + if (!outrecord) + continue; + + //Assume the spentness is from this, should do a full chain rescan after? //todo + outrecord->MarkSpent(false); + outrecord->MarkPendingSpend(false); + + wdb.WriteTxRecord(input.hash, *txrecord_input); + LogPrintf("%s: Marking %s as unspent\n", __func__, input.ToString()); + } + } +} + +void AnonWallet::DeletePendingTx(uint256 txid) { + LOCK(cs_main); + LOCK(pwalletParent->cs_wallet); + AnonWalletDB wdb(*walletDatabase); + + auto mi = mapRecords.find(txid); + if (mi == mapRecords.end()) { + LogPrintf("ERROR: %s: Unable to find pending transaction to delete.\n", __func__); + return; + } + CTransactionRecord* txrecord = &mi->second; + + InternalResetSpent(wdb, txrecord); + + mapRecords.erase(txid); + mapLockedRecords.erase(txid); + wdb.EraseTxRecord(txid); + pwalletParent->NotifyTransactionChanged(pwalletParent.get(), txid, CT_DELETED); +} + bool KeyIdFromScriptPubKey(const CScript& script, CKeyID& id) { CTxDestination dest; @@ -5516,7 +5570,7 @@ void AnonWallet::RescanWallet() std::set setErase; - auto scanTransaction = [&](auto && mi) { + auto scanTransaction = [&](auto && mi) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwalletParent->cs_wallet) { uint256 txid = mi->first; CTransactionRecord* txrecord = &mi->second; @@ -5526,25 +5580,7 @@ void AnonWallet::RescanWallet() //This particular transaction never made it into the chain. If it is a certain amount of time old, delete it. if (GetTime() - txrecord->nTimeReceived > 60*20) { //20 minutes too old? - for (auto input : txrecord->vin) { - //If the input is marked as spent because of this tx, then unmark - auto mi_2 = mapRecords.find(input.hash); - if (mi_2 != mapRecords.end()) { - CTransactionRecord* txrecord_input = &mi_2->second; - COutputRecord* outrecord = txrecord_input->GetOutput(input.n); - - //not sure why this would ever happen - if (!outrecord) - continue; - - //Assume the spentness is from this, should do a full chain rescan after? //todo - outrecord->MarkSpent(false); - outrecord->MarkPendingSpend(false); - - wdb.WriteTxRecord(input.hash, *txrecord_input); - LogPrintf("%s: Marking %s as unspent\n", __func__, input.ToString()); - } - } + InternalResetSpent(wdb, txrecord); setErase.emplace(txid); return; } diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 1600b27ff9..c0d63e3f11 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -6,6 +6,8 @@ #ifndef PARTICL_WALLET_HDWALLET_H #define PARTICL_WALLET_HDWALLET_H +#include + #include #include #include @@ -77,6 +79,23 @@ class CStoredTransaction }; }; +// A class that holds reference to a pending tx, so it can mark inputs +// as no longer pending spend and delete the transaction if the transaction is not +// broadcast successfully. +class CPendingSpend +{ +public: + CPendingSpend(AnonWallet& wallet, uint256 txhash) : wallet(wallet), txhash(txhash) {} + ~CPendingSpend(); + + void SetSuccess(bool s) { success = s; } + +private: + AnonWallet& wallet; + uint256 txhash; + bool success; +}; + class AnonWallet { std::shared_ptr walletDatabase; @@ -294,6 +313,9 @@ class AnonWallet bool AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate); void MarkOutputSpent(const COutPoint& outpoint, bool isSpent); + std::unique_ptr GetPendingSpendForTx(uint256 txid); + void DeletePendingTx(uint256 txid); + enum RescanWalletType { RESCAN_WALLET_FULL, RESCAN_WALLET_LOCKED_ONLY, @@ -421,6 +443,9 @@ class AnonWallet bool fCTOut, std::string& sError); + void InternalResetSpent(AnonWalletDB& wdb, CTransactionRecord* txrecord) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwalletParent->cs_wallet); + template bool werror(std::string fmt, Params... parameters) const { return error(("%s " + fmt).c_str(), GetDisplayName(), parameters...); From 3b423c6af2a2afbe5ee2e25646311c52d5362b91 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sun, 23 Oct 2022 15:03:46 -0700 Subject: [PATCH 15/25] RingCT Stake: verify block signature and unmark pending spends if the block is not created. --- src/miner.cpp | 15 +++--- src/veil/proofofstake/blockvalidation.cpp | 65 +++++++++++++++++++---- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index db781b8b66..fb37317f29 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -33,6 +33,8 @@ #include #include +#include +#include #include #include @@ -178,6 +180,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LOCK(cs_mapblockindex); pindexPrev = mapBlockIndex.at(hashBest); } + std::unique_ptr pendingRCTStake; if (fProofOfStake && pindexPrev->nHeight + 1 >= Params().HeightPoSStart()) { //POS block - one coinbase is null then non null coinstake //POW block - one coinbase that is not null @@ -192,10 +195,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (!pwalletMain->CreateZerocoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) { if (!pwalletMain->CreateRingCTStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) return nullptr; - else { - // output debugging info - return nullptr; - } + pendingRCTStake = pwalletMain->GetAnonWallet()->GetPendingSpendForTx(txCoinStake.GetHash()); } pblock->nTime = nTxNewTime; @@ -539,8 +539,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc } else if (pblock->vtx[1]->IsRingCtSpend()) { // TODO: enable_wallet guards. COutPoint prevout; - COutputRecord record; - CKeyID keyid; + // Get keyimage from the input if (!pwalletMain->GetAnonWallet()->IsMyAnonInput(pblock->vtx[1]->vin[0], prevout, key)) { LogPrint(BCLog::STAKING, "Failed to get key from anon spend\n"); return nullptr; @@ -560,6 +559,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (pindexPrev && pindexPrev != chainActive.Tip()) { error("%s: stale tip.", __func__); pblocktemplate->nFlags |= TF_STAILTIP; + if (pendingRCTStake) + pendingRCTStake->SetSuccess(true); return std::move(pblocktemplate); } @@ -580,6 +581,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc } pblocktemplate->nFlags = TF_SUCCESS; + if (pendingRCTStake) + pendingRCTStake->SetSuccess(true); return std::move(pblocktemplate); } diff --git a/src/veil/proofofstake/blockvalidation.cpp b/src/veil/proofofstake/blockvalidation.cpp index a8b15981ba..46611a9d05 100644 --- a/src/veil/proofofstake/blockvalidation.cpp +++ b/src/veil/proofofstake/blockvalidation.cpp @@ -4,9 +4,12 @@ #include "blockvalidation.h" +#include "chainparams.h" #include "primitives/block.h" #include "primitives/transaction.h" #include "util/system.h" +#include "validation.h" +#include "veil/ringct/rctindex.h" #include "veil/zerocoin/zchain.h" namespace veil { @@ -16,19 +19,63 @@ bool ValidateBlockSignature(const CBlock& block) if (block.IsProofOfWork()) return true; - if (block.vtx.size() < 2 || block.vtx[1]->vin.empty() || !block.vtx[1]->vin[0].IsZerocoinSpend()) + if (block.vtx.size() < 2 || block.vtx[1]->vin.empty()) return error("%s: Block transaction structure is not compatible with Veil's Proof of Stake validation", __func__); - //Get the zerocoin that was staked - auto spend = TxInToZerocoinSpend(block.vtx[1]->vin[0]); - if (!spend) - return error("%s: failed to get spend from txin", __func__); - auto pubkey = spend->getPubKey(); + //Get the coin that was staked + if (block.vtx[1]->vin[0].IsZerocoinSpend()) { + auto spend = TxInToZerocoinSpend(block.vtx[1]->vin[0]); + if (!spend) + return error("%s: failed to get spend from txin", __func__); + CPubKey pubkey = spend->getPubKey(); - if (!pubkey.IsValid()) - return error("%s: Public Key from zerocoin stake is not valid", __func__); + if (!pubkey.IsValid()) + return error("%s: Public Key from zerocoin stake is not valid", __func__); - return pubkey.Verify(block.GetHash(), block.vchBlockSig); + return pubkey.Verify(block.GetHash(), block.vchBlockSig); + } else if (block.vtx[1]->vin[0].IsAnonInput()) { + if (block.nHeight < Params().HeightRingCTStaking()) { + return error("%s: RingCT staking not accepted before height %d", + __func__, Params().HeightRingCTStaking()); + } + CTxIn txin = block.vtx[1]->vin[0]; + const std::vector &vKeyImages = txin.scriptData.stack[0]; + const std::vector vMI = txin.scriptWitness.stack[0]; + + uint32_t nInputs, nRingSize; + txin.GetAnonInfo(nInputs, nRingSize); + size_t nCols = nRingSize; + size_t nRows = nInputs + 1; + + if (vKeyImages.size() != nInputs * 33) + return error("%s: bad keyimage size", __func__); + + const CCmpPubKey &ki = *((CCmpPubKey *) &vKeyImages[0]); + + size_t ofs = 0, nB = 0; + for (size_t i = 0; i < nCols; ++i) { + int64_t nIndex = 0; + + if (0 != GetVarInt(vMI, ofs, (uint64_t &) nIndex, nB)) + return false; + ofs += nB; + + CAnonOutput ao; + if (!pblocktree->ReadRCTOutput(nIndex, ao)) + return false; + + CPubKey pubkey; + pubkey.Set(ao.pubkey.begin(), ao.pubkey.end()); + if (!pubkey.IsValid()) + return error("%s: public key from ringct stake is not valid"); + + if (pubkey.Verify(block.GetHash(), block.vchBlockSig)) + return true; + } + return error("%s: No pubkeys verified for RingCT stake", __func__); + } + + return error("%s: Stake transaction is not zerocoin or ringct spend", __func__); } } From 727d150e39297b47313e98b3288b537746da6804 Mon Sep 17 00:00:00 2001 From: Zannick Date: Fri, 28 Oct 2022 15:52:54 -0700 Subject: [PATCH 16/25] RingCT stake: Move rewards to coinbase. - Provide the txCoinbase to the CoinStake object for filling out further if necessary. (Originally, so that we could reference them in the coinstake, but this won't be necessary if we make them ringct to begin with. Still, it's easier to have in one place.) - Fix two misc error() calls that were broken. - Use blind_sum and verify_tally to confirm that the anon coin rewards sum to the expected number. --- src/consensus/tx_verify.cpp | 2 +- src/miner.cpp | 167 +++++++++++++++------------ src/miner.h | 3 + src/validation.cpp | 93 +++++++++------ src/veil/proofofstake/kernel.cpp | 23 ++-- src/veil/proofofstake/stakeinput.cpp | 11 +- src/veil/proofofstake/stakeinput.h | 6 +- src/veil/ringct/anon.cpp | 39 ++++++- src/veil/ringct/anon.h | 1 + src/veil/ringct/anonwallet.cpp | 142 +++++++++++++++-------- src/veil/ringct/anonwallet.h | 3 + src/wallet/wallet.cpp | 13 ++- src/wallet/wallet.h | 6 +- 13 files changed, 329 insertions(+), 180 deletions(-) diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 1dfe6744ad..4c4b636be8 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -563,7 +563,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c } else { // Return stake reward in nTxFee txfee = nPlainValueOut - nValueIn; - if (nCt > 0 || nRingCT > 0) { // counters track both outputs and inputs + if (nCt > 0) { // counters track both outputs and inputs return state.DoS(100, error("ConnectBlock(): non-standard elements in coinstake"), REJECT_INVALID, "bad-coinstake-outputs"); } diff --git a/src/miner.cpp b/src/miner.cpp index fb37317f29..01dda27cd1 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -130,6 +130,84 @@ void BlockAssembler::resetBlock() nFees = 0; } +void BlockAssembler::CreateCoinbase( + CMutableTransaction& coinbaseTx, + const CScript& scriptPubKeyIn, bool fProofOfStake, + CAmount nNetworkRewardReserve) +{ + CAmount nNetworkReward = nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : nNetworkRewardReserve; + + coinbaseTx.vin.resize(1); + coinbaseTx.vin[0].prevout.SetNull(); + + CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment; + veil::Budget().GetBlockRewards(nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); + + // Budget + founder = stake ? 3 : 4 + // Budget alone = stake ? 2 : 3 + int outSize = nBudgetPayment > 0 && nFounderPayment > 0 + ? (fProofOfStake ? 3 : 4) + : nBudgetPayment > 0 + ? (fProofOfStake ? 2 : 3) + : 1; + + // ringct PoS needs additional slots, that it will append later + coinbaseTx.vpout.reserve(fProofOfStake ? outSize + 2 : outSize); + coinbaseTx.vpout.resize(outSize); + coinbaseTx.vpout[0] = MAKE_OUTPUT(); + + if (!fProofOfStake) { + //Miner gets the block reward and any network reward + CAmount nMinerReward = nBlockReward + nNetworkReward; + OUTPUT_PTR outCoinbase = MAKE_OUTPUT(); + outCoinbase->scriptPubKey = scriptPubKeyIn; + outCoinbase->nValue = nMinerReward; + coinbaseTx.vpout[0] = std::move(outCoinbase); + } + + // Budget Payment + if (nBudgetPayment) { + std::string strBudgetAddress = veil::Budget().GetBudgetAddress(nHeight); // KeyID for now + CBitcoinAddress addressFounder(strBudgetAddress); + assert(addressFounder.IsValid()); + CTxDestination dest = DecodeDestination(strBudgetAddress); + auto budgetScript = GetScriptForDestination(dest); + + OUTPUT_PTR outBudget = MAKE_OUTPUT(); + outBudget->scriptPubKey = budgetScript; + outBudget->nValue = nBudgetPayment; + coinbaseTx.vpout[fProofOfStake ? 0 : 1] = (std::move(outBudget)); + + std::string strFoundationAddress = veil::Budget().GetFoundationAddress(nHeight); // KeyID for now + CTxDestination destFoundation = DecodeDestination(strFoundationAddress); + auto foundationScript = GetScriptForDestination(destFoundation); + + OUTPUT_PTR outFoundation = MAKE_OUTPUT(); + outFoundation->scriptPubKey = foundationScript; + outFoundation->nValue = nFoundationPayment; + coinbaseTx.vpout[fProofOfStake ? 1 : 2] = (std::move(outFoundation)); + + std::string strFounderAddress = veil::Budget().GetFounderAddress(); // KeyID for now + CTxDestination destFounder = DecodeDestination(strFounderAddress); + auto founderScript = GetScriptForDestination(destFounder); + + if (nFounderPayment) { // Founder payment will eventually hit 0 + OUTPUT_PTR outFounder = MAKE_OUTPUT(); + outFounder->scriptPubKey = founderScript; + outFounder->nValue = nFounderPayment; + coinbaseTx.vpout[fProofOfStake ? 2 : 3] = (std::move(outFounder)); + } + } + + //Must add the height to the coinbase scriptsig + coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; + + if (fProofOfStake && !nBudgetPayment) { + coinbaseTx.vpout[0]->SetValue(0); + coinbaseTx.vpout[0]->SetScriptPubKey(CScript()); + } +} + std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx, bool fProofOfStake, bool fProofOfFullNode, int nPoWType) { int64_t nTimeStart = GetTimeMicros(); @@ -180,9 +258,19 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LOCK(cs_mapblockindex); pindexPrev = mapBlockIndex.at(hashBest); } + + assert(pindexPrev != nullptr); + // Needs to be set for CreateCoinbase + nHeight = pindexPrev->nHeight + 1; + CAmount nNetworkRewardReserve = pindexPrev ? pindexPrev->nNetworkRewardReserve : 0; + + // This is done first as the coinstake txn may need it. + CMutableTransaction coinbaseTx; + CreateCoinbase(coinbaseTx, scriptPubKeyIn, fProofOfStake, nNetworkRewardReserve); + std::unique_ptr pendingRCTStake; - if (fProofOfStake && pindexPrev->nHeight + 1 >= Params().HeightPoSStart()) { - //POS block - one coinbase is null then non null coinstake + if (fProofOfStake && nHeight >= Params().HeightPoSStart()) { + //POS block - one coinbase is null/ringct then non null coinstake //POW block - one coinbase that is not null pblock->nTime = GetAdjustedTime(); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus(), true, pblock->PowType()); @@ -192,8 +280,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) return nullptr; - if (!pwalletMain->CreateZerocoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) { - if (!pwalletMain->CreateRingCTStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart)) + if (!pwalletMain->CreateZerocoinStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart, coinbaseTx)) { + if (nHeight < Params().HeightRingCTStaking() || !pwalletMain->CreateRingCTStake(pindexPrev, pblock->nBits, txCoinStake, nTxNewTime, nComputeTimeStart, coinbaseTx)) return nullptr; pendingRCTStake = pwalletMain->GetAnonWallet()->GetPendingSpendForTx(txCoinStake.GetHash()); } @@ -204,9 +292,6 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LOCK(cs_main); - assert(pindexPrev != nullptr); - nHeight = pindexPrev->nHeight + 1; - // Get the time before Computing the block version if (!fProofOfStake) { pblock->nTime = GetAdjustedTime(); @@ -255,7 +340,6 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc nLastBlockTx = nBlockTx; nLastBlockWeight = nBlockWeight; - CAmount nNetworkRewardReserve = pindexPrev ? pindexPrev->nNetworkRewardReserve : 0; std::string strRewardAddress = Params().NetworkRewardAddress(); CTxDestination rewardDest = DecodeDestination(strRewardAddress); CScript rewardScript = GetScriptForDestination(rewardDest); @@ -398,77 +482,10 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc } pblock->vtx = vtxReplace; - CAmount nNetworkReward = nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : nNetworkRewardReserve; - - //! Create coinbase transaction. - CMutableTransaction coinbaseTx; - coinbaseTx.vin.resize(1); - coinbaseTx.vin[0].prevout.SetNull(); - - CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment; - veil::Budget().GetBlockRewards(nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); - if (nBudgetPayment > 0 && nFounderPayment > 0) - coinbaseTx.vpout.resize(fProofOfStake ? 3 : 4); - else if (nBudgetPayment > 0) - coinbaseTx.vpout.resize(fProofOfStake ? 2 : 3); - else { - coinbaseTx.vpout.resize(1); - } - coinbaseTx.vpout[0] = MAKE_OUTPUT(); - - if (!fProofOfStake) { - //Miner gets the block reward and any network reward - CAmount nMinerReward = nBlockReward + nNetworkReward; - OUTPUT_PTR outCoinbase = MAKE_OUTPUT(); - outCoinbase->scriptPubKey = scriptPubKeyIn; - outCoinbase->nValue = nMinerReward; - coinbaseTx.vpout[0] = (std::move(outCoinbase)); - } - - // Budget Payment - if (nBudgetPayment) { - std::string strBudgetAddress = veil::Budget().GetBudgetAddress(chainActive.Height()+1); // KeyID for now - CBitcoinAddress addressFounder(strBudgetAddress); - assert(addressFounder.IsValid()); - CTxDestination dest = DecodeDestination(strBudgetAddress); - auto budgetScript = GetScriptForDestination(dest); - - OUTPUT_PTR outBudget = MAKE_OUTPUT(); - outBudget->scriptPubKey = budgetScript; - outBudget->nValue = nBudgetPayment; - coinbaseTx.vpout[fProofOfStake ? 0 : 1] = (std::move(outBudget)); - - std::string strFoundationAddress = veil::Budget().GetFoundationAddress(chainActive.Height()+1); // KeyID for now - CTxDestination destFoundation = DecodeDestination(strFoundationAddress); - auto foundationScript = GetScriptForDestination(destFoundation); - - OUTPUT_PTR outFoundation = MAKE_OUTPUT(); - outFoundation->scriptPubKey = foundationScript; - outFoundation->nValue = nFoundationPayment; - coinbaseTx.vpout[fProofOfStake ? 1 : 2] = (std::move(outFoundation)); - - std::string strFounderAddress = veil::Budget().GetFounderAddress(); // KeyID for now - CTxDestination destFounder = DecodeDestination(strFounderAddress); - auto founderScript = GetScriptForDestination(destFounder); - - if (nFounderPayment) { // Founder payment will eventually hit 0 - OUTPUT_PTR outFounder = MAKE_OUTPUT(); - outFounder->scriptPubKey = founderScript; - outFounder->nValue = nFounderPayment; - coinbaseTx.vpout[fProofOfStake ? 2 : 3] = (std::move(outFounder)); - } - } - - //Must add the height to the coinbase scriptsig - coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; if (fProofOfStake) { if (pblock->vtx.size() < 2) pblock->vtx.resize(2); - if (!nBudgetPayment) { - coinbaseTx.vpout[0]->SetValue(0); - coinbaseTx.vpout[0]->SetScriptPubKey(CScript()); - } pblock->vtx[1] = MakeTransactionRef(std::move(txCoinStake)); } pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); diff --git a/src/miner.h b/src/miner.h index 947bb935f4..93614a21d6 100644 --- a/src/miner.h +++ b/src/miner.h @@ -216,6 +216,9 @@ class BlockAssembler void resetBlock(); /** Add a tx to the block */ void AddToBlock(CTxMemPool::txiter iter); + /** Create the coinbase transaction. */ + void CreateCoinbase(CMutableTransaction& coinbaseTx, const CScript& scriptPubKeyIn, + bool fProofOfStake, CAmount nNetworkRewardReserve); // Methods for how to add transactions to a block. /** Add transactions based on feerate including unconfirmed ancestors diff --git a/src/validation.cpp b/src/validation.cpp index 33bbc8b2cd..7be350db5f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2444,6 +2444,13 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl bool fZCLimpMode = tstate == ThresholdState::ACTIVE; bool fCheckBlacklistHard = pindex->nHeight >= Params().HeightEnforceBlacklist(); + CAmount networkReward = pindex->nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : pindex->nNetworkRewardReserve; + + // The block rewards are stratified based upon the height of the block. + CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment = 0; + veil::Budget().GetBlockRewards(pindex->nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); + CAmount nExpStakeReward = networkReward + nBlockReward; + CAmount nBlockValueIn = 0; CAmount nBlockValueOut = 0; int64_t nTimeZerocoinSpendCheck = 0; @@ -2474,6 +2481,11 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CAmount nTxValueOut = 0; if (tx.IsCoinBase() && !tx.HasBlindedValues()) { nTxValueOut += tx.GetValueOut(); + } else if (tx.IsCoinBase() && block.IsProofOfStake()) { + // covers ringct stakes + if (!VerifyCoinbase(nExpStakeReward, tx, state)) + return false; + nTxValueOut += nExpStakeReward; } else if (tx.IsCoinBase()) { // Check tx with blinded values for (auto& pout : tx.vpout) { @@ -2796,12 +2808,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (!mapSpends.empty()) LogPrint(BCLog::BENCH, " - Check zerocoin spends: %.2fms\n", MILLI * nTimeZerocoinSpendCheck); - CAmount networkReward = pindex->nNetworkRewardReserve > Params().MaxNetworkReward() ? Params().MaxNetworkReward() : pindex->nNetworkRewardReserve; pindex->nNetworkRewardReserve -= networkReward; - // The block rewards are stratified based upon the height of the block. - CAmount nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment = 0; - veil::Budget().GetBlockRewards(pindex->nHeight, nBlockReward, nFounderPayment, nFoundationPayment, nBudgetPayment); //Check proof of full node if (!fSkipComputation && (block.fProofOfFullNode || block.hashPoFN != uint256())) { @@ -2823,38 +2831,45 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CAmount nCreated = nBlockValueOut - nBlockValueIn; // Check change doesn't exceed fees and that only PoFN blocks have blind txouts - // TODO for ringctstake - if ((block.fProofOfFullNode || block.hashPoFN != uint256()) && block.vtx[1]->HasBlindedValues()) { - // This should consist only of block fees, so it should be <= nFees - CAmount nBlindFeePayout = 0; - for (auto& pout : block.vtx[1]->vpout) { - // Check that the max value doesn't exceed the creation limit, so we can - // be sure the block doesn't generate generate more than it should - if (!(pout->IsType(OUTPUT_CT) || pout->IsType(OUTPUT_RINGCT))) - continue; + if ((block.fProofOfFullNode || block.hashPoFN != uint256())) { + if (block.vtx[0]->HasBlindedValues()) { + // ringctstake currently has no mechanism for claiming fees + } else if (block.vtx[1]->HasBlindedValues()) { + // This should consist only of block fees, so it should be <= nFees + CAmount nBlindFeePayout = 0; + for (auto& pout : block.vtx[1]->vpout) { + // Check that the max value doesn't exceed the creation limit, so we can + // be sure the block doesn't generate generate more than it should + if (!(pout->IsType(OUTPUT_CT) || pout->IsType(OUTPUT_RINGCT))) + continue; + + if (pout->IsType(OUTPUT_RINGCT)) + state.fHasAnonOutput = true; - if (pout->IsType(OUTPUT_RINGCT)) - state.fHasAnonOutput = true; - - int nExponent, nMantissa; - CAmount nMin, nMax; - if (GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax) != 0) - return state.DoS(100, error("ConnectBlock(): couldn't get range proof info\n", - REJECT_INVALID, "bad-range-proof")); - else { - nCreated += nMax; - nBlindFeePayout += nMax; + int nExponent, nMantissa; + CAmount nMin, nMax; + if (GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax) != 0) + return state.DoS(100, error("ConnectBlock(): couldn't get range proof info\n", + REJECT_INVALID, "bad-range-proof")); + else { + nCreated += nMax; + nBlindFeePayout += nMax; + } } - } - if (nBlindFeePayout > nFees) - return state.DoS(100, error("ConnectBlock(): blind fee payout is too large\n", - REJECT_INVALID, "bad-cs-amount")); + if (nBlindFeePayout > nFees) + return state.DoS(100, error("ConnectBlock(): blind fee payout is too large\n"), + REJECT_INVALID, "bad-cs-amount"); + } } else if (block.IsProofOfStake() && block.vtx[1]->HasBlindedValues()) { - return state.DoS(100, error("ConnectBlock(): coinstake without proof of full node has blinded values\n", - REJECT_INVALID, "bad-cs-txout")); + return state.DoS(100, error("ConnectBlock(): coinstake without proof of full node has blinded values\n"), + REJECT_INVALID, "bad-cs-txout"); } + // check ringct coinbase + if (block.IsProofOfStake() && block.vtx[0]->HasBlindedValues() && !VerifyCoinbase(nExpStakeReward, *block.vtx[0], state)) + return false; + if (nCreated > nCreationLimit) { LogPrintf("%s : BlockReward=%s Network=%s Founder=%s Budget=%s Foundation=%s\n", __func__, FormatMoney(nBlockReward), FormatMoney(networkReward), FormatMoney(nFounderPayment), FormatMoney(nBudgetPayment), @@ -4732,17 +4747,29 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c } if (block.IsProofOfStake()) { + size_t expVpSize = 1; + if (block.vtx[1]->IsRingCtSpend()) { + if (nHeight < Params().HeightRingCTStaking()) + return state.DoS(50, false, REJECT_INVALID, "ringct-stake", false, strprintf("RingCT PoS not allowed until height %d", Params().HeightRingCTStaking())); + // might be covered elsewhere? + if (!block.vtx[1]->IsCoinStake()) + return state.DoS(50, false, REJECT_INVALID, "bad-coinstake-txn", false, strprintf("RingCT PoS has invalid coinstake tx")); + // two ringct outputs + expVpSize += 2; + } if (!veil::BudgetParams::IsSuperBlock(pindexPrev->nHeight + 1)) { - if (block.vtx[0]->vpout.size() != 1) + if (block.vtx[0]->vpout.size() != expVpSize) + return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS non-superblock has invalid coinbase out")); + if (block.vtx[0]->vpout[0]->nVersion != OUTPUT_STANDARD) return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS non-superblock has invalid coinbase out")); auto pTxBase = ((CTxOutStandard*) &*(block.vtx[0]->vpout[0])); - if (pTxBase->nValue != 0 || pTxBase->scriptPubKey != CScript() || pTxBase->nVersion != OUTPUT_STANDARD) + if (pTxBase->nValue != 0 || pTxBase->scriptPubKey != CScript()) return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS non-superblock has invalid coinbase out")); } else { // next block is a superblock - if (block.vtx[0]->vpout.size() > 3) + if (block.vtx[0]->vpout.size() > expVpSize + 2 || block.vtx[0]->vpout.size() < expVpSize) return state.DoS(50, false, REJECT_INVALID, "bad-coinbase-vpout", false, strprintf("PoS superblock has invalid coinbase out")); } } diff --git a/src/veil/proofofstake/kernel.cpp b/src/veil/proofofstake/kernel.cpp index 67344acdda..60ee514e97 100644 --- a/src/veil/proofofstake/kernel.cpp +++ b/src/veil/proofofstake/kernel.cpp @@ -117,17 +117,26 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co return error("%s: called on non-coinstake %s", __func__, txRef->GetHash().ToString().c_str()); //Construct the stakeinput object - if (txRef->vin.size() != 1 && txRef->vin[0].IsZerocoinSpend()) - return error("%s: Stake is not a zerocoinspend", __func__); + if (txRef->vin.size() != 1) + return error("%s: Stake is not correctly sized for coinstake", __func__); const CTxIn& txin = txRef->vin[0]; - auto spend = TxInToZerocoinSpend(txin); - if (!spend) + if (txin.IsZerocoinSpend()) { + auto spend = TxInToZerocoinSpend(txin); + if (!spend) + return false; + + if (spend->getSpendType() != libzerocoin::SpendType::STAKE) + return error("%s: spend is using the wrong SpendType (%d)", __func__, (int)spend->getSpendType()); + + stake = std::unique_ptr(new ZerocoinStake(*spend)); + } else if (txin.IsAnonInput()) { + // TODO return false; - stake = std::unique_ptr(new ZerocoinStake(*spend.get())); - if (spend->getSpendType() != libzerocoin::SpendType::STAKE) - return error("%s: spend is using the wrong SpendType (%d)", __func__, (int)spend->getSpendType()); + } else { + return error("%s: Stake is not a zerocoin or ringctspend", __func__); + } CBlockIndex* pindex = stake->GetIndexFrom(); if (!pindex) diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index bedb436295..cb84a54970 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -321,9 +321,15 @@ bool RingCTStake::MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew) * @return true upon success. * false if the AnonWallet fails to find the StakeAddress or if AddAnonInputs() fails. */ -bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMutableTransaction& txCoinStake, bool& retryable) +bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) { AnonWallet* panonWallet = pwallet->GetAnonWallet(); + + // Update the coinbase tx + std::string strError; + if (!panonWallet->AddCoinbaseRewards(txCoinbase, nReward, strError)) + return false; + CTransactionRef ptx = MakeTransactionRef(txCoinStake); CWalletTx wtx(pwallet, ptx); @@ -346,7 +352,6 @@ bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMut tempRecipient.min_value = GetBracketMinValue(); vecSend.emplace_back(tempRecipient); - std::string strError; CTransactionRecord rtx; CAmount nFeeRet = 0; retryable = false; @@ -584,7 +589,7 @@ bool ZerocoinStake::MarkSpent(CWallet *pwallet, const uint256& txid) #endif } -bool ZerocoinStake::CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) { +bool ZerocoinStake::CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) { if (!CreateTxOuts(pwallet, txCoinStake.vpout, nBlockReward)) { LogPrintf("%s : failed to get scriptPubKey\n", __func__); retryable = true; diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 670ae33755..761d98904c 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -39,7 +39,7 @@ class CStakeInput virtual bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) = 0; virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) = 0; virtual bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) = 0; - virtual bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) = 0; + virtual bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) = 0; }; @@ -77,7 +77,7 @@ class RingCTStake : public CStakeInput bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; - bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) override; + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override; }; @@ -111,7 +111,7 @@ class ZerocoinStake : public CStakeInput bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; - bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable) override; + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override; bool IsZerocoins() override { return true; } bool MarkSpent(CWallet* pwallet, const uint256& txid); diff --git a/src/veil/ringct/anon.cpp b/src/veil/ringct/anon.cpp index 101da5a76d..1cef7a57d5 100644 --- a/src/veil/ringct/anon.cpp +++ b/src/veil/ringct/anon.cpp @@ -29,7 +29,7 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) size_t nStandard = 0, nCt = 0, nRingCT = 0; CAmount nPlainValueOut = tx.GetPlainValueOut(nStandard, nCt, nRingCT); CAmount nTxFee = 0; - if (!tx.GetCTFee(nTxFee)) + if (!tx.GetCTFee(nTxFee) && !tx.IsCoinStake()) return state.DoS(100, error("%s: bad-fee-output", __func__), REJECT_INVALID, "bad-fee-output"); nPlainValueOut += nTxFee; @@ -94,7 +94,8 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) vpOutCommits.push_back(&vDL[(1 + (nInputs+1) * nRingSize) * 32]); vpInputSplitCommits.push_back(&vDL[(1 + (nInputs+1) * nRingSize) * 32]); } else { - vpOutCommits.push_back(plainCommitment.data); + if (nPlainValueOut > 0) + vpOutCommits.push_back(plainCommitment.data); secp256k1_pedersen_commitment *pc; for (const auto &txout : tx.vpout) { @@ -157,7 +158,9 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) // Verify commitment sums match if (fSplitCommitments) { std::vector vpOutCommits; - vpOutCommits.push_back(plainCommitment.data); + + if (nPlainValueOut > 0) + vpOutCommits.push_back(plainCommitment.data); secp256k1_pedersen_commitment *pc; for (const auto &txout : tx.vpout) { @@ -175,6 +178,36 @@ bool VerifyMLSAG(const CTransaction &tx, CValidationState &state) return true; } +bool VerifyCoinbase(CAmount nExpStakeReward, const CTransaction &tx, CValidationState &state) +{ + std::vector vBlindPlain(32); + secp256k1_pedersen_commitment plainCommitment; + + std::vector vpPlainCommits; + std::vector vpOutCommits; + + if (!secp256k1_pedersen_commit(secp256k1_ctx_blind, &plainCommitment, &vBlindPlain[0], + (uint64_t) nExpStakeReward, secp256k1_generator_h)) { + return state.DoS(100, error("%s: %s", __func__, "Pedersen Commit failed for plain out."), REJECT_INTERNAL, "plain-pedersen-commit-failed"); + } + + vpPlainCommits.push_back(plainCommitment.data); + + secp256k1_pedersen_commitment *pc; + for (const auto &txout : tx.vpout) { + if ((pc = txout->GetPCommitment())) + vpOutCommits.push_back(pc->data); + } + + int rv; + if (1 != (rv = secp256k1_pedersen_verify_tally(secp256k1_ctx_blind, + (const secp256k1_pedersen_commitment* const*)vpPlainCommits.data(), vpPlainCommits.size(), + (const secp256k1_pedersen_commitment* const*)vpOutCommits.data(), vpOutCommits.size()))) + return state.DoS(100, error("%s: verify-commit-tally-failed %d", __func__, rv), REJECT_INVALID, "verify-commit-tally-failed"); + + return true; +} + bool AddKeyImagesToMempool(const CTransaction &tx, CTxMemPool &pool) { for (const CTxIn &txin : tx.vin) { diff --git a/src/veil/ringct/anon.h b/src/veil/ringct/anon.h index e45d783f6c..4614f5f102 100644 --- a/src/veil/ringct/anon.h +++ b/src/veil/ringct/anon.h @@ -19,6 +19,7 @@ const size_t ANON_FEE_MULTIPLIER = 2; bool VerifyMLSAG(const CTransaction &tx, CValidationState &state); +bool VerifyCoinbase(CAmount nExpStakeReward, const CTransaction &tx, CValidationState &state); bool AddKeyImagesToMempool(const CTransaction &tx, CTxMemPool &pool); bool RemoveKeyImagesFromMempool(const uint256 &hash, const CTxIn &txin, CTxMemPool &pool); diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 3ff75b95e7..f762f78cca 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -3504,9 +3504,11 @@ bool AnonWallet::SetOutputs( int& nChangePosInOut, bool fSkipFee, bool fFeesFromChange, + bool fAddFeeDataOutput, + bool fUseBlindSum, std::string& sError) { - if (vpout.size() < 2) { + if (fAddFeeDataOutput) { OUTPUT_PTR outFee = MAKE_OUTPUT(); outFee->vData.push_back(DO_FEE); outFee->vData.resize(9); // More bytes than varint fee could use @@ -3514,6 +3516,16 @@ bool AnonWallet::SetOutputs( } bool fFirst = true; + std::vector vpBlinds; + int lastBlind = 0; + if (fUseBlindSum) { + for (size_t i = vecSend.size() - 1; i >= 0; --i) { + if (vecSend[i].nType == OUTPUT_CT || vecSend[i].nType == OUTPUT_RINGCT) { + lastBlind = i; + break; + } + } + } for (size_t i = 0; i < vecSend.size(); ++i) { auto &recipient = vecSend[i]; @@ -3540,9 +3552,20 @@ bool AnonWallet::SetOutputs( recipient.n = vpout.size(); vpout.push_back(txbout); if (recipient.nType == OUTPUT_CT || recipient.nType == OUTPUT_RINGCT) { - if (recipient.vBlind.size() != 32) { + if (fUseBlindSum && (int)i == lastBlind) { recipient.vBlind.resize(32); - GetStrongRandBytes(&recipient.vBlind[0], 32); + // Last to-be-blinded value: compute from all other blinding factors. + // sum of output blinding values must equal sum of input blinding values + if (!secp256k1_pedersen_blind_sum(secp256k1_ctx_blind, &recipient.vBlind[0], &vpBlinds[0], vpBlinds.size(), 0)) { + return wserrorN(1, sError, __func__, "secp256k1_pedersen_blind_sum failed."); + } + } else { + if (recipient.vBlind.size() != 32) { + recipient.vBlind.resize(32); + GetStrongRandBytes(&recipient.vBlind[0], 32); + } + if (fUseBlindSum) + vpBlinds.push_back(&recipient.vBlind[0]); } if (!AddCTData(txbout.get(), recipient, sError)) @@ -3570,7 +3593,7 @@ bool AnonWallet::ArrangeOutBlinds( sError = strprintf("Pedersen Commit failed for plain out."); return error("%s: %s", __func__, sError); } - + LogPrintf("Creating plain commitment of value %d\n", nValueOutPlain); vpOutCommits.push_back(plainCommitment->data); vpOutBlinds.push_back(&vBlindPlain[0]); } @@ -3746,7 +3769,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } // Insert a sender-owned 0 value output that becomes the change output if needed - { + if (!fProofOfStake) { // Fill an output to ourself CTempRecipient recipient; recipient.nType = fCTOut ? OUTPUT_CT : OUTPUT_RINGCT; @@ -3802,7 +3825,7 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st bool fFeesFromChange = nMaximumInputs > 0 && nChange >= MIN_FINAL_CHANGE + nFeeRet; if (!SetOutputs(txNew.vpout, vecSend, nFeeRet, nSubtractFeeFromAmount, - nValueOutPlain, nChangePosInOut, fSkipFee, fFeesFromChange, sError)) { + nValueOutPlain, nChangePosInOut, fSkipFee, fFeesFromChange, true, false, sError)) { return false; } @@ -4037,45 +4060,6 @@ bool AnonWallet::AddAnonInputs_Inner(CWalletTx &wtx, CTransactionRecord &rtx, st } } - if (fProofOfStake) { - // Add reward outputs - std::vector rewards(2); - rewards[0].address = rewards[1].address = vecSend[0].address; - rewards[0].nType = rewards[1].nType = OUTPUT_RINGCT; - rewards[0].fSubtractFeeFromAmount = rewards[1].fSubtractFeeFromAmount = false; - rewards[0].fExemptFeeSub = rewards[1].fExemptFeeSub = true; - rewards[0].SetAmount(GetRandInt(coinControl->nStakeReward - 1)); - rewards[1].SetAmount(coinControl->nStakeReward - rewards[0].nAmount); - if (!ExpandTempRecipients(rewards, sError)) - return false; - if (!SetOutputs(txNew.vpout, rewards, nFeeRet, nSubtractFeeFromAmount, - nValueOutPlain, nChangePosInOut, true, false, sError)) { - return false; - } - std::vector vpOutCommits; - std::vector vpOutBlinds; - std::vector vBlindPlain(32, 0); - secp256k1_pedersen_commitment plainCommitment; - // Add 3 blinds: reward total as plain output, two rewards that sum to total - nValueOutPlain = coinControl->nStakeReward; - if (!ArrangeOutBlinds(txNew.vpout, rewards, vpOutCommits, vpOutBlinds, vBlindPlain, - &plainCommitment, nValueOutPlain, nChangePosInOut, fCTOut, sError)) { - return false; - } - - // compute the blind sum of the rewards to equal the total - if (!secp256k1_pedersen_blind_sum(secp256k1_ctx_blind, &rewards[1].vBlind[0], &vpOutBlinds[0], vpOutBlinds.size(), 1)) { - wserrorN(1, sError, __func__, "secp256k1_pedersen_blind_sum failed."); - return false; - } - - CTxOutBase *pout = (CTxOutBase*)txNew.vpout[rewards[1].n].get(); - if (!AddCTData(pout, rewards[1], sError)) { - return false; // sError will be set - } - vecSend.insert(vecSend.end(), rewards.begin(), rewards.end()); - } - rtx.nFee = nFeeRet; rtx.nFlags |= ORF_ANON_IN; AddOutputRecordMetaData(rtx, vecSend); @@ -4137,6 +4121,72 @@ bool AnonWallet::AddAnonInputs(CWalletTx &wtx, CTransactionRecord &rtx, std::vec return true; } +bool AnonWallet::AddCoinbaseRewards( + CMutableTransaction& txCoinbase, CAmount nStakeReward, std::string& sError) +{ + // Add reward outputs + std::vector rewards(2); + + for (CTempRecipient& recp : rewards) { + recp.nType = OUTPUT_RINGCT; + recp.address = GetStealthStakeAddress(); + recp.fSubtractFeeFromAmount = false; + recp.fExemptFeeSub = true; + recp.fOverwriteRangeProofParams = false; + } + + if (nStakeReward > 1) { + rewards[0].SetAmount(GetRandInt(nStakeReward - 2) + 1); + rewards[1].SetAmount(nStakeReward - rewards[0].nAmount); + } else { + LogPrint(BCLog::STAKING, "Creating a RingCT stake with a tiny reward: %d\n", nStakeReward); + rewards[0].SetAmount(nStakeReward); + rewards.pop_back(); + } + + if (!ExpandTempRecipients(rewards, sError)) + return false; + CAmount nValueOutPlain = 0; + int nChangePosInOut = -1; + + if (!SetOutputs(txCoinbase.vpout, rewards, 0, 0, + nValueOutPlain, nChangePosInOut, true, false, false, true, sError)) { + return false; + } + + std::vector vpOutCommits; + std::vector vpOutBlinds; + std::vector vBlindPlain(32, 0); + secp256k1_pedersen_commitment plainCommitment; + // Reward total as plain output and two blinds + nValueOutPlain = nStakeReward; + if (!ArrangeOutBlinds(txCoinbase.vpout, rewards, vpOutCommits, vpOutBlinds, vBlindPlain, + &plainCommitment, nValueOutPlain, nChangePosInOut, true, sError)) { + return false; + } + + std::vector vpCommitsIn, vpCommitsOut; + vpCommitsIn.push_back(&plainCommitment); + secp256k1_pedersen_commitment *pc; + for (auto &txout : txCoinbase.vpout) { + if ((pc = txout->GetPCommitment())) + vpCommitsOut.push_back(pc); + } + + int rv; + if (1 != (rv = secp256k1_pedersen_verify_tally(secp256k1_ctx_blind, vpCommitsIn.data(), vpCommitsIn.size(), + vpCommitsOut.data(), vpCommitsOut.size()))) + { + LogPrintf("verify_tally failed: %d commits, plain: %d, r1: %d, r2: %d, r1+r2: %d, rv: %d\n", + vpCommitsOut.size(), nValueOutPlain, rewards[0].nAmount, rewards[1].nAmount, + rewards[0].nAmount + rewards[1].nAmount, rv + ); + return false; + } + + return true; +} + bool AnonWallet::CoinToTxIn( const COutputR& coin, CTxIn& txin, veil_ringct::TransactionInputsSigContext& inCtx, size_t nRingSize) @@ -4239,7 +4289,7 @@ bool AnonWallet::CreateStakeTxOuts( CAmount nValueOutPlain = 0; // SetOutputs - if (!SetOutputs(vpout, vecOut, 0, 0, nValueOutPlain, nChangePosInOut, true, false, sError)) { + if (!SetOutputs(vpout, vecOut, 0, 0, nValueOutPlain, nChangePosInOut, true, false, true, false, sError)) { return error("%s: %s", __func__, sError); } diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index c0d63e3f11..07b37fb7c0 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -239,6 +239,7 @@ class AnonWallet bool GetRandomHidingOutputs(size_t nInputSize, size_t nRingSize, std::set &setHave, std::vector& randomoutputs, std::string &sError); + bool AddCoinbaseRewards(CMutableTransaction& txCoinbase, CAmount nStakeReward, std::string& sError); bool IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint); bool IsMyAnonInput(const CTxIn& txin, COutPoint& myOutpoint, CKey& key); @@ -430,6 +431,8 @@ class AnonWallet int& nChangePosInOut, bool fSkipFee, bool fFeesFromChange, + bool fAddFeeDataOutput, + bool fUseBlindSum, std::string& sError); bool ArrangeOutBlinds( std::vector& vpout, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5e407f9642..5a9ce6ea60 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3859,8 +3859,9 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CTransac return true; } +// RingCTStake needs the coinbase tx somehow template -bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart) +bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart, CMutableTransaction& txCoinbase) { static_assert(std::is_base_of::value, "TStake must derive from CStakeInput"); // The following split & combine thresholds are important to security @@ -3946,7 +3947,7 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, txNew.vpout.emplace_back(CTxOut(0, scriptEmpty).GetSharedPtr()); bool retryable = true; - if (!stakeInput->CreateCoinStake(this, nBlockReward, txNew, retryable)) { + if (!stakeInput->CreateCoinStake(this, nBlockReward, txNew, retryable, txCoinbase)) { if (retryable) continue; return false; @@ -3958,13 +3959,13 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, return false; } -bool CWallet::CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart) +bool CWallet::CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart, CMutableTransaction& txCoinbase) { - return CreateCoinStake(pindexBest, nBits, txNew, nTxNewTime, nComputeTimeStart); + return CreateCoinStake(pindexBest, nBits, txNew, nTxNewTime, nComputeTimeStart, txCoinbase); } -bool CWallet::CreateRingCTStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart) +bool CWallet::CreateRingCTStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart, CMutableTransaction& txCoinbase) { - return CreateCoinStake(pindexBest, nBits, txNew, nTxNewTime, nComputeTimeStart); + return CreateCoinStake(pindexBest, nBits, txNew, nTxNewTime, nComputeTimeStart, txCoinbase); } bool CWallet::SelectStakeCoins(std::list >& listInputs) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 5138254a2c..b3a430e7c7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1097,11 +1097,11 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface private: template - bool CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart); + bool CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart, CMutableTransaction& txCoinbase); public: - bool CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart); - bool CreateRingCTStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart); + bool CreateZerocoinStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart, CMutableTransaction& txCoinbase); + bool CreateRingCTStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart, CMutableTransaction& txCoinbase); bool SelectStakeCoins(std::list >& listInputs); bool SelectStakeCoins(std::list >& listInputs); From 986c7a5983804154fd9928cb9c377a21d3b92c2b Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 29 Oct 2022 11:59:44 -0700 Subject: [PATCH 17/25] RingCT stake: accept and load the block Remove some debugging statements and other cleanup. --- src/chainparams.h | 1 + src/miner.cpp | 2 +- src/validation.cpp | 107 +++++++++---- src/veil/proofofstake/blockvalidation.cpp | 6 +- src/veil/proofofstake/kernel.cpp | 17 +- src/veil/proofofstake/stakeinput.cpp | 183 +++++++++++++++++----- src/veil/proofofstake/stakeinput.h | 47 ++++++ src/veil/ringct/anonwallet.cpp | 9 +- src/veil/ringct/blind.cpp | 6 +- src/veil/ringct/blind.h | 2 +- src/veil/ringct/temprecipient.h | 6 +- 11 files changed, 296 insertions(+), 90 deletions(-) diff --git a/src/chainparams.h b/src/chainparams.h index 4cd70a9176..3b074d193b 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -148,6 +148,7 @@ class CChainParams int HeightEnforceBlacklist() const { return nHeightEnforceBlacklist; } int HeightProgPowDAGSizeReduction() const { return nHeightProgPowDAGSizeReduction; } int HeightRingCTStaking() const { return nHeightRingCTStaking; } + int RingCT_RequiredStakeDepth() const { return nZerocoinRequiredStakeDepthV2; } uint32_t PowUpdateTimestamp() const { return nPowUpdateTimestamp; } uint64_t KIforkTimestamp() const { return nTimeKIfork; } diff --git a/src/miner.cpp b/src/miner.cpp index 01dda27cd1..a91ebb0e39 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -570,7 +570,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LogPrint(BCLog::STAKING, "%s: Failed to sign block hash\n", __func__); return nullptr; } - LogPrint(BCLog::STAKING, "%s: FOUND STAKE!!\n block: \n%s\n", __func__, pblock->ToString()); + LogPrint(BCLog::STAKING, "%s: FOUND STAKE!!\n", __func__); } if (pindexPrev && pindexPrev != chainActive.Tip()) { diff --git a/src/validation.cpp b/src/validation.cpp index 7be350db5f..4f0d668838 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -194,7 +194,9 @@ class CChainState { bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fProofOfStake, bool fProofOfFullNode, int nMaxHeightNoPoWScore) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool AcceptBlock(const std::shared_ptr& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool ContextualCheckZerocoinStake(CBlockIndex* pindex, CStakeInput* stake); + bool ContextualCheckStake(CBlockIndex* pindex, CStakeInput* stake); + bool ContextualCheckRingCTStake(CBlockIndex* pindex, PublicRingCTStake* stake); + bool ContextualCheckZerocoinStake(CBlockIndex* pindex, ZerocoinStake* stake); // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view); @@ -2506,7 +2508,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // Use max values in range proofs, so we know the max value out int nExponent, nMantissa; CAmount nMin, nMax; - if (GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax) != 0) + if (!GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax)) return state.DoS(100, error("ConnectBlock(): couldn't get range proof info\n", REJECT_INVALID, "bad-range-proof")); else @@ -2848,7 +2850,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl int nExponent, nMantissa; CAmount nMin, nMax; - if (GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax) != 0) + if (!GetRangeProofInfo(*(pout->GetPRangeproof()), nExponent, nMantissa, nMin, nMax)) return state.DoS(100, error("ConnectBlock(): couldn't get range proof info\n", REJECT_INVALID, "bad-range-proof")); else { @@ -4931,33 +4933,78 @@ static CDiskBlockPos SaveBlockToDisk(const CBlock& block, int nHeight, const CCh return blockPos; } -bool CChainState::ContextualCheckZerocoinStake(CBlockIndex* pindex, CStakeInput* stake) +bool CChainState::ContextualCheckStake(CBlockIndex* pindex, CStakeInput* stake) { - if (ZerocoinStake* stakeCheck = dynamic_cast(stake)) { - CBlockIndex* pindexFrom = stakeCheck->GetIndexFrom(); - if (!pindexFrom) - return error("%s: failed to get index associated with zerocoin stake checksum", __func__); - - int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); - if (pindex->nHeight >= Params().HeightLightZerocoin()) - nRequiredDepth = Params().Zerocoin_RequiredStakeDepthV2(); - - if (pindex->nHeight - pindexFrom->nHeight < nRequiredDepth) - return error("%s: zerocoin stake does not have required confirmation depth", __func__); - - //The checksum needs to be the exact checksum from the modifier height - libzerocoin::CoinDenomination denom = libzerocoin::AmountToZerocoinDenomination(stakeCheck->GetValue()); - int nHeightStake = pindex->nHeight - nRequiredDepth; - CBlockIndex* pindexFrom2 = pindex->GetAncestor(nHeightStake); - if (!pindexFrom2) - return error("%s: block ancestor does not exist", __func__); - - uint256 hashCheckpoint = pindexFrom2->GetAccumulatorHash(denom); - if (hashCheckpoint != stakeCheck->GetChecksum()) - return error("%s: accumulator checksum is different than the modifier block. indexfromheight=%d stake=%s blockfrom=%s", __func__, pindexFrom->nHeight, stakeCheck->GetChecksum().GetHex(), hashCheckpoint.GetHex()); - } else { - return error("%s: dynamic_cast of stake ptr failed", __func__); + switch (stake->GetType()) { + case STAKE_ZEROCOIN: + ZerocoinStake* stakeZerocoin; + try { + stakeZerocoin = dynamic_cast(stake); + } catch (std::bad_cast) { + return false; + } + + return ContextualCheckZerocoinStake(pindex, stakeZerocoin); + case STAKE_RINGCT: + PublicRingCTStake* stakeRCT = nullptr; + + try { + stakeRCT = dynamic_cast(stake); + } catch (std::bad_cast) { + return false; + } + + return ContextualCheckRingCTStake(pindex, stakeRCT); } + return error ("%s: Invalid stake type %d block height %d", __func__, stake->GetType(), pindex->nHeight); +} + +bool CChainState::ContextualCheckRingCTStake(CBlockIndex* pindex, PublicRingCTStake* stake) +{ + // Check that all included inputs are beyond the minimum stake age + const std::vector& vAnonInputs = stake->GetTxInputs(); + for (const COutPoint& input : vAnonInputs) { + CTransactionRef ptxPrev; + int nHeightTx = 0; + if (!IsTransactionInChain(input.hash, nHeightTx, ptxPrev, Params().GetConsensus(), pindex)) + return error("%s: could not find tx %s within the same chain", __func__, input.hash.GetHex()); + if (nHeightTx == 0 || nHeightTx > pindex->nHeight - Params().RingCT_RequiredStakeDepth()) + return error("%s: included RingCT input is not below the required stake depth : %s", __func__, input.ToString()); + if (ptxPrev->vpout.size() <= input.n) + return error("%s: RingCT Input %s does not exist", input.ToString()); + + //Check that it is a ringct output + const CTxOutBaseRef txbout = ptxPrev->vpout[input.n]; + if (txbout->GetType() != OUTPUT_RINGCT) + return error ("%s: RingCT Input %s is not a ringct output type", __func__, input.ToString()); + } + // Stake hash check elsewhere confirms that the stake value is above the minimum + return true; +} + +bool CChainState::ContextualCheckZerocoinStake(CBlockIndex* pindex, ZerocoinStake* stake) +{ + CBlockIndex* pindexFrom = stake->GetIndexFrom(); + if (!pindexFrom) + return error("%s: failed to get index associated with zerocoin stake checksum", __func__); + + int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); + if (pindex->nHeight >= Params().HeightLightZerocoin()) + nRequiredDepth = Params().Zerocoin_RequiredStakeDepthV2(); + + if (pindex->nHeight - pindexFrom->nHeight < nRequiredDepth) + return error("%s: zerocoin stake does not have required confirmation depth", __func__); + + //The checksum needs to be the exact checksum from the modifier height + libzerocoin::CoinDenomination denom = libzerocoin::AmountToZerocoinDenomination(stake->GetValue()); + int nHeightStake = pindex->nHeight - nRequiredDepth; + CBlockIndex* pindexFrom2 = pindex->GetAncestor(nHeightStake); + if (!pindexFrom2) + return error("%s: block ancestor does not exist", __func__); + + uint256 hashCheckpoint = pindexFrom2->GetAccumulatorHash(denom); + if (hashCheckpoint != stake->GetChecksum()) + return error("%s: accumulator checksum is different than the modifier block. indexfromheight=%d stake=%s blockfrom=%s", __func__, pindexFrom->nHeight, stake->GetChecksum().GetHex(), hashCheckpoint.GetHex()); return true; } @@ -4994,8 +5041,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, CVali if (!stake) return error("%s: null stake ptr", __func__); - if (!ContextualCheckZerocoinStake(pindex, stake.get())) - return state.DoS(100, error("%s: zerocoin stake fails context checks", __func__)); + if (!ContextualCheckStake(pindex, stake.get())) + return state.DoS(100, error("%s: stake fails context checks", __func__)); // This stake has already been seen in a different block, prevent disk-space attack by requiring valid PoW block header uint256 hashBlock = block.GetHash(); diff --git a/src/veil/proofofstake/blockvalidation.cpp b/src/veil/proofofstake/blockvalidation.cpp index 46611a9d05..48d594f9a9 100644 --- a/src/veil/proofofstake/blockvalidation.cpp +++ b/src/veil/proofofstake/blockvalidation.cpp @@ -34,10 +34,6 @@ bool ValidateBlockSignature(const CBlock& block) return pubkey.Verify(block.GetHash(), block.vchBlockSig); } else if (block.vtx[1]->vin[0].IsAnonInput()) { - if (block.nHeight < Params().HeightRingCTStaking()) { - return error("%s: RingCT staking not accepted before height %d", - __func__, Params().HeightRingCTStaking()); - } CTxIn txin = block.vtx[1]->vin[0]; const std::vector &vKeyImages = txin.scriptData.stack[0]; const std::vector vMI = txin.scriptWitness.stack[0]; @@ -67,7 +63,7 @@ bool ValidateBlockSignature(const CBlock& block) CPubKey pubkey; pubkey.Set(ao.pubkey.begin(), ao.pubkey.end()); if (!pubkey.IsValid()) - return error("%s: public key from ringct stake is not valid"); + return error("%s: public key from ringct stake is not valid", __func__); if (pubkey.Verify(block.GetHash(), block.vchBlockSig)) return true; diff --git a/src/veil/proofofstake/kernel.cpp b/src/veil/proofofstake/kernel.cpp index 60ee514e97..c2418aca84 100644 --- a/src/veil/proofofstake/kernel.cpp +++ b/src/veil/proofofstake/kernel.cpp @@ -122,6 +122,7 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co const CTxIn& txin = txRef->vin[0]; + CBlockIndex* pindexFrom; if (txin.IsZerocoinSpend()) { auto spend = TxInToZerocoinSpend(txin); if (!spend) @@ -131,20 +132,20 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co return error("%s: spend is using the wrong SpendType (%d)", __func__, (int)spend->getSpendType()); stake = std::unique_ptr(new ZerocoinStake(*spend)); + pindexFrom = stake->GetIndexFrom(); } else if (txin.IsAnonInput()) { - // TODO - return false; + stake = std::unique_ptr(new PublicRingCTStake(txRef)); + pindexFrom = pindexCheck->GetAncestor(pindexCheck->nHeight - Params().RingCT_RequiredStakeDepth()); } else { return error("%s: Stake is not a zerocoin or ringctspend", __func__); } - CBlockIndex* pindex = stake->GetIndexFrom(); - if (!pindex) + if (!pindexFrom) return error("%s: Failed to find the block index", __func__); // Read block header CBlock blockprev; - if (!ReadBlockFromDisk(blockprev, pindex->GetBlockPos(), Params().GetConsensus())) + if (!ReadBlockFromDisk(blockprev, pindexFrom->GetBlockPos(), Params().GetConsensus())) return error("CheckProofOfStake(): INFO: failed to find block"); arith_uint256 bnTargetPerCoinDay; @@ -152,7 +153,7 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co uint64_t nStakeModifier = 0; if (!stake->GetModifier(nStakeModifier, pindexCheck->pprev)) { - if (pindexCheck->nHeight - Params().HeightLightZerocoin() > 400) + if (txin.IsAnonInput() || pindexCheck->nHeight - Params().HeightLightZerocoin() > 400) return error("%s failed to get modifier for stake input\n", __func__); } @@ -160,10 +161,12 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co unsigned int nTxTime = nTimeBlock; // Enforce VIP-1 after it was activated - CAmount nValue = (int)nTxTime > Params().EnforceWeightReductionTime() + CAmount nValue = txin.IsAnonInput() || (int)nTxTime > Params().EnforceWeightReductionTime() ? stake->GetWeight() : stake->GetValue(); + if (nValue == 0) + return error("%s: coinstake %s has no stake weight\n", __func__, txRef->GetHash().GetHex()); if (!CheckStake(stake->GetUniqueness(), nValue, nStakeModifier, ArithToUint256(bnTargetPerCoinDay), nBlockFromTime, nTxTime, hashProofOfStake)) { diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index cb84a54970..edb579d4c8 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -13,6 +13,8 @@ #include "validation.h" #include "stakeinput.h" #include "veil/proofofstake/kernel.h" +#include "veil/ringct/anon.h" +#include "veil/ringct/blind.h" #ifdef ENABLE_WALLET #include "wallet/coincontrol.h" #include "wallet/wallet.h" @@ -138,6 +140,40 @@ bool CheckMinStake(const CAmount& nAmount) return true; } +CAmount GetRingCTWeightForValue(const CAmount& nValueIn) { + // fast mode + if (nValueIn <= nBareMinStake) + return 0; + + // bracket is at least 1 now. + int bracket = fast_log16(nValueIn - nOneSat); + // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really + // 1 << (4 + 4 * bracket - 4) + CAmount val = (1ULL << (4 * bracket)) + nOneSat; + + switch (bracket) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + return val; + case 8: + return (val * 95) / 100; + case 9: + return (val * 91) / 100; + case 10: + return (val * 71) / 100; + case 11: + return (val * 5) / 10; + case 12: + return (val * 3) / 10; + default: + return val / 10; + } +} CBlockIndex* RingCTStake::GetIndexFrom() { @@ -196,44 +232,12 @@ CAmount RingCTStake::GetBracketMinValue() int bracket = fast_log16(nValueIn - nOneSat); // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really // 1 << (4 + 4 * bracket - 4) - return (1 << (4 * bracket)) + nOneSat; + return (1ULL << (4 * bracket)) + nOneSat; } // We further reduce the weights of higher brackets to match zerocoin reductions. CAmount RingCTStake::GetWeight() { - CAmount nValueIn = GetValue(); - // fast mode - if (nValueIn <= nBareMinStake) - return 0; - - // bracket is at least 1 now. - int bracket = fast_log16(nValueIn - nOneSat); - // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really - // 1 << (4 + 4 * bracket - 4) - CAmount val = (1L << (4 * bracket)) + nOneSat; - - switch (bracket) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - return val; - case 8: - return (val * 95) / 100; - case 9: - return (val * 91) / 100; - case 10: - return (val * 71) / 100; - case 11: - return (val * 5) / 10; - case 12: - return (val * 3) / 10; - default: - return val / 10; - } + return GetRingCTWeightForValue(GetValue()); } bool RingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) @@ -367,6 +371,7 @@ bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMut ZerocoinStake::ZerocoinStake(const libzerocoin::CoinSpend& spend) { + nType = STAKE_ZEROCOIN; this->nChecksum = spend.getAccumulatorChecksum(); this->denom = spend.getDenomination(); uint256 nSerial = spend.getCoinSerialNumber().getuint256(); @@ -382,7 +387,7 @@ int ZerocoinStake::GetChecksumHeightFromMint() if (nNewBlockHeight >= Params().HeightLightZerocoin()) { nHeightChecksum = nNewBlockHeight - Params().Zerocoin_RequiredStakeDepthV2(); } else { - nHeightChecksum = chainActive.Height() + 1 - Params().Zerocoin_RequiredStakeDepth(); + nHeightChecksum = nNewBlockHeight - Params().Zerocoin_RequiredStakeDepth(); } //Need to return the first occurance of this checksum in order for the validation process to identify a specific @@ -622,4 +627,110 @@ bool ZerocoinStake::CreateCoinStake(CWallet* pwallet, const CAmount& nBlockRewar return false; } return true; -} \ No newline at end of file +} + +/** + * @brief Get the lowest possible value of the inputs used in this RingCT transaction. + * @param[out] nValue: The returned minimum value of the RingCT output [usually 2]. + * @return true upon success. + * false if the output type is not OUTPUT_RINGCT or if GetRangeProofInfo() fails. + */ +bool PublicRingCTStake::GetMinimumInputValue(CAmount& nValue) const +{ + int nExp = 0; + int nMantissa = 0; + CAmount nMinValue = 0; + CAmount nMaxValue = 0; + CTxOutRingCT* txout = nullptr; + for (auto& pout : m_ptx->vpout) { + if (pout->GetType() != OUTPUT_RINGCT) + continue; + txout = (CTxOutRingCT*)pout.get(); + break; + } + + if (!txout) + return error("%s: PublicRingCTStake has no RingCT outputs.", __func__); + + if (!GetRangeProofInfo(txout->vRangeproof, nExp, nMantissa, nMinValue, nMaxValue)) + return error("%s: Failed to get range proof info.", __func__); + + nValue = nMinValue; + return true; +} + +/** + * @brief Get the theoretical value of the RingCT stake. + * @return CAmount: the minimum value of the PublicRingCTStake. Returns 0 on fail. + * @see GetMinimumInputValue() + */ +CAmount PublicRingCTStake::GetValue() +{ + CAmount nValue = 0; + if (!GetMinimumInputValue(nValue)) + return 0; + + return nValue; +} + +/** + * @brief Gets the relative weight of the RingCT stake, based on its input. + * @return CAmount: the weighted value of the PublicRingCTStake. Returns 0 on fail. + */ +CAmount PublicRingCTStake::GetWeight() { + return GetRingCTWeightForValue(GetValue()); +} + +bool PublicRingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) +{ + if (!pindexChainPrev) + return false; + + return GetStakeModifier(nStakeModifier, *pindexChainPrev); +} + +/** + * @brief Get the inputs used for the RingCT transaction. This includes all inputs, including decoy inputs. + * @return std::vector: A vector of the outpoints that are inputs in the transaction. + */ +std::vector PublicRingCTStake::GetTxInputs() const +{ + return GetRingCtInputs(m_ptx->vin[0]); +} + +/** + * @brief Get a hash of the the key image for output 0. + * @param[out] hashPubKey: resulting hash of the key image + * @return bool: true upon success. false if there is a casting error when attempting to extract the key. + */ +bool PublicRingCTStake::GetPubkeyHash(uint256& hashPubKey) const +{ + //Extract the pubkeyhash from the keyimage + try { + const CTxIn &txin = m_ptx->vin[0]; + const std::vector vKeyImages = txin.scriptData.stack[0]; + uint32_t nInputs, nRingSize; + txin.GetAnonInfo(nInputs, nRingSize); + const CCmpPubKey &ki = *((CCmpPubKey *) &vKeyImages[0]); + hashPubKey = ki.GetHash(); + } catch (...) { + return error("%s: Deserialization of compressed pubkey failed.", __func__); + } + + return true; +} + +/** + * @brief Get the deterministic uniqueness of the RingCT output that is spent in this transaction. + * The uniqueness of a RingCT stake is a hash of the key image. + * + * @return CDataStream : uniqueness is serialized into a datastream object. + */ +CDataStream PublicRingCTStake::GetUniqueness() +{ + uint256 hashPubKey; + GetPubkeyHash(hashPubKey); + CDataStream ss(0,0); + ss << hashPubKey; + return ss; +} diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 761d98904c..58c962f73d 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -19,11 +19,18 @@ class CKeyStore; class CWallet; class CWalletTx; +enum StakeInputType +{ + STAKE_ZEROCOIN, + STAKE_RINGCT, +}; + class CStakeInput { protected: CBlockIndex* pindexFrom = nullptr; libzerocoin::CoinDenomination denom = libzerocoin::CoinDenomination::ZQ_ERROR; + StakeInputType nType = STAKE_RINGCT; public: virtual ~CStakeInput(){}; @@ -35,6 +42,7 @@ class CStakeInput virtual bool IsZerocoins() = 0; virtual CDataStream GetUniqueness() = 0; libzerocoin::CoinDenomination GetDenomination() {return denom;}; + StakeInputType GetType() const { return nType; } virtual bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) = 0; virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) = 0; @@ -94,6 +102,7 @@ class ZerocoinStake : public CStakeInput public: explicit ZerocoinStake(libzerocoin::CoinDenomination denom, const uint256& hashSerial) { + nType = STAKE_ZEROCOIN; this->denom = denom; this->hashSerial = hashSerial; this->pindexFrom = nullptr; @@ -122,4 +131,42 @@ class ZerocoinStake : public CStakeInput static int HeightToModifierHeight(int nHeight); }; + +/** + * @brief A RingCt output that has been published to the blockchain in a coinstake transaction. + * @note The PublicRingCTStake contains no private information about the RingCt output. The data that a PublicRingCTStake + * reveals that would not have been revealed in a typical RingCt transaction is a narrowed range of values in the rangeproof. + * An object of this class is safe to communicate to peers, but should only exist inside of a block and not as a loose transaction. + */ +class PublicRingCTStake : public CStakeInput +{ +private: + //! The CTransactionRef that is the coinstake transaction containing this CStakeInput + CTransactionRef m_ptx; + +public: + explicit PublicRingCTStake(const CTransactionRef& txStake) : m_ptx(txStake) {} + + // None of these are implemented, intentionally. + CBlockIndex* GetIndexFrom() override { return nullptr; } + bool IsZerocoins() override { return false; } + bool GetTxFrom(CTransaction& tx) override { return false; } + bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override { return false; } + bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override { return false; } + bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override {return false; } + bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override { return false; } + + CAmount GetValue() override; + CAmount GetWeight() override; + CDataStream GetUniqueness() override; + bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; + + // PublicRingCt specific items + std::vector GetTxInputs() const; + +private: + bool GetMinimumInputValue(CAmount& nValue) const; + bool GetPubkeyHash(uint256& hashPubKey) const; +}; + #endif //PIVX_STAKEINPUT_H diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index f762f78cca..3107250dbb 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -1640,8 +1640,8 @@ bool AnonWallet::AddCTData(CTxOutBase *txout, CTempRecipient &r, std::string &sE if (r.fOverwriteRangeProofParams == true) { min_value = r.min_value; - ct_exponent = r.ct_exponent || ct_exponent; - ct_bits = r.ct_bits || ct_bits; + ct_exponent = r.ct_exponent >= 0 ? r.ct_exponent : ct_exponent; + ct_bits = r.ct_bits >= 0 ? r.ct_bits : ct_bits; } if (1 != secp256k1_rangeproof_sign(secp256k1_ctx_blind, @@ -5500,7 +5500,8 @@ bool AnonWallet::ScanForOwnedOutputs(const CTransaction &tx, size_t &nCT, size_t continue; } else if (txout->IsType(OUTPUT_STANDARD)) { - if (nOutputId < (int)tx.vpout.size()-1 + if (!tx.IsCoinStake() + && nOutputId < (int)tx.vpout.size()-1 && tx.vpout[nOutputId+1]->IsType(OUTPUT_DATA)) { CTxOutData *txd = (CTxOutData*) tx.vpout[nOutputId+1].get(); @@ -6421,7 +6422,7 @@ bool AnonWallet::AddToRecord(CTransactionRecord &rtxIn, const CTransaction &tx, mapLockedRecords.erase(txhash); // Plain to plain will always be a wtx, revisit if adding p2p to rtx - if (!tx.GetCTFee(rtx.nFee)) + if (!tx.IsCoinBase() && !tx.IsCoinStake() && !tx.GetCTFee(rtx.nFee)) LogPrintf("%s: ERROR - GetCTFee failed %s.\n", __func__, txhash.ToString()); // If txn has change, it must have been sent by this wallet diff --git a/src/veil/ringct/blind.cpp b/src/veil/ringct/blind.cpp index 9ef52a3f8f..4cf9e6a7be 100644 --- a/src/veil/ringct/blind.cpp +++ b/src/veil/ringct/blind.cpp @@ -108,11 +108,11 @@ int SelectRangeProofParameters(uint64_t nValueIn, uint64_t &minValue, int &expon return 0; }; -int GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value) +bool GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value) { - return (!(secp256k1_rangeproof_info(secp256k1_ctx_blind, + return 1 == secp256k1_rangeproof_info(secp256k1_ctx_blind, &rexp, &rmantissa, (uint64_t*) &min_value, (uint64_t*) &max_value, - &vRangeproof[0], vRangeproof.size()) == 1)); + &vRangeproof[0], vRangeproof.size()); }; void ECC_Start_Blinding() diff --git a/src/veil/ringct/blind.h b/src/veil/ringct/blind.h index 406c241bd7..86462b802c 100644 --- a/src/veil/ringct/blind.h +++ b/src/veil/ringct/blind.h @@ -14,7 +14,7 @@ extern secp256k1_context *secp256k1_ctx_blind; int SelectRangeProofParameters(uint64_t nValueIn, uint64_t &minValue, int &exponent, int &nBits); -int GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value); +bool GetRangeProofInfo(const std::vector &vRangeproof, int &rexp, int &rmantissa, CAmount &min_value, CAmount &max_value); void ECC_Start_Blinding(); void ECC_Stop_Blinding(); diff --git a/src/veil/ringct/temprecipient.h b/src/veil/ringct/temprecipient.h index b0a44ac981..3053cbe37e 100644 --- a/src/veil/ringct/temprecipient.h +++ b/src/veil/ringct/temprecipient.h @@ -61,9 +61,9 @@ class CTempRecipient // TODO: range proof parameters, try to keep similar for fee // Allow an overwrite of the parameters. bool fOverwriteRangeProofParams = false; - uint64_t min_value; - int ct_exponent; - int ct_bits; + uint64_t min_value = 0; + int ct_exponent = -1; + int ct_bits = -1; CKey sEphem; CPubKey pkTo; From 4787fba2566c3378c75917be5cf2a744661ab643 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 29 Oct 2022 12:01:48 -0700 Subject: [PATCH 18/25] Alter some regtest params. --- src/chainparams.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ac3d58a4cb..db303b1fc0 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -759,7 +759,7 @@ class CRegTestParams : public CChainParams { // ProgPow, RandomX, Sha256d consensus.nProgPowTargetSpacing = 172; consensus.nRandomXTargetSpacing = 600; - consensus.nSha256DTargetSpacing = 1200; + consensus.nSha256DTargetSpacing = 120; consensus.nDgwPastBlocks = 60; // number of blocks to average in Dark Gravity Wave consensus.nDgwPastBlocks_old = 60; // number of blocks to average in Dark Gravity Wave @@ -879,7 +879,7 @@ class CRegTestParams : public CChainParams { /** RingCT/Stealth **/ nDefaultRingSize = 11; - nHeightLightZerocoin = 500; + nHeightLightZerocoin = 110; nZerocoinRequiredStakeDepthV2 = 10; //The required confirmations for a zerocoin to be stakable nHeightEnforceBlacklist = 0; nHeightRingCTStaking = 300; From 52e904f43a94c684293f61643149eb02f519a15b Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 29 Oct 2022 15:48:55 -0700 Subject: [PATCH 19/25] RingCT Staking: Make hash proof match. - Stake uniqueness is the hash of the public key of the input. - Stake indexfrom is the block index at the minimum stake depth. --- src/validation.cpp | 2 +- src/veil/proofofstake/kernel.cpp | 8 ++-- src/veil/proofofstake/stakeinput.cpp | 34 +++++++++------ src/veil/proofofstake/stakeinput.h | 22 ++++++---- src/veil/zerocoin/accumulators.cpp | 2 +- src/veil/zerocoin/accumulators.h | 2 +- src/wallet/wallet.cpp | 62 +++++++++++++++++++++++++--- src/wallet/wallet.h | 4 +- 8 files changed, 102 insertions(+), 34 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 4f0d668838..0f915ec008 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4984,7 +4984,7 @@ bool CChainState::ContextualCheckRingCTStake(CBlockIndex* pindex, PublicRingCTSt bool CChainState::ContextualCheckZerocoinStake(CBlockIndex* pindex, ZerocoinStake* stake) { - CBlockIndex* pindexFrom = stake->GetIndexFrom(); + const CBlockIndex* pindexFrom = stake->GetIndexFrom(pindex); if (!pindexFrom) return error("%s: failed to get index associated with zerocoin stake checksum", __func__); diff --git a/src/veil/proofofstake/kernel.cpp b/src/veil/proofofstake/kernel.cpp index c2418aca84..3eae646496 100644 --- a/src/veil/proofofstake/kernel.cpp +++ b/src/veil/proofofstake/kernel.cpp @@ -40,7 +40,7 @@ bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t CDataStream ss(SER_GETHASH, 0); ss << nStakeModifier << nTimeBlockFrom << ssUniqueID << nTimeTx; hashProofOfStake = Hash(ss.begin(), ss.end()); - //LogPrintf("%s: modifier:%d nTimeBlockFrom:%d nTimeTx:%d hash:%s\n", __func__, nStakeModifier, nTimeBlockFrom, nTimeTx, hashProofOfStake.GetHex()); + //LogPrintf("%s: modifier:%d nTimeBlockFrom:%d nTimeTx:%d u:%s hash:%s\n", __func__, nStakeModifier, nTimeBlockFrom, nTimeTx, Hash(ssUniqueID.begin(), ssUniqueID.end()).GetHex(), hashProofOfStake.GetHex()); return stakeTargetHit(UintToArith256(hashProofOfStake), nValueIn, UintToArith256(bnTarget)); } @@ -122,7 +122,7 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co const CTxIn& txin = txRef->vin[0]; - CBlockIndex* pindexFrom; + const CBlockIndex* pindexFrom; if (txin.IsZerocoinSpend()) { auto spend = TxInToZerocoinSpend(txin); if (!spend) @@ -132,10 +132,10 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co return error("%s: spend is using the wrong SpendType (%d)", __func__, (int)spend->getSpendType()); stake = std::unique_ptr(new ZerocoinStake(*spend)); - pindexFrom = stake->GetIndexFrom(); + pindexFrom = stake->GetIndexFrom(pindexCheck->pprev); } else if (txin.IsAnonInput()) { stake = std::unique_ptr(new PublicRingCTStake(txRef)); - pindexFrom = pindexCheck->GetAncestor(pindexCheck->nHeight - Params().RingCT_RequiredStakeDepth()); + pindexFrom = stake->GetIndexFrom(pindexCheck->pprev); } else { return error("%s: Stake is not a zerocoin or ringctspend", __func__); } diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index edb579d4c8..6ab6fa5b42 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -175,20 +175,19 @@ CAmount GetRingCTWeightForValue(const CAmount& nValueIn) { } } -CBlockIndex* RingCTStake::GetIndexFrom() +// To keep the input private, we use the stake depth as the index from. +const CBlockIndex* RingCTStake::GetIndexFrom(const CBlockIndex* pindexPrev) { if (pindexFrom) return pindexFrom; - if (coin.nDepth > 0) - pindexFrom = LookupBlockIndex(coin.rtx->second.blockHash); + pindexFrom = pindexPrev->GetAncestor(pindexPrev->nHeight - Params().RingCT_RequiredStakeDepth()); return pindexFrom; } bool RingCTStake::GetTxFrom(CTransaction& tx) { - // TODO return false; } @@ -250,10 +249,9 @@ bool RingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pinde CDataStream RingCTStake::GetUniqueness() { - //The unique identifier for a VEIL RingCT txo is... txhash + n? - CDataStream ss(SER_GETHASH, 0); - ss << coin.txhash; - ss << coin.i; + //The unique identifier for a VEIL RingCT txo is the hash of the keyimage pubkey. + CDataStream ss(0, 0); + ss << hashPubKey; return ss; } @@ -410,7 +408,7 @@ uint256 ZerocoinStake::GetChecksum() // The Zerocoin block index is the first appearance of the accumulator checksum that was used in the spend // note that this also means when staking that this checksum should be from a block that is beyond 60 minutes old and // 100 blocks deep. -CBlockIndex* ZerocoinStake::GetIndexFrom() +const CBlockIndex* ZerocoinStake::GetIndexFrom(const CBlockIndex* pindexPrev) { if (pindexFrom) return pindexFrom; @@ -461,7 +459,7 @@ int ZerocoinStake::HeightToModifierHeight(int nHeight) //Use the first accumulator checkpoint that occurs 60 minutes after the block being staked from bool ZerocoinStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) { - CBlockIndex* pindex = GetIndexFrom(); + const CBlockIndex* pindex = GetIndexFrom(pindexChainPrev); if (!pindex || !pindexChainPrev) { return false; @@ -483,7 +481,7 @@ bool ZerocoinStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pin pindex = pindex->pprev; } - nStakeModifier = UintToArith256(pindex->mapAccumulatorHashes[denom]).GetLow64(); + nStakeModifier = UintToArith256(pindex->mapAccumulatorHashes.at(denom)).GetLow64(); return true; } @@ -504,7 +502,7 @@ bool ZerocoinStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) #ifdef ENABLE_WALLET } - CBlockIndex* pindexCheckpoint = GetIndexFrom(); + const CBlockIndex* pindexCheckpoint = GetIndexFrom(nullptr); if (!pindexCheckpoint) return error("%s: failed to find checkpoint block index", __func__); @@ -517,7 +515,7 @@ bool ZerocoinStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) int nSecurityLevel = 100; CZerocoinSpendReceipt receipt; - if (!pwallet->MintToTxIn(mint, nSecurityLevel, hashTxOut, txIn, receipt, libzerocoin::SpendType::STAKE, GetIndexFrom())) + if (!pwallet->MintToTxIn(mint, nSecurityLevel, hashTxOut, txIn, receipt, libzerocoin::SpendType::STAKE, pindexCheckpoint)) return error("%s\n", receipt.GetStatusMessage()); return true; @@ -659,6 +657,16 @@ bool PublicRingCTStake::GetMinimumInputValue(CAmount& nValue) const return true; } +const CBlockIndex* PublicRingCTStake::GetIndexFrom(const CBlockIndex* pindexPrev) +{ + if (pindexFrom) + return pindexFrom; + + pindexFrom = pindexPrev->GetAncestor(pindexPrev->nHeight - Params().RingCT_RequiredStakeDepth()); + + return pindexFrom; +} + /** * @brief Get the theoretical value of the RingCT stake. * @return CAmount: the minimum value of the PublicRingCTStake. Returns 0 on fail. diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 58c962f73d..b564f6ee10 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -28,13 +28,13 @@ enum StakeInputType class CStakeInput { protected: - CBlockIndex* pindexFrom = nullptr; + const CBlockIndex* pindexFrom = nullptr; libzerocoin::CoinDenomination denom = libzerocoin::CoinDenomination::ZQ_ERROR; StakeInputType nType = STAKE_RINGCT; public: virtual ~CStakeInput(){}; - virtual CBlockIndex* GetIndexFrom() = 0; + virtual const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) = 0; virtual bool GetTxFrom(CTransaction& tx) = 0; virtual CAmount GetValue() = 0; virtual CAmount GetWeight() = 0; @@ -65,12 +65,16 @@ class RingCTStake : public CStakeInput veil_ringct::TransactionOutputsSigContext tx_outCtx; CTransactionRecord rtx; + // A hash of the key image of the RingCt output. This is used for the CStakeInput's uniqueness. + uint256 hashPubKey; + CAmount GetBracketMinValue(); public: - explicit RingCTStake(const COutputR& coin_) : coin(coin_), tx_inCtx(RING_SIZE, 2) { } + explicit RingCTStake(const COutputR& coin_, uint256 hashPubKey_) + : coin(coin_), tx_inCtx(RING_SIZE, 2), hashPubKey(hashPubKey_) {} - CBlockIndex* GetIndexFrom() override; + const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; bool GetTxFrom(CTransaction& tx) override; CAmount GetValue() override; CAmount GetWeight() override; @@ -111,7 +115,7 @@ class ZerocoinStake : public CStakeInput explicit ZerocoinStake(const libzerocoin::CoinSpend& spend); - CBlockIndex* GetIndexFrom() override; + const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; bool GetTxFrom(CTransaction& tx) override; CAmount GetValue() override; CAmount GetWeight() override; @@ -148,7 +152,6 @@ class PublicRingCTStake : public CStakeInput explicit PublicRingCTStake(const CTransactionRef& txStake) : m_ptx(txStake) {} // None of these are implemented, intentionally. - CBlockIndex* GetIndexFrom() override { return nullptr; } bool IsZerocoins() override { return false; } bool GetTxFrom(CTransaction& tx) override { return false; } bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override { return false; } @@ -156,11 +159,16 @@ class PublicRingCTStake : public CStakeInput bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override {return false; } bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override { return false; } + // Most of these are similar to RingCTStake's. TODO: maybe we could have + // a base ringctstake class instead of duplicating. + const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; CAmount GetValue() override; CAmount GetWeight() override; - CDataStream GetUniqueness() override; bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; + // Uses the transaction embedded + CDataStream GetUniqueness() override; + // PublicRingCt specific items std::vector GetTxInputs() const; diff --git a/src/veil/zerocoin/accumulators.cpp b/src/veil/zerocoin/accumulators.cpp index cd7362dea6..96b334c6bc 100644 --- a/src/veil/zerocoin/accumulators.cpp +++ b/src/veil/zerocoin/accumulators.cpp @@ -372,7 +372,7 @@ bool GetAccumulatorValue(int& nHeight, const libzerocoin::CoinDenomination denom } bool GenerateAccumulatorWitness(const PublicCoin &coin, Accumulator& accumulator, AccumulatorWitness& witness, - int nSecurityLevel, int& nMintsAdded, std::string& strError, CBlockIndex* pindexCheckpoint) + int nSecurityLevel, int& nMintsAdded, std::string& strError, const CBlockIndex* pindexCheckpoint) { LogPrintf("%s: generating\n", __func__); CBlockIndex* pindex = nullptr; diff --git a/src/veil/zerocoin/accumulators.h b/src/veil/zerocoin/accumulators.h index 44262b99a8..9461c07934 100644 --- a/src/veil/zerocoin/accumulators.h +++ b/src/veil/zerocoin/accumulators.h @@ -16,7 +16,7 @@ class CBlockIndex; std::map GetMintMaturityHeight(); -bool GenerateAccumulatorWitness(const libzerocoin::PublicCoin &coin, libzerocoin::Accumulator& accumulator, libzerocoin::AccumulatorWitness& witness, int nSecurityLevel, int& nMintsAdded, std::string& strError, CBlockIndex* pindexCheckpoint = nullptr); +bool GenerateAccumulatorWitness(const libzerocoin::PublicCoin &coin, libzerocoin::Accumulator& accumulator, libzerocoin::AccumulatorWitness& witness, int nSecurityLevel, int& nMintsAdded, std::string& strError, const CBlockIndex* pindexCheckpoint = nullptr); bool GetAccumulatorValueFromDB(uint256 nCheckpoint, libzerocoin::CoinDenomination denom, CBigNum& bnAccValue); bool GetAccumulatorValueFromChecksum(const uint256& hashChecksum, bool fMemoryOnly, CBigNum& bnAccValue); void AddAccumulatorChecksum(const uint256 nChecksum, const CBigNum &bnValue, bool fMemoryOnly); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5a9ce6ea60..568717f637 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -29,12 +29,13 @@ #include #include #include +#include #include #include +#include +#include #include -#include #include -#include #include #include #include @@ -3884,7 +3885,7 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, return false; //Small sleep if too far back on timing - if (GetAdjustedTime() - chainActive.Tip()->GetBlockTime() < 1) + if (GetAdjustedTime() - pindexBest->GetBlockTime() < 1) UninterruptibleSleep(std::chrono::milliseconds{2500}); CScript scriptPubKeyKernel; @@ -3894,7 +3895,7 @@ bool CWallet::CreateCoinStake(const CBlockIndex* pindexBest, unsigned int nBits, if (IsLocked() || ShutdownRequested()) return false; - CBlockIndex *pindexFrom = stakeInput->GetIndexFrom(); + const CBlockIndex *pindexFrom = stakeInput->GetIndexFrom(pindexBest); if (!pindexFrom || pindexFrom->nHeight < 1) { LogPrintf("*** no pindexfrom\n"); continue; @@ -4037,6 +4038,49 @@ bool CWallet::StakeableRingCTCoins() return false; } +bool CWallet::GetKeyImageForCoin(const COutputR& coin, AnonWalletDB& wdb, CCmpPubKey& keyimage) +{ + // Get pubkey hash for the StakeInput. + CStoredTransaction stx; + if (!wdb.ReadStoredTx(coin.txhash, stx)) { + LogPrint(BCLog::STAKING, "Failed to read stored transaction %s\n", coin.txhash.GetHex()); + return false; + } + + if (!stx.tx->vpout[coin.i]->IsType(OUTPUT_RINGCT)) { + LogPrint(BCLog::STAKING, "Output %d somehow not a RingCT output, tx=%s\n", coin.i, coin.txhash.GetHex()); + return false; + } + + CTxOutRingCT* txout = (CTxOutRingCT*)stx.tx->vpout[coin.i].get(); + CCmpPubKey pk = txout->pk; + int64_t index; + if (!pblocktree->ReadRCTOutputLink(pk, index)) { + LogPrint(BCLog::STAKING, "RingCT public key not found in database: %s\n", HexStr(pk.begin(), pk.end())); + return false; + } + + CAnonOutput anonOutput; + if (!pblocktree->ReadRCTOutput(index, anonOutput)) { + LogPrint(BCLog::STAKING, "Output %d not found in database.\n", index); + return false; + } + + CKeyID idk = anonOutput.pubkey.GetID(); + CKey key; + if (!pAnonWalletMain->GetKey(idk, key)) { + LogPrint(BCLog::STAKING, "No key for output: %s\n", HexStr(anonOutput.pubkey.begin(), anonOutput.pubkey.end())); + return false; + } + + if (secp256k1_get_keyimage(secp256k1_ctx_blind, keyimage.ncbegin(), anonOutput.pubkey.begin(), key.begin()) != 0) { + LogPrint(BCLog::STAKING, "RingCT stake unable to get key image\n"); + return false; + } + + return true; +} + bool CWallet::SelectStakeCoins(std::list >& listInputs) { LOCK(cs_main); @@ -4044,6 +4088,7 @@ bool CWallet::SelectStakeCoins(std::list >& listInp std::vector vCoins; CCoinControl coincontrol; pAnonWalletMain->AvailableAnonCoins(vCoins, true, &coincontrol); + AnonWalletDB wdb(GetDBHandle()); // TODO: change required depth int nRequiredDepth = Params().Zerocoin_RequiredStakeDepth(); @@ -4052,7 +4097,12 @@ bool CWallet::SelectStakeCoins(std::list >& listInp for (auto coin : vCoins) { if (coin.nDepth >= nRequiredDepth) { - std::unique_ptr input(new RingCTStake(coin)); + // Get pubkey hash for the StakeInput. + CCmpPubKey keyimage; + if (!GetKeyImageForCoin(coin, wdb, keyimage)) + continue; + + std::unique_ptr input(new RingCTStake(coin, keyimage.GetHash())); listInputs.push_back(std::move(input)); } } @@ -5749,7 +5799,7 @@ CScript GetLargestContributor(std::set& setCoins) } bool CWallet::MintToTxIn(CZerocoinMint zerocoinSelected, int nSecurityLevel, const uint256& hashTxOut, CTxIn& newTxIn, - CZerocoinSpendReceipt& receipt, libzerocoin::SpendType spendType, CBlockIndex* pindexCheckpoint) + CZerocoinSpendReceipt& receipt, libzerocoin::SpendType spendType, const CBlockIndex* pindexCheckpoint) { auto hashSerial = GetSerialHash(zerocoinSelected.GetSerialNumber()); CMintMeta meta = zTracker->Get(hashSerial); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index b3a430e7c7..00ef13ce54 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -97,6 +97,7 @@ class CzWallet; struct FeeCalculation; enum class FeeEstimateMode; class AnonWallet; +class AnonWalletDB; class CTransactionRecord; /** (client) version numbers for particular wallet features */ @@ -839,7 +840,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface CZerocoinSpendReceipt& receipt, std::vector& vSelectedMints, std::vector& vNewMints, bool fMintChange, bool fMinimizeChange, CTxDestination* address = NULL); bool MintToTxIn(CZerocoinMint zerocoinSelected, int nSecurityLevel, const uint256& hashTxOut, CTxIn& newTxIn, - CZerocoinSpendReceipt& receipt, libzerocoin::SpendType spendType, CBlockIndex* pindexCheckpoint = nullptr); + CZerocoinSpendReceipt& receipt, libzerocoin::SpendType spendType, const CBlockIndex* pindexCheckpoint = nullptr); std::string MintZerocoinFromOutPoint(CAmount nValue, CWalletTx& wtxNew, std::vector& vDMints, const std::vector vOutpts); std::string MintZerocoin(CAmount nValue, CWalletTx& wtxNew, std::vector& vDMints, OutputTypes inputtype, @@ -1104,6 +1105,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface bool CreateRingCTStake(const CBlockIndex* pindexBest, unsigned int nBits, CMutableTransaction& txNew, unsigned int& nTxNewTime, int64_t& nComputeTimeStart, CMutableTransaction& txCoinbase); bool SelectStakeCoins(std::list >& listInputs); + bool GetKeyImageForCoin(const COutputR& coin, AnonWalletDB& wdb, CCmpPubKey& keyimage); bool SelectStakeCoins(std::list >& listInputs); bool StakeableRingCTCoins(); From 9156bd4552fc3d2a611510c59878c6e30e9bc2cb Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 29 Oct 2022 18:26:03 -0700 Subject: [PATCH 20/25] RingCT Stake: Rangeproof the min. allowed weight. As the stake weight controls the hash difficulty ceiling, we can simply choose the lowest bracket in which the stake hash comes under the ceiling, and use that as our rangeproof min_value for added privacy. - Fix overflow detection when multiplying arith_uint256. Otherwise regtest stake targets can be a little wonky. - Remove an unnecessary back-and-forth cast to uint256. - The RingCT stake bracket weights are basically constants, so do that. --- src/arith_uint256.cpp | 13 ++++- src/arith_uint256.h | 1 + src/veil/proofofstake/kernel.cpp | 16 +++-- src/veil/proofofstake/kernel.h | 2 +- src/veil/proofofstake/stakeinput.cpp | 87 ++++++++++++++++++---------- src/veil/proofofstake/stakeinput.h | 4 +- 6 files changed, 84 insertions(+), 39 deletions(-) diff --git a/src/arith_uint256.cpp b/src/arith_uint256.cpp index 28e1696f49..416ef5a7ac 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -66,7 +66,7 @@ base_uint& base_uint::operator*=(uint32_t b32) } template -base_uint& base_uint::operator*=(const base_uint& b) +base_uint& base_uint::safeMultiply(const base_uint& b, bool& overflow) { base_uint a; for (int j = 0; j < WIDTH; j++) { @@ -76,11 +76,20 @@ base_uint& base_uint::operator*=(const base_uint& b) a.pn[i + j] = n & 0xffffffff; carry = n >> 32; } + // j > 0 implies we hit i + j == WIDTH before i == WIDTH + overflow |= carry > 0 || (j > 0 && pn[j] > 0 && b.pn[WIDTH - j] > 0); } *this = a; return *this; } +template +base_uint& base_uint::operator*=(const base_uint& b) +{ + bool overflow = true; + return safeMultiply(b, overflow); +} + template base_uint& base_uint::operator/=(const base_uint& b) { @@ -188,6 +197,7 @@ unsigned int base_uint::bits() const template base_uint<256>::base_uint(const std::string&); template base_uint<256>& base_uint<256>::operator<<=(unsigned int); template base_uint<256>& base_uint<256>::operator>>=(unsigned int); +template base_uint<256>& base_uint<256>::safeMultiply(const base_uint<256>& b, bool& overflow); template base_uint<256>& base_uint<256>::operator*=(uint32_t b32); template base_uint<256>& base_uint<256>::operator*=(const base_uint<256>& b); template base_uint<256>& base_uint<256>::operator/=(const base_uint<256>& b); @@ -203,6 +213,7 @@ template unsigned int base_uint<256>::bits() const; template base_uint<512>::base_uint(const std::string&); template base_uint<512>& base_uint<512>::operator<<=(unsigned int); template base_uint<512>& base_uint<512>::operator>>=(unsigned int); +template base_uint<512>& base_uint<512>::safeMultiply(const base_uint<512>& b, bool& overflow); template base_uint<512>& base_uint<512>::operator*=(uint32_t b32); template base_uint<512>& base_uint<512>::operator*=(const base_uint<512>& b); template base_uint<512>& base_uint<512>::operator/=(const base_uint<512>& b); diff --git a/src/arith_uint256.h b/src/arith_uint256.h index 52c7a61d9d..7a023823a3 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -166,6 +166,7 @@ class base_uint return *this; } + base_uint& safeMultiply(const base_uint& b, bool& overflow); base_uint& operator*=(uint32_t b32); base_uint& operator*=(const base_uint& b); base_uint& operator/=(const base_uint& b); diff --git a/src/veil/proofofstake/kernel.cpp b/src/veil/proofofstake/kernel.cpp index 3eae646496..eb133dede2 100644 --- a/src/veil/proofofstake/kernel.cpp +++ b/src/veil/proofofstake/kernel.cpp @@ -24,17 +24,19 @@ using namespace std; bool stakeTargetHit(arith_uint256 hashProofOfStake, int64_t nValueIn, arith_uint256 bnTargetPerCoinDay) { //get the stake weight - weight is equal to coin amount - arith_uint256 bnTarget = arith_uint256(nValueIn) * bnTargetPerCoinDay; + arith_uint256 bnTarget(nValueIn); + bool overflow = false; + bnTarget.safeMultiply(bnTargetPerCoinDay, overflow); //Double check for overflow, give max value if overflow - if (bnTargetPerCoinDay > bnTarget) + if (overflow) bnTarget = ~arith_uint256(); // Now check if proof-of-stake hash meets target protocol return hashProofOfStake < bnTarget; } -bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const uint256& bnTarget, +bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const arith_uint256& bnTarget, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake) { CDataStream ss(SER_GETHASH, 0); @@ -42,7 +44,7 @@ bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t hashProofOfStake = Hash(ss.begin(), ss.end()); //LogPrintf("%s: modifier:%d nTimeBlockFrom:%d nTimeTx:%d u:%s hash:%s\n", __func__, nStakeModifier, nTimeBlockFrom, nTimeTx, Hash(ssUniqueID.begin(), ssUniqueID.end()).GetHex(), hashProofOfStake.GetHex()); - return stakeTargetHit(UintToArith256(hashProofOfStake), nValueIn, UintToArith256(bnTarget)); + return stakeTargetHit(UintToArith256(hashProofOfStake), nValueIn, bnTarget); } @@ -92,9 +94,11 @@ bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockF i++; // if stake hash does not meet the target then continue to next iteration - if (!CheckStake(ssUniqueID, nValueIn, nStakeModifier, ArithToUint256(bnTargetPerCoinDay), nTimeBlockFrom, nTryTime, hashProofOfStake)) + if (!CheckStake(ssUniqueID, nValueIn, nStakeModifier, bnTargetPerCoinDay, nTimeBlockFrom, nTryTime, hashProofOfStake)) continue; + stakeInput->OnStakeFound(bnTargetPerCoinDay, hashProofOfStake); + if (setFoundStakes.count(hashProofOfStake)) continue; setFoundStakes.emplace(hashProofOfStake); @@ -168,7 +172,7 @@ bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, co if (nValue == 0) return error("%s: coinstake %s has no stake weight\n", __func__, txRef->GetHash().GetHex()); - if (!CheckStake(stake->GetUniqueness(), nValue, nStakeModifier, ArithToUint256(bnTargetPerCoinDay), nBlockFromTime, + if (!CheckStake(stake->GetUniqueness(), nValue, nStakeModifier, bnTargetPerCoinDay, nBlockFromTime, nTxTime, hashProofOfStake)) { return error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s \n", txRef->GetHash().GetHex(), hashProofOfStake.GetHex()); diff --git a/src/veil/proofofstake/kernel.h b/src/veil/proofofstake/kernel.h index a38520285e..8a4324530e 100644 --- a/src/veil/proofofstake/kernel.h +++ b/src/veil/proofofstake/kernel.h @@ -9,7 +9,7 @@ #include "validation.h" #include "stakeinput.h" -bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const uint256& bnTarget, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake); +bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const arith_uint256& bnTarget, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake); bool stakeTargetHit(arith_uint256 hashProofOfStake, int64_t nValueIn, arith_uint256 bnTargetPerCoinDay); bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, const CBlockIndex* pindexBest, uint256& hashProofOfStake, bool fWeightStake); bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, const uint32_t& nBits, const unsigned int& nTimeBlock, uint256& hashProofOfStake, std::unique_ptr& stake); diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 6ab6fa5b42..588b3e0c33 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -130,7 +130,7 @@ const uint64_t BRACKETBASE = 16; const uint32_t LOG2BRACKETBASE = 4; const CAmount nBareMinStake = BRACKETBASE; -const CAmount nOneSat = 1; +constexpr CAmount nOneSat = 1; bool CheckMinStake(const CAmount& nAmount) { @@ -140,39 +140,33 @@ bool CheckMinStake(const CAmount& nAmount) return true; } -CAmount GetRingCTWeightForValue(const CAmount& nValueIn) { +static constexpr size_t RingCTNumBuckets = 13; +static constexpr uint64_t RingCTWeightsByBracket[RingCTNumBuckets + 1] = { + 0, + (1ULL << (4 * 1)) + nOneSat, + (1ULL << (4 * 2)) + nOneSat, + (1ULL << (4 * 3)) + nOneSat, + (1ULL << (4 * 4)) + nOneSat, + (1ULL << (4 * 5)) + nOneSat, + (1ULL << (4 * 6)) + nOneSat, + (1ULL << (4 * 7)) + nOneSat, + (((1ULL << (4 * 8)) + nOneSat) * 95) / 100, + (((1ULL << (4 * 9)) + nOneSat) * 91) / 100, + (((1ULL << (4 * 10)) + nOneSat) * 71) / 100, + (((1ULL << (4 * 11)) + nOneSat) * 5) / 10, + (((1ULL << (4 * 12)) + nOneSat) * 3) / 10, + ((1ULL << (4 * 13)) + nOneSat) / 10, + }; + +CAmount GetRingCTWeightForValue(const CAmount& nValueIn) +{ // fast mode if (nValueIn <= nBareMinStake) return 0; // bracket is at least 1 now. int bracket = fast_log16(nValueIn - nOneSat); - // We'd do 16 << (4 * (bracket - 1)) but 16 is 1 << 4 so it's really - // 1 << (4 + 4 * bracket - 4) - CAmount val = (1ULL << (4 * bracket)) + nOneSat; - - switch (bracket) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - return val; - case 8: - return (val * 95) / 100; - case 9: - return (val * 91) / 100; - case 10: - return (val * 71) / 100; - case 11: - return (val * 5) / 10; - case 12: - return (val * 3) / 10; - default: - return val / 10; - } + return RingCTWeightsByBracket[bracket > RingCTNumBuckets ? RingCTNumBuckets : bracket]; } // To keep the input private, we use the stake depth as the index from. @@ -235,8 +229,41 @@ CAmount RingCTStake::GetBracketMinValue() } // We further reduce the weights of higher brackets to match zerocoin reductions. -CAmount RingCTStake::GetWeight() { - return GetRingCTWeightForValue(GetValue()); +CAmount RingCTStake::GetWeight() +{ + if (nWeight == 0) { + nWeight = GetRingCTWeightForValue(GetValue()); + } + return nWeight; +} + +void RingCTStake::OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) +{ + CAmount nValueIn = GetValue(); + // fast mode + if (nValueIn <= nBareMinStake) + return; + + // bracket is at least 1 now. + arith_uint256 hashProof = UintToArith256(hashProofOfStake); + int bracket = fast_log16(nValueIn - nOneSat); + if (bracket > RingCTNumBuckets) + bracket = RingCTNumBuckets; + for (--bracket; bracket > 0; --bracket) { + arith_uint256 newTarget(RingCTWeightsByBracket[bracket]); + bool overflow = false; + newTarget.safeMultiply(bnTarget, overflow); + // in case of overflow, the real target is MAX_UINT256, which is an auto-pass. + if (overflow) { + continue; + } + // Stop when we find a bucket that fails to meet the target. + if (hashProof >= newTarget) { + break; + } + } + ++bracket; + nWeight = RingCTWeightsByBracket[bracket]; } bool RingCTStake::GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index b564f6ee10..330befd5f3 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -48,6 +48,7 @@ class CStakeInput virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) = 0; virtual bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) = 0; virtual bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) = 0; + virtual void OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) {} }; @@ -59,6 +60,7 @@ class RingCTStake : public CStakeInput // Contains: depth (coin.nDepth), txo idx (coin.i), output record (coin.rtx->second) // A copy is required for lifetime. const COutputR coin; + CAmount nWeight = 0; // Details for constructing a tx from this stake input. veil_ringct::TransactionInputsSigContext tx_inCtx; @@ -88,7 +90,7 @@ class RingCTStake : public CStakeInput bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; - + void OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) override; bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override; }; From d6af2ecd60edf721d6a96af720c93c34c4b7e294 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 29 Oct 2022 19:54:29 -0700 Subject: [PATCH 21/25] RingCT Stake: Cleanup Add enable_wallet guards to similar places as existing ones. Remove unused stake functions from early attempts. --- src/miner.cpp | 9 +- src/veil/proofofstake/stakeinput.cpp | 58 +++------ src/veil/proofofstake/stakeinput.h | 17 +-- src/veil/ringct/anonwallet.cpp | 182 --------------------------- src/veil/ringct/anonwallet.h | 4 - 5 files changed, 24 insertions(+), 246 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index a91ebb0e39..32e0307f69 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -554,13 +554,16 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc } #endif } else if (pblock->vtx[1]->IsRingCtSpend()) { - // TODO: enable_wallet guards. COutPoint prevout; // Get keyimage from the input - if (!pwalletMain->GetAnonWallet()->IsMyAnonInput(pblock->vtx[1]->vin[0], prevout, key)) { - LogPrint(BCLog::STAKING, "Failed to get key from anon spend\n"); +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET) || !pwalletMain->GetAnonWallet()->IsMyAnonInput(pblock->vtx[1]->vin[0], prevout, key)) { +#endif + LogPrint(BCLog::STAKING, "%s: Failed to get key from anon spend\n", __func__); return nullptr; +#ifdef ENABLE_WALLET } +#endif } else { error("%s: invalid block created. Stake is neither zerocoin nor anon spend!", __func__); return nullptr; diff --git a/src/veil/proofofstake/stakeinput.cpp b/src/veil/proofofstake/stakeinput.cpp index 588b3e0c33..97de6608b4 100644 --- a/src/veil/proofofstake/stakeinput.cpp +++ b/src/veil/proofofstake/stakeinput.cpp @@ -282,50 +282,6 @@ CDataStream RingCTStake::GetUniqueness() return ss; } -bool RingCTStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) -{ -#ifdef ENABLE_WALLET - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { -#endif - return error("%s: wallet disabled", __func__); -#ifdef ENABLE_WALLET - } - - return pwallet->GetAnonWallet()->CoinToTxIn(coin, txIn, tx_inCtx, RING_SIZE); -#endif -} - -bool RingCTStake::CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nReward) -{ -#ifdef ENABLE_WALLET - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { -#endif - return error("%s: wallet disabled", __func__); -#ifdef ENABLE_WALLET - } - - return pwallet->GetAnonWallet()->CreateStakeTxOuts(coin, vpout, GetValue(), nReward, GetBracketMinValue(), tx_outCtx, rtx); -#endif -} - -bool RingCTStake::CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) -{ -#ifdef ENABLE_WALLET - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { -#endif - return error("%s: wallet disabled", __func__); -#ifdef ENABLE_WALLET - } - - if (!pwallet->GetAnonWallet()->SignStakeTx(coin, txNew, tx_inCtx, tx_outCtx)) - return false; - - if (!MarkSpent(pwallet->GetAnonWallet(), txNew)) - return error("%s: failed to mark ringct input as used\n", __func__); - return true; -#endif -} - bool RingCTStake::MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew) { rtx.nFlags |= ORF_ANON_IN; @@ -352,6 +308,12 @@ bool RingCTStake::MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew) */ bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) { +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { +#endif + return error("%s: wallet disabled", __func__); +#ifdef ENABLE_WALLET + } AnonWallet* panonWallet = pwallet->GetAnonWallet(); // Update the coinbase tx @@ -392,6 +354,7 @@ bool RingCTStake::CreateCoinStake(CWallet* pwallet, const CAmount& nReward, CMut txCoinStake = CMutableTransaction(*wtx.tx); return true; +#endif } ZerocoinStake::ZerocoinStake(const libzerocoin::CoinSpend& spend) @@ -620,6 +583,12 @@ bool ZerocoinStake::MarkSpent(CWallet *pwallet, const uint256& txid) } bool ZerocoinStake::CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) { +#ifdef ENABLE_WALLET + if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { +#endif + return error("%s: wallet disabled", __func__); +#ifdef ENABLE_WALLET + } if (!CreateTxOuts(pwallet, txCoinStake.vpout, nBlockReward)) { LogPrintf("%s : failed to get scriptPubKey\n", __func__); retryable = true; @@ -652,6 +621,7 @@ bool ZerocoinStake::CreateCoinStake(CWallet* pwallet, const CAmount& nBlockRewar return false; } return true; +#endif } /** diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 330befd5f3..65e266f750 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -44,9 +44,6 @@ class CStakeInput libzerocoin::CoinDenomination GetDenomination() {return denom;}; StakeInputType GetType() const { return nType; } - virtual bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) = 0; - virtual bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) = 0; - virtual bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) = 0; virtual bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) = 0; virtual void OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) {} }; @@ -87,9 +84,6 @@ class RingCTStake : public CStakeInput bool MarkSpent(AnonWallet* panonwallet, CMutableTransaction& txNew); - bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; - bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; - bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; void OnStakeFound(const arith_uint256& bnTarget, const uint256& hashProofOfStake) override; bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override; }; @@ -123,9 +117,9 @@ class ZerocoinStake : public CStakeInput CAmount GetWeight() override; bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) override; CDataStream GetUniqueness() override; - bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override; - bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override; - bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override; + bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()); + bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal); + bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew); bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override; bool IsZerocoins() override { return true; } @@ -154,15 +148,12 @@ class PublicRingCTStake : public CStakeInput explicit PublicRingCTStake(const CTransactionRef& txStake) : m_ptx(txStake) {} // None of these are implemented, intentionally. - bool IsZerocoins() override { return false; } bool GetTxFrom(CTransaction& tx) override { return false; } - bool CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut = uint256()) override { return false; } - bool CreateTxOuts(CWallet* pwallet, std::vector& vpout, CAmount nTotal) override { return false; } - bool CompleteTx(CWallet* pwallet, CMutableTransaction& txNew) override {return false; } bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) override { return false; } // Most of these are similar to RingCTStake's. TODO: maybe we could have // a base ringctstake class instead of duplicating. + bool IsZerocoins() override { return false; } const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; CAmount GetValue() override; CAmount GetWeight() override; diff --git a/src/veil/ringct/anonwallet.cpp b/src/veil/ringct/anonwallet.cpp index 3107250dbb..966090a2cd 100644 --- a/src/veil/ringct/anonwallet.cpp +++ b/src/veil/ringct/anonwallet.cpp @@ -4187,188 +4187,6 @@ bool AnonWallet::AddCoinbaseRewards( return true; } -bool AnonWallet::CoinToTxIn( - const COutputR& coin, CTxIn& txin, veil_ringct::TransactionInputsSigContext& inCtx, - size_t nRingSize) -{ - // Mostly copied from AddAnonInputs_Inner - std::string sError; - int rv; - - txin.nSequence = CTxIn::SEQUENCE_FINAL; - txin.prevout.n = COutPoint::ANON_MARKER; - txin.SetAnonInfo(1, nRingSize); - - std::vector> vCoins; - vCoins.emplace_back(coin.rtx, coin.i); - - std::set setHave; // Anon prev-outputs can only be used once per transaction. - // Must add real outputs to setHave before picking decoys - uint32_t nSigInputs, nSigRingSize; - txin.GetAnonInfo(nSigInputs, nSigRingSize); - - // ArrangeBlinds part 1: place real output - inCtx.vInputBlinds.resize(32 * nSigInputs); - - if (!PlaceRealOutputs(inCtx.vMI, inCtx.secretColumn, nSigRingSize, setHave, vCoins, inCtx.vInputBlinds, sError)) { - return error("%s: %s", __func__, sError); - } - - // ArrangeBlinds part 2: hiding outputs (no dummy sigs) - if (!PickHidingOutputs(inCtx.vMI, inCtx.secretColumn, nSigRingSize, setHave, sError)) - return error("%s: %s", __func__, sError); - - - for (size_t k = 0; k < nSigInputs; ++k) - for (size_t i = 0; i < nSigRingSize; ++i) { - PutVarInt(inCtx.vPubkeyMatrixIndices, inCtx.vMI[k][i]); - } - - // Since we don't need the dummy sigs for fee calculation, - // just emplace empty vectors in the proper places. - txin.scriptWitness.stack.emplace_back(inCtx.vPubkeyMatrixIndices); - - // if sign - txin.scriptData.stack.emplace_back(33 * nSigInputs); - - // end ArrangeBlinds - - if (!GetKeyImage(txin, inCtx.vMI, inCtx.secretColumn, sError)) - return error("%s: %s", __func__, sError); - - // SetBlinds - // TODO: pass inCtx instead of its components? - if (!SetBlinds(nSigRingSize, nSigInputs, inCtx.vMI, inCtx.vsk, inCtx.vpsk, inCtx.vm, inCtx.vCommitments, - inCtx.vpInCommits, inCtx.vpBlinds, inCtx.vInputBlinds, - inCtx.secretColumn, sError)) - return error("%s: %s", __func__, sError); - - LogPrintf("Stake: set up tx ins\n"); - return true; -} - -bool AnonWallet::CreateStakeTxOuts( - const COutputR& coin, std::vector& vpout, - CAmount nInput, CAmount nReward, CAmount bracketMin, - veil_ringct::TransactionOutputsSigContext& outCtx, - CTransactionRecord& rtx) -{ - std::string sError; - std::vector vecOut; - - CTempRecipient stakeRet; - stakeRet.nType = OUTPUT_RINGCT; - stakeRet.fChange = false; - stakeRet.address = GetStealthStakeAddress(); - stakeRet.SetAmount(nInput); - stakeRet.isMine = true; - stakeRet.fExemptFeeSub = true; - // This should set the proof that our stake input is large enough when SetOutputs is called. - stakeRet.fOverwriteRangeProofParams = true; - stakeRet.min_value = (uint64_t)bracketMin; - stakeRet.ct_exponent = 0; - stakeRet.ct_bits = 0; - - int nChangePosInOut = -1; - - CTempRecipient reward; - reward.nType = OUTPUT_RINGCT; - reward.fChange = false; - reward.fExemptFeeSub = true; - reward.address = GetStealthStakeAddress(); - reward.SetAmount(nReward); - - vecOut.push_back(stakeRet); // already inserted via InsertChangeAddress - //vecOut.push_back(reward); - - Shuffle(vecOut.begin(), vecOut.end(), FastRandomContext()); - - if (!ExpandTempRecipients(vecOut, sError)) - return false; - - CAmount nValueOutPlain = 0; - - // SetOutputs - if (!SetOutputs(vpout, vecOut, 0, 0, nValueOutPlain, nChangePosInOut, true, false, true, false, sError)) { - return error("%s: %s", __func__, sError); - } - - // ArrangeOutBlinds - if (!ArrangeOutBlinds(vpout, vecOut, outCtx.vpOutCommits, outCtx.vpOutBlinds, outCtx.vBlindPlain, - &outCtx.plainCommitment, nValueOutPlain, -1, false, sError)) { - return error("%s: %s", __func__, sError); - } - - std::vector &vData = ((CTxOutData*)vpout[1].get())->vData; - vData.resize(1); - if (0 != PutVarInt(vData, 0)) { - sError = strprintf("Failed to add fee to stake transaction."); - return error("%s: %s", __func__, sError); - } - - AddOutputRecordMetaData(rtx, vecOut); - LogPrintf("Stake: set up tx outs\n"); - return true; -} - -bool AnonWallet::SignStakeTx( - const COutputR& coin, CMutableTransaction& txNew, - veil_ringct::TransactionInputsSigContext& inCtx, - veil_ringct::TransactionOutputsSigContext& outCtx) -{ - std::string sError; - int rv; - - CTxIn& txin = txNew.vin[0]; - // This deals with the outputs and the tx as a whole. - uint32_t nSigInputs, nSigRingSize; - txin.GetAnonInfo(nSigInputs, nSigRingSize); - size_t nCols = nSigRingSize; - size_t nRows = nSigInputs + 1; - - std::vector& vKeyImages = txin.scriptData.stack[0]; - uint8_t blindSum[32]; - memset(blindSum, 0, 32); - inCtx.vpsk[nRows-1] = blindSum; - - size_t vDLsize = (1 + nRows * nCols) * 32; // extra element for C, extra row for commitment row - txin.scriptWitness.stack.emplace_back(vDLsize); // creates the vector with |vDLsize| capacity - std::vector &vDL = txin.scriptWitness.stack[1]; - vDL.resize(vDLsize); // creates |vDLsize| elements - - inCtx.vpBlinds.insert(inCtx.vpBlinds.end(), outCtx.vpOutBlinds.begin(), outCtx.vpOutBlinds.end()); - - if (0 != (rv = secp256k1_prepare_mlsag(&inCtx.vm[0], blindSum, - outCtx.vpOutCommits.size(), outCtx.vpOutCommits.size(), nCols, nRows, - &inCtx.vpInCommits[0], &outCtx.vpOutCommits[0], &inCtx.vpBlinds[0]))) { - sError = strprintf("Failed to prepare mlsag with %d.", rv); - return error("%s: %s", __func__, sError); - } - - uint256 hashOutputs = txNew.GetOutputsHash(); - - uint8_t randSeed[32]; - GetStrongRandBytes(randSeed, 32); - if (0 != (rv = secp256k1_generate_mlsag(secp256k1_ctx_blind, &vKeyImages[0], &vDL[0], &vDL[32], - randSeed, hashOutputs.begin(), nCols, nRows, inCtx.secretColumn, - &inCtx.vpsk[0], &inCtx.vm[0]))) { - sError = strprintf("Failed to generate mlsag with %d.", rv); - return error("%s: %s", __func__, sError); - } - - CTransaction tx(txNew); - LogPrintf("Signed stake tx: %s\n", tx.ToString()); - - // Validate the mlsag - if (0 != (rv = secp256k1_verify_mlsag(secp256k1_ctx_blind, hashOutputs.begin(), nCols, - nRows, &inCtx.vm[0], &vKeyImages[0], &vDL[0], &vDL[32]))) { - sError = strprintf("Failed to generate mlsag on initial generation %d.", rv); - return error("%s: %s", __func__, sError); - } - - return true; -} - bool AnonWallet::CreateStealthChangeAccount(AnonWalletDB* wdb) { diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 07b37fb7c0..037adea5f0 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -254,10 +254,6 @@ class AnonWallet void GetAllScanKeys(std::vector& vStealthAddresses); bool IsMyPubKey(const CKeyID& keyId); - bool CoinToTxIn(const COutputR& coin, CTxIn& txin, veil_ringct::TransactionInputsSigContext& inCtx, size_t nRingSize); - bool CreateStakeTxOuts(const COutputR& coin, std::vector& vpout, CAmount nInput, CAmount nReward, CAmount bracketMin, veil_ringct::TransactionOutputsSigContext& outCtx, CTransactionRecord& rtx); - bool SignStakeTx(const COutputR& coin, CMutableTransaction& txNew, veil_ringct::TransactionInputsSigContext& inCtx, veil_ringct::TransactionOutputsSigContext& outCtx); - void LoadToWallet(const uint256 &hash, const CTransactionRecord &rtx); bool LoadTxRecords(); From fae067a0e37fc3e02da65423d4768de925b86811 Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 29 Oct 2022 20:11:04 -0700 Subject: [PATCH 22/25] RingCT Stake: Removed transactionsigcontext. --- src/Makefile.am | 1 - src/veil/proofofstake/stakeinput.h | 17 +++----- src/veil/ringct/anonwallet.h | 1 - src/veil/ringct/transactionsigcontext.h | 54 ------------------------- 4 files changed, 6 insertions(+), 67 deletions(-) delete mode 100644 src/veil/ringct/transactionsigcontext.h diff --git a/src/Makefile.am b/src/Makefile.am index 057c10d81d..9beb294329 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -216,7 +216,6 @@ BITCOIN_CORE_H = \ veil/ringct/stealth.h \ veil/ringct/temprecipient.h \ veil/ringct/transactionrecord.h \ - veil/ringct/transactionsigcontext.h \ veil/ringct/types.h \ veil/ringct/watchonlydb.h \ veil/ringct/watchonly.h \ diff --git a/src/veil/proofofstake/stakeinput.h b/src/veil/proofofstake/stakeinput.h index 65e266f750..79641287f7 100644 --- a/src/veil/proofofstake/stakeinput.h +++ b/src/veil/proofofstake/stakeinput.h @@ -6,11 +6,10 @@ #ifndef PIVX_STAKEINPUT_H #define PIVX_STAKEINPUT_H -#include "veil/ringct/transactionrecord.h" -#include "veil/ringct/transactionsigcontext.h" -#include "veil/zerocoin/accumulatormap.h" #include "chain.h" #include "streams.h" +#include "veil/ringct/transactionrecord.h" +#include "veil/zerocoin/accumulatormap.h" #include "libzerocoin/CoinSpend.h" @@ -19,8 +18,7 @@ class CKeyStore; class CWallet; class CWalletTx; -enum StakeInputType -{ +enum StakeInputType { STAKE_ZEROCOIN, STAKE_RINGCT, }; @@ -41,7 +39,7 @@ class CStakeInput virtual bool GetModifier(uint64_t& nStakeModifier, const CBlockIndex* pindexChainPrev) = 0; virtual bool IsZerocoins() = 0; virtual CDataStream GetUniqueness() = 0; - libzerocoin::CoinDenomination GetDenomination() {return denom;}; + libzerocoin::CoinDenomination GetDenomination() { return denom; }; StakeInputType GetType() const { return nType; } virtual bool CreateCoinStake(CWallet* pwallet, const CAmount& nBlockReward, CMutableTransaction& txCoinStake, bool& retryable, CMutableTransaction& txCoinbase) = 0; @@ -59,9 +57,6 @@ class RingCTStake : public CStakeInput const COutputR coin; CAmount nWeight = 0; - // Details for constructing a tx from this stake input. - veil_ringct::TransactionInputsSigContext tx_inCtx; - veil_ringct::TransactionOutputsSigContext tx_outCtx; CTransactionRecord rtx; // A hash of the key image of the RingCt output. This is used for the CStakeInput's uniqueness. @@ -71,7 +66,7 @@ class RingCTStake : public CStakeInput public: explicit RingCTStake(const COutputR& coin_, uint256 hashPubKey_) - : coin(coin_), tx_inCtx(RING_SIZE, 2), hashPubKey(hashPubKey_) {} + : coin(coin_), hashPubKey(hashPubKey_) {} const CBlockIndex* GetIndexFrom(const CBlockIndex* pindexPrev) override; bool GetTxFrom(CTransaction& tx) override; @@ -170,4 +165,4 @@ class PublicRingCTStake : public CStakeInput bool GetPubkeyHash(uint256& hashPubKey) const; }; -#endif //PIVX_STAKEINPUT_H +#endif // PIVX_STAKEINPUT_H diff --git a/src/veil/ringct/anonwallet.h b/src/veil/ringct/anonwallet.h index 037adea5f0..021659d670 100644 --- a/src/veil/ringct/anonwallet.h +++ b/src/veil/ringct/anonwallet.h @@ -16,7 +16,6 @@ #include #include #include -#include #include #include diff --git a/src/veil/ringct/transactionsigcontext.h b/src/veil/ringct/transactionsigcontext.h deleted file mode 100644 index e67b643967..0000000000 --- a/src/veil/ringct/transactionsigcontext.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2018-2019 Veil developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef VEIL_RINGCT_TRANSACTIONSIGCONTEXT_H -#define VEIL_RINGCT_TRANSACTIONSIGCONTEXT_H - -#include -#include -#include - -#include -#include -#include "veil/ringct/types.h" - -namespace veil_ringct { - -// A wrapper to hold vectors for ringct txn signing, for the inputs. -struct TransactionInputsSigContext { - TransactionInputsSigContext(size_t nCols, size_t nRows) - : vsk(nRows - 1), vpsk(nRows), vm(nCols * nRows * 33), - vpInCommits(nCols * (nRows - 1)) - { - vCommitments.reserve(nCols * (nRows - 1)); - } - - size_t secretColumn; - - std::vector> vMI; - std::vector vPubkeyMatrixIndices; - // SetBlinds - std::vector vsk; - std::vector vpsk; - std::vector vm; - std::vector vCommitments; - std::vector vpInCommits; - ec_point vInputBlinds; - std::vector vpBlinds; -}; - -// A wrapper to hold vectors for ringct txn signing, for the outputs. -struct TransactionOutputsSigContext { - TransactionOutputsSigContext() : vBlindPlain(32) {} - - // ArrangeOutBlinds - std::vector vpOutCommits; - std::vector vpOutBlinds; - std::vector vBlindPlain; - secp256k1_pedersen_commitment plainCommitment; -}; - -} // namespace veil_ringct - -#endif // VEIL_RINGCT_TRANSACTIONSIGCONTEXT_H \ No newline at end of file From 4b7e21f9ac6f7e75df9e3f66be971ca9becdc73a Mon Sep 17 00:00:00 2001 From: Zannick Date: Sat, 29 Oct 2022 20:25:30 -0700 Subject: [PATCH 23/25] RingCT Stake: change required depth. And add include. --- src/wallet/wallet.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 568717f637..5447f4a72f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -25,6 +25,7 @@ #include #include #include