From 83dd28649d83ff41908a3d6471e59e8656c659b5 Mon Sep 17 00:00:00 2001 From: Duddino Date: Tue, 12 Sep 2023 13:49:24 +0200 Subject: [PATCH] Shield stake proof --- src/blockassembler.cpp | 5 +- src/kernel.cpp | 22 ++++++-- src/kernel.h | 11 +++- src/primitives/block.h | 34 ++++++++--- src/rpc/blockchain.cpp | 4 ++ src/sapling/sapling_validation.cpp | 57 +++++++++++-------- src/wallet/wallet.cpp | 90 +++++++++++++++++++++++++++++- src/wallet/wallet.h | 4 +- 8 files changed, 185 insertions(+), 42 deletions(-) diff --git a/src/blockassembler.cpp b/src/blockassembler.cpp index ea3c9f769e6fb2..5e10955c4a8b1b 100644 --- a/src/blockassembler.cpp +++ b/src/blockassembler.cpp @@ -108,7 +108,10 @@ bool SolveProofOfStake(CBlock* pblock, CBlockIndex* pindexPrev, CMutableTransact if (pblock->IsProofOfShieldStake()) { auto& shieldStake = *static_cast(pStake); - pwallet->ComputeShieldStakeProof(*pblock, shieldStake, shieldStake.note.value()); + + if (!pwallet->ComputeShieldStakeProof(*pblock, shieldStake, shieldStake.suggestedValue)) { + return false; + } } return true; } diff --git a/src/kernel.cpp b/src/kernel.cpp index d6c397162e4b2a..7d66fafb471378 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -7,6 +7,7 @@ #include "kernel.h" +#include "arith_uint256.h" #include "chainparams.h" #include "consensus/params.h" #include "consensus/validation.h" @@ -70,7 +71,7 @@ uint256 CStakeKernel::GetHash() const } // Check that the kernel hash meets the target required -bool CStakeKernel::CheckKernelHash(bool fSkipLog) const +bool CStakeKernel::CheckKernelHash(bool fSkipLog) { // Get weighted target arith_uint256 bnTarget; @@ -80,7 +81,8 @@ bool CStakeKernel::CheckKernelHash(bool fSkipLog) const // Check PoS kernel hash const arith_uint256& hashProofOfStake = UintToArith256(GetHash()); const bool res = hashProofOfStake < bnTarget; - + suggestedValue = ComputeSuggestedValue(stakeValue, bnTarget, hashProofOfStake); + LogPrintf("%d\n", suggestedValue); if (!fSkipLog || res) { LogPrint(BCLog::STAKING, "%s : Proof Of Stake:" "\nstakeModifier=%s" @@ -97,6 +99,14 @@ bool CStakeKernel::CheckKernelHash(bool fSkipLog) const return res; } +CAmount CStakeKernel::ComputeSuggestedValue(CAmount stakevalue, const arith_uint256& bnTarget, const arith_uint256& hashProofOfStake) const +{ + arith_uint256 diff; + diff.SetCompact(nBits); + auto total = static_cast((((hashProofOfStake) / diff).Get64() + 1) * 100); + if (total > stakevalue) return stakevalue; + return total; +} /* * PoS Validation @@ -135,7 +145,7 @@ static bool LoadStakeInput(const CBlock& block, std::unique_ptr& st * @param[in] nTimeTx new blocktime * @return bool true if stake kernel hash meets target protocol */ -bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx) +bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx, CAmount* suggestedValue) { if (!stakeInput) return false; @@ -146,7 +156,11 @@ bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int // Verify Proof Of Stake CStakeKernel stakeKernel(pindexPrev, stakeInput, nBits, nTimeTx); - return stakeKernel.CheckKernelHash(true); + bool check = stakeKernel.CheckKernelHash(true); + if (suggestedValue) + *suggestedValue = stakeKernel.GetSuggestedValue(); + + return check; } // This checks if the provided note value is valid diff --git a/src/kernel.h b/src/kernel.h index 8e2118d7123825..8a13de0ced1d53 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -8,6 +8,7 @@ #ifndef PIVX_KERNEL_H #define PIVX_KERNEL_H +#include "arith_uint256.h" #include "stakeinput.h" class CStakeKernel { @@ -26,7 +27,11 @@ class CStakeKernel { uint256 GetHash() const; // Check that the kernel hash meets the target required - bool CheckKernelHash(bool fSkipLog = false) const; + bool CheckKernelHash(bool fSkipLog = false); + CAmount GetSuggestedValue() const + { + return suggestedValue; + } private: // kernel message hashed @@ -37,6 +42,8 @@ class CStakeKernel { // hash target unsigned int nBits{0}; // difficulty for the target CAmount stakeValue{0}; // target multiplier + CAmount suggestedValue{0}; + CAmount ComputeSuggestedValue(CAmount stakevalue, const arith_uint256& bnTarget, const arith_uint256& hashProofOfStake) const; }; /* PoS Validation */ @@ -50,7 +57,7 @@ class CStakeKernel { * @param[in] nTimeTx new blocktime * @return bool true if stake kernel hash meets target protocol */ -bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx); +bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx, CAmount* suggestedValue = nullptr); /* * CheckProofOfStake Check if block has valid proof of stake diff --git a/src/primitives/block.h b/src/primitives/block.h index 27bf49c60da0f2..6512f52ec90af9 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -81,22 +81,42 @@ class ShieldStakeProof { public: CAmount amount; - SpendDescription input; - OutputDescription output; - std::vector bindingSig; + uint256 inputCv; + uint256 rk; + SpendDescription::spend_auth_sig_t spendSig; + libzcash::GrothProof inputProof = {{0}}; + + uint256 outputCv; + uint256 epk; + uint256 cmu; + libzcash::GrothProof outputProof = {{0}}; + libzcash::GrothProof sig = {{0}}; void SetNull() { amount = 0; - output = OutputDescription(); - bindingSig.clear(); + inputCv.SetNull(); + spendSig = {{0}}; + rk.SetNull(); + inputProof = {{0}}; + outputCv.SetNull(); + epk.SetNull(); + cmu.SetNull(); + outputProof = {{0}}; } SERIALIZE_METHODS(ShieldStakeProof, obj) { READWRITE(obj.amount); - READWRITE(obj.output); - READWRITE(obj.bindingSig); + READWRITE(obj.inputCv); + READWRITE(obj.rk); + READWRITE(obj.spendSig); + READWRITE(obj.inputProof); + READWRITE(obj.epk); + READWRITE(obj.cmu); + READWRITE(obj.outputCv); + READWRITE(obj.outputProof); + READWRITE(obj.sig); } }; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c6767903d7d4c4..045dc84ceba5b4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -168,6 +168,10 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn result.pushKV("bits", strprintf("%08x", block.nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); + if (block.IsProofOfShieldStake()) { + auto& p = block.shieldStakeProof; + result.pushKV("shieldproofamount", p.amount); + } if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); diff --git a/src/sapling/sapling_validation.cpp b/src/sapling/sapling_validation.cpp index b2251e62564ceb..8eec33b9ab3403 100644 --- a/src/sapling/sapling_validation.cpp +++ b/src/sapling/sapling_validation.cpp @@ -232,36 +232,47 @@ bool CheckShieldStake(const CBlock& block, CValidationState& state, const CChain if (!block.IsProofOfShieldStake()) { return false; } - return true; - // TODO: check rest - // In addition to the regular checks for a tx, we also have to ensure that the provided - // shieldStakeAmount is valid. To do this without creating a new circuit, the staker inserts - // a dummy note in the proof, so that Input - DummyNote = shieldStakeAmount. - // To prevent others from potentially spending that note, we change the output proof sighash - auto* saplingCtx = librustzcash_sapling_verification_ctx_init(); + LogPrintf("%d", block.shieldStakeProof.amount); + const auto& saplingData = block.vtx[1].get()->sapData.get(); - const auto& spend = saplingData.vShieldedSpend[0]; + auto ctx = librustzcash_sapling_verification_ctx_init(); + const auto& inputNote = saplingData.vShieldedSpend[0]; + const auto& p = block.shieldStakeProof; + const int DOS_LEVEL_BLOCK = 100; + uint256 dataToBeSigned; - // TODO: get the sighash - if (!librustzcash_sapling_check_spend(saplingCtx, spend.cv.begin(), spend.anchor.begin(), spend.nullifier.begin(), spend.rk.begin(), spend.zkproof.begin(), spend.spendAuthSig.begin(), dataToBeSigned.begin())) { - librustzcash_sapling_verification_ctx_free(saplingCtx); - return false; - } - const auto& output = block.shieldStakeProof.output; - if (!librustzcash_sapling_check_output(saplingCtx, output.cv.begin(), output.cmu.begin(), output.ephemeralKey.begin(), output.zkproof.begin())) { - librustzcash_sapling_verification_ctx_free(saplingCtx); - return false; + try { + // TODO: write signature for shield + // dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL, 0, SIGVERSION_SAPLING); + } catch (const std::logic_error& ex) { + // A logic error should never occur because we pass NOT_AN_INPUT and + // SIGHASH_ALL to SignatureHash(). + return state.DoS(100, error("%s: error computing signature hash", __func__), + REJECT_INVALID, "error-computing-signature-hash"); } - // dataToBeSigned = ...; + if (!librustzcash_sapling_check_spend(ctx, p.inputCv.begin(), inputNote.anchor.begin(), inputNote.nullifier.begin(), p.rk.begin(), p.inputProof.begin(), p.spendSig.begin(), dataToBeSigned.begin())) { + librustzcash_sapling_verification_ctx_free(ctx); + return state.DoS( + DOS_LEVEL_BLOCK, + error("%s: Sapling spend description invalid", __func__), + REJECT_INVALID, "bad-txns-sapling-spend-description-invalid"); + } - if (!librustzcash_sapling_final_check(saplingCtx, block.shieldStakeProof.amount, block.shieldStakeProof.bindingSig.data(), dataToBeSigned.begin())) { - librustzcash_sapling_verification_ctx_free(saplingCtx); - return false; + if (!librustzcash_sapling_check_output(ctx, p.outputCv.begin(), p.cmu.begin(), p.epk.begin(), p.outputProof.begin())) { + librustzcash_sapling_verification_ctx_free(ctx); + return state.DoS(100, error("%s: Sapling output description invalid", __func__), + REJECT_INVALID, "bad-txns-sapling-output-description-invalid"); } - librustzcash_sapling_verification_ctx_free(saplingCtx); - LogPrintf("Phonenuix"); + if (!librustzcash_sapling_final_check(ctx, block.shieldStakeProof.amount, block.shieldStakeProof.sig.data(), dataToBeSigned.begin())) { + librustzcash_sapling_verification_ctx_free(ctx); + return state.DoS( + 100, + error("%s: Sapling binding signature invalid", __func__), + REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid"); + } + librustzcash_sapling_verification_ctx_free(ctx); return true; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a2c81dbd2f6101..952cd6321c25a2 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,13 +5,20 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "optional.h" #include "consensus/params.h" #include "librustzcash.h" +#include "optional.h" #include "primitives/transaction.h" +#include "sapling/address.h" +#include "sapling/incrementalmerkletree.h" +#include "sapling/note.h" +#include "sapling/sapling_transaction.h" +#include "sapling/sapling_util.h" #include "sapling/saplingscriptpubkeyman.h" #include "sapling/zip32.h" +#include "serialize.h" #include "stakeinput.h" +#include "version.h" #include #include #if defined(HAVE_CONFIG_H) @@ -3426,7 +3433,12 @@ CStakeableInterface* CWallet::CreateCoinStake(const CBlockIndex& indexPrev, unsi } ++nAttempts; - bool fKernelFound = Stake(&indexPrev, &*stakeInput, nBits, nTxNewTime); + CAmount suggestedValue; + bool fKernelFound = Stake(&indexPrev, &*stakeInput, nBits, nTxNewTime, &suggestedValue); + // Refactor maybe + if (stakeInput->IsShieldPIV()) { + static_cast(stakeOutput.get())->suggestedValue = suggestedValue; + } // update staker status (time, attemps) pStakerStatus->SetLastTime(nTxNewTime); @@ -3466,6 +3478,7 @@ bool CWallet::CreateShieldReward(const CBlockIndex& indexPrev, const CStakeableS noteop.emplace_back(shieldNote.op); m_sspk_man->GetSaplingNoteWitnesses(noteop, witnesses, anchor); txBuilder.AddSaplingSpend(sk.expsk, shieldNote.note, anchor, witnesses[0].get()); + const auto& txTrial = txBuilder.Build().GetTx(); if (txTrial) { txNew = CMutableTransaction(*txTrial); @@ -5011,10 +5024,81 @@ CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, const CBlockIndex*& _pindex) : COutput(txIn, iIn, nDepthIn, true /*fSpendable*/, true /*fSolvable*/, true /*fSafe*/), pindex(_pindex) {} -bool CWallet::ComputeShieldStakeProof(CBlock& block, CStakeableShieldNote& note, CAmount suggestedValue) +bool CWallet::ComputeShieldStakeProof(CBlock& block, CStakeableShieldNote& note, CAmount suggestedValue) const { assert(block.IsProofOfShieldStake()); assert(note.note.value() >= suggestedValue); + + const auto& spendNote = block.vtx[1]->sapData->vShieldedSpend[0]; + auto* ctx = librustzcash_sapling_proving_ctx_init(); + libzcash::SaplingExtendedSpendingKey sk; + if (!GetSaplingExtendedSpendingKey(note.address, sk)) { + return false; + } + + uint256 alpha; + uint256 anchor; + uint256 dataToBeSigned; + std::vector> witnesses; + std::vector noteop; + noteop.emplace_back(note.op); + m_sspk_man->GetSaplingNoteWitnesses(noteop, witnesses, anchor); + + librustzcash_sapling_generate_r(alpha.begin()); + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << witnesses[0]->path(); + std::vector witness(ss.begin(), ss.end()); + assert(anchor == spendNote.anchor); + librustzcash_sapling_spend_sig( + sk.expsk.ask.begin(), + alpha.begin(), + dataToBeSigned.begin(), + block.shieldStakeProof.spendSig.data()); + + if (!librustzcash_sapling_spend_proof(ctx, sk.expsk.full_viewing_key().ak.begin(), + sk.expsk.nsk.begin(), + note.note.d.data(), + note.note.r.begin(), + alpha.begin(), + note.note.value(), + anchor.begin(), + witness.data(), + block.shieldStakeProof.inputCv.begin(), + block.shieldStakeProof.rk.begin(), + block.shieldStakeProof.inputProof.begin())) { + librustzcash_sapling_proving_ctx_free(ctx); + return false; + } + uint256 dummyEsk; + + CAmount amount = note.note.value() - suggestedValue; + uint256 rcm; + librustzcash_sapling_generate_r(rcm.begin()); + libzcash::SaplingPaymentAddress paymentAddress(note.address); + const std::array emptyMemo = {{0xF6}}; + libzcash::SaplingNote dummyNote(paymentAddress.d, paymentAddress.pk_d, amount, rcm); + libzcash::SaplingNotePlaintext notePlaintext(dummyNote, emptyMemo); + auto res = notePlaintext.encrypt(dummyNote.pk_d); + if (!res) return false; + auto& encryptor = res->second; + + ss = CDataStream(SER_NETWORK, PROTOCOL_VERSION); + ss << paymentAddress; + std::vector addressBytes(ss.begin(), ss.end()); + + if (!librustzcash_sapling_output_proof(ctx, encryptor.get_esk().begin(), addressBytes.data(), rcm.begin(), amount, block.shieldStakeProof.outputCv.begin(), block.shieldStakeProof.outputProof.begin())) { + librustzcash_sapling_proving_ctx_free(ctx); + return false; + } + block.shieldStakeProof.cmu = *dummyNote.cmu(); + block.shieldStakeProof.epk = encryptor.get_epk(); + + if (!librustzcash_sapling_binding_sig(ctx, suggestedValue, dataToBeSigned.data(), block.shieldStakeProof.sig.begin())) { + librustzcash_sapling_proving_ctx_free(ctx); + return false; + } + librustzcash_sapling_proving_ctx_free(ctx); block.shieldStakeProof.amount = suggestedValue; + LogPrintf("%s : Shield Stake proof generated with value %d\n", __func__, suggestedValue); return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9c00b3d9e45570..943a11ba27fecd 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1132,7 +1132,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetAvailableShieldedBalance(bool fUseCache = true) const; CAmount GetUnconfirmedShieldedBalance() const; - bool ComputeShieldStakeProof(CBlock& block, CStakeableShieldNote& note, CAmount suggestedValue); + bool ComputeShieldStakeProof(CBlock& block, CStakeableShieldNote& note, CAmount suggestedValue) const; static CFeeRate minTxFee; @@ -1347,7 +1347,7 @@ class CStakeableShieldNote : public SaplingNoteEntry, public CStakeableInterface { public: uint256 nullifier; - + CAmount suggestedValue = 0; explicit CStakeableShieldNote(const SaplingNoteEntry& _note, uint256 _nullifier) : SaplingNoteEntry(_note), nullifier(_nullifier) {} std::unique_ptr ToInput() const override {