Skip to content

Commit

Permalink
Shield stake proof
Browse files Browse the repository at this point in the history
  • Loading branch information
Duddino authored and panleone committed Sep 13, 2023
1 parent 1e5f3dd commit 83dd286
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 42 deletions.
5 changes: 4 additions & 1 deletion src/blockassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ bool SolveProofOfStake(CBlock* pblock, CBlockIndex* pindexPrev, CMutableTransact

if (pblock->IsProofOfShieldStake()) {
auto& shieldStake = *static_cast<CStakeableShieldNote*>(pStake);
pwallet->ComputeShieldStakeProof(*pblock, shieldStake, shieldStake.note.value());

if (!pwallet->ComputeShieldStakeProof(*pblock, shieldStake, shieldStake.suggestedValue)) {
return false;
}
}
return true;
}
Expand Down
22 changes: 18 additions & 4 deletions src/kernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "kernel.h"

#include "arith_uint256.h"
#include "chainparams.h"
#include "consensus/params.h"
#include "consensus/validation.h"
Expand Down Expand Up @@ -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;
Expand All @@ -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"
Expand All @@ -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<CAmount>((((hashProofOfStake) / diff).Get64() + 1) * 100);
if (total > stakevalue) return stakevalue;
return total;
}

/*
* PoS Validation
Expand Down Expand Up @@ -135,7 +145,7 @@ static bool LoadStakeInput(const CBlock& block, std::unique_ptr<CStakeInput>& 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;

Expand All @@ -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
Expand Down
11 changes: 9 additions & 2 deletions src/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef PIVX_KERNEL_H
#define PIVX_KERNEL_H

#include "arith_uint256.h"
#include "stakeinput.h"

class CStakeKernel {
Expand All @@ -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
Expand All @@ -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 */
Expand All @@ -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
Expand Down
34 changes: 27 additions & 7 deletions src/primitives/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,42 @@ class ShieldStakeProof
{
public:
CAmount amount;
SpendDescription input;
OutputDescription output;
std::vector<unsigned char> 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);
}
};

Expand Down
4 changes: 4 additions & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
57 changes: 34 additions & 23 deletions src/sapling/sapling_validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
90 changes: 87 additions & 3 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <memory>
#include <vector>
#if defined(HAVE_CONFIG_H)
Expand Down Expand Up @@ -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<CStakeableShieldNote*>(stakeOutput.get())->suggestedValue = suggestedValue;
}

// update staker status (time, attemps)
pStakerStatus->SetLastTime(nTxNewTime);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Optional<SaplingWitness>> witnesses;
std::vector<SaplingOutPoint> 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<unsigned char> 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<unsigned char, ZC_MEMO_SIZE> 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<unsigned char> 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;
}
4 changes: 2 additions & 2 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<CStakeInput> ToInput() const override
{
Expand Down

0 comments on commit 83dd286

Please sign in to comment.